API Unicraft
Читайте финансы строительной компании по мере того, как сотрудники вносят их в Unicraft - закупки, аренду, зарплаты подённых рабочих и по табелю, платежи, акты (форма 2), поступления и подотчётные авансы - и передавайте обратно, что ваша программа сделала с каждой записью. Создано в первую очередь для подключения бухгалтерской программы компании, чтобы никто не вносил один и тот же платёж дважды.
Начало работы
- Администратор компании открывает Компания → Интеграции в Unicraft, создаёт ключ и копирует его. Он отображается только один раз.
- Каждый вызов отправляется на
https://unicraft.ge/api/v1/…с ключом в заголовкеAuthorization. - Читайте ленту изменений с самого начала (
after=0): вы получите всю историю компании начиная с самых старых записей, а затем всё новое по мере появления.
curl https://unicraft.ge/api/v1/me \
-H "Authorization: Bearer uck_your_key"
{
"company": { "id": "cc39…", "name": "Your Construction LLC" },
"key": { "id": "eddd…", "name": "ORIS connector" },
"scopes": ["accounting:read", "accounting:write"]
}
Ключи
Ключ принадлежит одной компании и открывает записи только этой компании. Передавайте его как Authorization: Bearer uck_… (или X-Api-Key: uck_…). Храните его на своём сервере и никогда на веб-странице.
| Область доступа | Что разрешено ключу |
|---|---|
accounting:read | Чтение проектов, ленты изменений и отдельных записей. Есть у каждого ключа. |
accounting:write | Также отметка записей как отправленных, с ошибкой или пропущенных (/v1/sync) и передача данных об оплате в вашей программе (/v1/paid). Требуется для коннектора. |
people:read | Также чтение личных номеров рабочих (worker.personal_no объекта timesheet_month) для расчёта зарплаты. Без него они приходят как null. |
Ключ может совершать до 600 вызовов в минуту. Администратор может отозвать ключ в любое время; после этого возвращается 401. У компании есть до 10 ключей - выделите каждой программе свой.
Лента изменений
Каждая добавленная, изменённая или удалённая закупка, аренда, месяц подённых рабочих, месяц табеля, платёж, акт, входящий платёж и подотчётный аванс получает следующий номер (seq) в ленте изменений компании. Вы сохраняете последний имеющийся у вас номер и запрашиваете то, что появилось после него.
- Каждое изменение несёт запись в её текущем виде (
data), а не в том, какой она была раньше. Запись, изменённая пять раз, появляется пять раз, каждый раз со своим последним состоянием - обрабатывайте каждую по еёid, чтобы повторное обращение к ней лишь обновляло созданное вами ранее. op- этоupsert(добавленная или изменённая) илиdelete. У удалённой записиdataимеет значениеnull; то же самое относится и к записи, удалённой после внесения изменения.- Изменение выдаётся после того, как пройдёт одна минута, чтобы завершающееся сохранение никогда не оказалось позади номера, который вы уже прошли. Ожидайте поступления записанных данных примерно через минуту.
- Запись также поступает повторно, когда отображаемые в ней данные меняются в другом месте: подотчётный аванс - когда на него что-то куплено, заявка - когда поступают её товары, аренда с подённой оплатой - каждую ночь (её стоимость растёт каждый день), месяц подённых рабочих - при изменении журнала, дня табеля или платежа по нему.
- Сопоставляйте записи со своими по
id(а субподрядчика, поставщика или проект - по егоid, где он есть), но ни в коем случае не по названию: названия копируются в том виде, в каком они были на момент внесения изменения. - Повторяйте запрос, пока
moreне станетfalse, затем опрашивайте раз в несколько минут.
Эндпоинты
/v1/meКомпания ключа, название ключа и его scopes. Быстрая проверка работоспособности ключа.
/v1/projectsПроекты компании - для сопоставления каждого со своими (например, центр затрат ORIS).
{ "projects": [ { "id": "bb2e…", "name": "Vake Residence", "name_ka": "ვაკე რეზიდენსი", "name_ru": null,
"currency": "GEL", "start_date": "2026-03-01", "end_date": null, "closed_on": null } ] }
/v1/changes?after=0&limit=100after: последний seq, который у вас есть (0 для начала). limit: от 1 до 500, 100 если не указано.
{
"changes": [
{ "seq": 19, "entity": "purchase", "id": "3d04…", "op": "upsert", "at": "2026-10-24T12:39:15Z",
"project_id": "bb2e…",
"data": { "entity": "purchase", "id": "3d04…", "date": "2026-10-24", "item": "Cement M400",
"quantity": 12, "unit": "bag", "unit_price": 16.5, "amount": 198, "currency": "GEL",
"supplier": { "name": "Beton Service LLC", "name_ka": "შპს ბეტონ სერვისი", "code": "205123456" },
"vat": "included", "vat_rate": 18, "net": 167.8, "vat_amount": 30.2,
"waybill_no": "0712345678", "paid_on": "2026-10-24", "due_on": null,
"project": { "id": "bb2e…", "name": "Vake Residence", "currency": "GEL" }, … } }
],
"next": 19,
"more": false
}
/v1/records/{entity}/{id}Одна запись в её текущем виде - та же структура, что и у data в изменении. 404, если она удалена или не принадлежит компании.
/v1/syncЧто ваша программа сделала с записями - см. Отметка об отправке. Требуется accounting:write.
/v1/paidЧто было оплачено в вашей программе, отмеченное оплаченным в Unicraft - см. Оплачено в вашей программе. Требуется accounting:write.
Записи
Каждая запись содержит entity, id, project (id, name, name_ka, name_ru, currency) и currency. Даты - YYYY-MM-DD; суммы - числа в валюте проекта. Названия приводятся на английском, грузинском и русском языках там, где они были так введены.
| сущность | Что это такое | Собственные поля |
|---|---|---|
project | Проект создан, переименован или удален. При удалении все содержимое удаляется вместе с ним - его собственные записи не приходят каждая как удаление. | name, name_ka, name_ru, currency, start_date, end_date, closed_on |
purchase | Позиция, закупленная для проекта: материалы, инструменты, услуги. Оплачена сразу, в кредит (due_on) или из подотчётного аванса. | purchase_id (позиции, закупленные вместе), date, kind, item, quantity, unit, unit_price, amount, supplier (name, code: его идентификационный код), vat, vat_rate, net, vat_amount (см. НДС), waybill_no, due_on, paid_on (null: задолженность), paid_from (cash_advance или null), cash_advance_id, request_id, note |
request | Утверждённая заявка на материалы, оплаченная поставщику выбранного предложения (товары по ней становятся закупками по мере поступления). | number, status, date, supplier (name, code), paid_on, amount, vat, vat_rate, net, vat_amount, invoice_no, waybill_nos |
site_payment | Оплата подённым рабочим или охране за месяц, либо аренда техники. | kind, month (YYYY-MM), paid_on, amount, rental (equipment, supplier), note |
subcontractor_payment | Платёж субподрядчику по одному из пунктов его графика работ с удержанием - или возврат удержания. | kind (interim или retention_release), paid_on, amount (оплачено наличными), retention (удержано), gross, subcontractor, item, act_id (акт (форма 2), который оплачивается, если есть), note |
act | Акт (форма 2): заказчика (к получению) или субподрядчика (к выплате). | party (client или subcontractor), number, period_from, period_to, date, total, retention, retention_pct, is_final, signed_on, paid_on (оплачено или получено), money_in_id, subcontractor |
money_in | Деньги, полученные по проекту: от заказчика, покупателя квартиры, кредит. | kind, date, amount, from, certificate_no, retention (true: возврат удержания), note |
rental | Аренда техники: задолженность перед поставщиком по мере прохождения дней (после оплаты это site_payment). | equipment, supplier, date (начало), daily_rate, counted (days: фиксированное количество дней от начала; timesheet: дни нахождения машины в табеле, полдня как половина), days, months ([{ month, days, amount }] по сегодняшний день), cost_to_date, note |
labour_month | Месяц подённых рабочих (kind: labour) или охраны (guard) проекта: начисленная задолженность, рассчитанная так же, как на странице Бухгалтерия - количество людей из ежедневных отчётов, умноженное на их дневную ставку, либо по табелю, если он ведётся на объекте. После оплаты это site_payment того же типа и месяца. Это заработная плата к начислению. | kind, month (YYYY-MM), days (человеко-дни), amount (начислено по сегодняшний день), paid |
timesheet_month | Один месяц рабочего в табеле: кто сколько заработал, для начисления зарплаты и подоходного налога. Это расшифровка labour_month - не проводите её в расходах повторно. Её id остаётся неизменным для этого рабочего и месяца; запись передаётся снова при любом изменении дня. | month (YYYY-MM), worker (name, personal_no, trade, day_rate), days (полдня считаются как 0.5), amount |
cash_advance | Подотчётный аванс (საქვეანგარიშო). Купленное на эти деньги оформляется как закупки с paid_from: "cash_advance". | date, amount, purpose, report_by, holder (name), spent, settled_on, settled_amount (+ возврат, − возмещение подотчётному лицу), note |
НДС. vat принимает значение included (сумма включает 18% НДС: net - без НДС, vat_amount - НДС к вычету), none (продавец не начисляет НДС) или null (не указано - net и vat_amount также null). Сумма всегда равна фактически оплаченной.
У закупки с amount 0 пока нет цены - офис оценит её позже, и тогда она снова поступит через ленту изменений. Бухгалтерский коннектор должен пропускать её до этого момента.
Отметка об отправке
После того как ваша программа обработает записи, сообщите Unicraft. Бухгалтер компании видит это рядом с каждой строкой на странице Бухгалтерия: Отправлено в ORIS, либо ошибку полностью, чтобы ничто не потерялось незаметно.
curl -X POST https://unicraft.ge/api/v1/sync \
-H "Authorization: Bearer uck_your_key" -H "Content-Type: application/json" \
-d '{ "system": "oris",
"items": [
{ "entity": "purchase", "id": "3d04…", "status": "sent", "external_id": "31" },
{ "entity": "act", "id": "9a1c…", "status": "error", "message": "Account [3110 7] is closed" }
] }'
{ "marked": 2, "of": 2 }
system указывает имя вашей программы (oris, fina, balance или вашей собственной). status - это sent, error или skipped (намеренно пропущено - например, закупка, у которой ещё нет цены, или тип, который ваша программа не ведёт; бухгалтер видит запись как пропущенную, а не как отправленную). Сохраняйте собственный id в external_id - например, транзакцию ORIS - чтобы обновить или удалить её при изменении записи. Повторная отметка записи заменяет предыдущую. До 500 элементов за вызов; записи, не принадлежащие компании, исключаются из marked.
Оплачено в вашей программе
Когда платёж проведён в бухгалтерской программе, сообщите Unicraft, и там он тоже будет отмечен как оплаченный - на странице Бухгалтерия его повторно отмечать не придётся.
curl -X POST https://unicraft.ge/api/v1/paid \
-H "Authorization: Bearer uck_your_key" -H "Content-Type: application/json" \
-d '{ "system": "oris",
"items": [
{ "entity": "purchase", "id": "3d04…", "paid_on": "2026-10-25", "external_id": "PAY-118" },
{ "entity": "labour_month", "id": "71be…", "paid_on": "2026-10-25", "amount": 2400, "external_id": "PAY-119" }
] }'
{ "results": [ { "entity": "purchase", "id": "3d04…", "result": "paid" },
{ "entity": "labour_month", "id": "71be…", "result": "paid" } ] }
| сущность | Что происходит |
|---|---|
purchase | Вся закупка (каждая строка с тем же purchase_id) отмечается оплаченной на paid_on, если оставалась задолженность. |
request | Отмечается оплаченным так, как это сделал бы бухгалтер: по amount или на сумму выбранного предложения. Уже полученные по нему товары отмечаются оплаченными вместе с ним; отдел закупок уведомляется. |
labour_month, rental | Записывается платёж на сумму amount (или на весь остаток долга). Укажите external_id (номер вашего платежа): платёж с тем же номером, отправленный повторно, дважды проведён не будет. |
paid_on - сегодня, если не указано; amount не обязательно. По каждому элементу возвращается result: paid, already_paid, not_approved (заявка ещё не утверждена), not_found (не принадлежит компании), future_date или bad_amount. Платёж возвращается к вам в ленте изменений (запись изменилась или появился новый site_payment) - платёж проведён вашей программой, поэтому пометьте его как skipped.
Ошибки
Ошибка возвращается в виде JSON: { "error": { "code": "bad_key", "message": "…" } }.
| Статус | код | Значение |
|---|---|---|
| 400 | bad_after, bad_id, bad_json, bad_body, bad_item, too_long, refused | Запрос неверен - в сообщении указано, что нужно изменить. |
| 401 | no_key, bad_key | Ключ отсутствует, недействителен или был отозван. |
| 403 | scope | У ключа нет прав на это действие. |
| 404 | no_route, no_entity, not_found | Такой адрес, тип записи или запись не существуют. |
| 429 | too_many | Более 600 вызовов в минуту - подождите и повторите попытку. |
| 500 | server | Что-то пошло не так с нашей стороны - попробуйте снова. |
Коннектор, вкратце
Что делает коннектор бухгалтерской программы каждые несколько минут:
let after = loadLastSeq(); // 0 the first time
for (;;) {
const r = await get(`/v1/changes?after=${after}&limit=200`);
const marks = [];
for (const c of r.changes) {
try {
if (!c.data) removeEntry(c.entity, c.id); // deleted in Unicraft
else if (c.data.amount === 0) { marks.push({ entity: c.entity, id: c.id, status: 'skipped' }); continue; }
else {
const own = upsertEntry(c.data); // e.g. an ORIS transaction, by c.id
marks.push({ entity: c.entity, id: c.id, status: 'sent', external_id: String(own) });
}
} catch (e) {
marks.push({ entity: c.entity, id: c.id, status: 'error', message: e.message });
}
}
if (marks.length) await post('/v1/sync', { system: 'oris', items: marks });
saveLastSeq(after = r.next);
if (!r.more) break;
}
На какие счета относится запись - поставщики, банк, касса, материалы - это собственный выбор компании, который настраивается в коннекторе один раз. Закупка, уже внесенная в вашу программу по накладной rs.ge, является той же закупкой: сопоставьте ее по waybill_no и code поставщика вместо повторного проведения. Вопросы или разработка коннектора для вашей программы: [email protected].