PDF
Розділ: Обмін: конверти, pull і ack

Обмін з обліковою системою

Як облікова система передає в ApexSFA довідники, залишки й продажі та забирає заявки, оплати, приходи й рейси: конверти, pull/ack, повтори.

Цей розділ описує, як облікова система дистрибʼютора обмінюється даними з ApexSFA: як передати в ApexSFA довідники, залишки, продажі й борги і як забрати з ApexSFA заявки торгових, оплати, приходи, інвентаризації, заявки на обладнання та рейси.

  • Базовий URL: https://api.apexsfa.com/api/v1
  • Формат: JSON, UTF-8, Content-Type: application/json, лише POST.
  • Автентифікація — підпис запиту ключем обміну (див. 2.2).

Примітка. Шляхи містять технічний сегмент /webhooks/1c/ — це назва каналу в API, змінювати його не треба. Він однаковий для будь-якої облікової системи.

Не хочете програмувати обмін? Для облікових систем BAS і сумісних є готова обробка обміну.


3.1. Модель обміну

Обидва напрямки ініціює облікова система — їй не потрібна публічна адреса, вистачить вихідного HTTPS:

НапрямокЯкЕндпоінти
Облікова система → ApexSFAоблікова система надсилає «конверти»POST /api/v1/webhooks/1c/inbound/batch, POST /api/v1/webhooks/1c/inbound
ApexSFA → облікова системаоблікова система сама забирає документи з черги (pull) і підтверджує обробку (ack)POST /api/v1/webhooks/1c/outbox/pull, POST /api/v1/webhooks/1c/outbox/ack
  Облікова система                                        ApexSFA
  ────────────────                                        ───────
  довідники, ціни, залишки,   ── inbound/batch ──▶   записує в тенант
  продажі, борги, каса, рейси

  заявки, оплати, приходи,    ◀── outbox/pull ───   черга документів
  інвентаризації, рейси       ─── outbox/ack ───▶   позначка «синхронізовано»

Тенант в обміні

Кожна точка синхронізації (філія компанії, яка веде свою базу обліку) — окремий тенант ApexSFA зі своїм tenant_id і своїм ключем обміну. Усе, що надіслано з ключем тенанта, лягає в цей тенант; коди філій у payload конвертів (branch_external_id тощо) не впливають на те, куди лягають дані. Черга документів для забору теж одна на тенант — її забирає одна база обліку.


3.2. Автентифікація

Кожен запит до /api/v1/webhooks/1c/* містить:

ЗаголовокОбовʼязковийОпис
X-Tenant-Idтак*UUID тенанта. *Можна не передавати, якщо в JSON-тілі є поле tenant_id.
X-Signatureтакt=<unix-час>,v2=<hex> — підпис тіла ключем обміну тенанта.
Content-Typeтакapplication/json
v2 = lowercase_hex( SHA256( "<t>" + "." + <тіло запиту як є, байти UTF-8> + "." + <ключ обміну> ) )
X-Signature: t=<t>,v2=<v2>

Повний опис, приклади на трьох мовах і контрольний приклад — у 2.2.

Коли повертається 401

Відповідь 401 з тілом {"error": {"code": "INVALID_CREDENTIALS", "message": "...", "details": [], "request_id": "..."}}:

messageПричина
Invalid X-Tenant-Idзаголовок не є UUID
Cannot resolve tenant / Tenant requiredтенант не передано ні в заголовку, ні в тілі
… integration not configured for tenantобмін для тенанта не підключено або вимкнено
Tenant is suspendedтенант призупинено — обмін зупинено
Invalid signatureневірний підпис, час поза ±300 с, повтор підпису або не той ключ

3.3. Облікова система → ApexSFA: конверти

Усі дані з облікової системи передаються конвертами:

ПолеТипОбовʼязковеОпис
entity_typestringтакТип сутності (перелік — у 3a). Невідомий тип → 422.
operationstringтакcreate | update | upsert | delete. На практиці надсилайте upsert; delete — деактивація або зняття запису (поведінка залежить від типу).
external_idstring, 1..50такІдентифікатор обʼєкта в обліковій системі (зазвичай GUID). Ключ ідемпотентності: повторний конверт з тим самим entity_type + external_id оновлює той самий запис, дубля не буде.
payloadobjectні (за замовч. {})Поля сутності — див. 3a.

Типи сутностей коротко:

  • довідники: branch, warehouse, supplier, supplier_point, price_type, manufacturer, product_category, product, product_external_code, counterparty, client (торгова точка), agent (торговий), client_assignment, driver, vehicle, transport_list, equipment_type, equipment_unit, manufacturer_price_type, manufacturer_payment_term;
  • ціни й залишки: price_list_item, product_stock, product_stock_snapshot, wms_stock, stock_document, excise_marks;
  • продажі, борги, каса: sale, goods_document, goods_period, debt_document, debt_reset, cash_document, cash_balance, cash_period, order_state, order_state_period;
  • логістика й рейси: trip, logistics_order, logistics_manufacturer, logistics_own_manufacturer, logistics_counterparty, logistics_point, logistics_warehouse, logistics_product_category, logistics_product, logistics_product_code, logistics_stock.

POST /api/v1/webhooks/1c/inbound/batch

Пакет конвертів — основний спосіб передачі.

ПолеТипОбовʼязковеОпис
tenant_idUUIDніЯкщо немає заголовка X-Tenant-Id.
partialboolні, за замовч. truetrue — кожен конверт обробляється окремо, помилка одного не скасовує інших. false — «все або нічого»: перша помилка відкочує весь пакет.
itemsarrayтак1..5000 конвертів. Рекомендуємо пакети по 1000.

Порядок конвертів у пакеті має значення: обробка йде послідовно, тому довідники-«батьки» ставте раніше за залежні записи (див. порядок первинного завантаження).

Порада. Завжди надсилайте дані через /inbound/batch з partial: true — так кожен рядок отримує власний результат, а збій одного не зупиняє інші.

Відповідь 200:

JSON
{
  "data": {
    "accepted_count": 2,
    "failed_count": 1,
    "results": [
      {"index": 0, "entity_type": "warehouse", "external_id": "8d3b4c5e-6f70-11ee-9a0b-0050569a1b2c",
       "accepted": true, "entity_id": "0192e004-5555-7ccc-8ddd-4c5d6e7f8091", "operation_performed": "upsert",
       "error": null, "details": null},
      {"index": 1, "entity_type": "client", "external_id": "5b1f2c3d-4e5f-11ee-9a0b-0050569a1b2c",
       "accepted": true, "entity_id": "0192e001-2222-7ccc-8ddd-1e2f3a4b5c6d", "operation_performed": "upsert",
       "error": null, "details": null},
      {"index": 2, "entity_type": "supplier_point", "external_id": "00000000-0000-0000-0000-000000000000",
       "accepted": false, "entity_id": null, "operation_performed": null,
       "error": "Supplier … not found — push the supplier first", "details": null}
    ]
  }
}
Поле результатуОпис
indexПозиція конверта у вхідному масиві (з 0).
entity_type, external_idЯк у конверті.
acceptedtrue — записано.
entity_idUUID запису в ApexSFA (може бути null для службових конвертів).
operation_performedОперація, яку виконано.
errorТекст помилки для відхиленого конверта.
detailsЛічильники й примітки обробника, напр. {"skipped": "client_not_found"} — рядок проігноровано, бо точки ще немає.

Помилка окремого конверта в режимі partial=true не змінює HTTP-код: відповідь лишається 200, перевіряйте failed_count і results[].accepted.

КодТілоКоли
200{"data": {"accepted_count": 0, "failed_count": 0, "results": []}}items порожній — це не помилка.
401INVALID_CREDENTIALSдив. 3.2.
422VALIDATION_ERRORПакет не пройшов перевірку формату (невідомий entity_type, порожній external_id, невірна operation, понад 5000 елементів — batch too large). Нічого не записано.
422BATCH_ATOMIC_FAILURE + accepted_so_far, failed_at_indexЛише partial=false: конверт failed_at_index не прийнято, увесь пакет відкочено.
429RATE_LIMIT_EXCEEDED + заголовок Retry-After: 60Перевищено ліміт — повторіть той самий пакет після паузи.
5xxINTERNAL_ERRORЗбій сервера — повторіть пакет пізніше.

POST /api/v1/webhooks/1c/inbound

Один конверт. Тіло — сам конверт (+ необовʼязковий tenant_id):

JSON
{"entity_type": "warehouse", "operation": "upsert", "external_id": "8d3b4c5e-6f70-11ee-9a0b-0050569a1b2c",
 "payload": {"name": "Склад Основний"}}

Відповідь 200: {"data": {"accepted": true, "entity_id": "…", "operation_performed": "upsert", "details": null}}. Відповідь 422 — конверт відхилено (запис не знайдено, порушено бізнес-правило): {"error": {"accepted": false, "error": "…"}}.

Порядок первинного завантаження

Під час першого підключення передавайте дані в такому порядку:

  1. Філія, склади, постачальники (+ їхні точки), типи цін.
  2. Групи номенклатури, виробники, номенклатура.
  3. Контрагенти, торгові, точки, привʼязки точок до торгових, обладнання, ціни.
  4. Залишки.
  5. Продажі, борги, рухи товару, каса.
  6. Рейси.

Деталі за кожною сутністю — у 3a.


3.4. ApexSFA → облікова система: pull і ack

Документи, які мають потрапити в облікову систему, ApexSFA кладе в чергу тенанта. Облікова система за розкладом (рекомендовано кожні 5–15 хв):

  1. викликає outbox/pull і отримує пачку елементів;
  2. створює або оновлює документи в себе;
  3. викликає outbox/ack з результатом по кожному елементу;
  4. якщо has_more = true — повторює з кроку 1.

POST /api/v1/webhooks/1c/outbox/pull

ПолеТипОбовʼязковеОпис
tenant_idUUIDніЯкщо немає заголовка X-Tenant-Id.
batch_sizeint, 1..100ні, за замовч. 50Скільки елементів видати.
entity_typesstring[], ≤20ніЗабрати лише ці типи, напр. ["order", "order_payment"]. Без поля — усі типи.

Відповідь 200 (повний приклад — examples/exchange-outbox-pull-response.json):

JSON
{
  "data": {
    "items": [
      {
        "queue_id": "0192f3a1-7c2e-7b10-9a4d-5e6f7a8b9c01",
        "entity_type": "order",
        "entity_id": "0192f3a0-1111-7aaa-8bbb-0c0d0e0f1a2b",
        "operation": "create",
        "attempt": 1,
        "payload": {"order_number": "ORD-20261027-0042", "...": "..."}
      }
    ],
    "has_more": false
  }
}
ПолеОпис
queue_idІдентифікатор елемента черги — його повертаєте в ack.
entity_typeТип документа: order, logistics_order, order_payment, arrival, stock_document, equipment_request, delivery_trip.
entity_idUUID документа в ApexSFA. Для одного документа може бути кілька елементів черги (напр. create, а потім cancel).
operationcreate | update | cancel (див. нижче).
attemptНомер видачі цього елемента (1, 2, …).
payloadЗнімок документа — див. 3.5.
has_moretrue — у черзі лишились елементи, заберіть наступну пачку.

Порядок видачі: за пріоритетом, потім від старіших до новіших.

Повторна видача. Виданий елемент закріплюється за вашою обліковою системою до ack. Якщо ack не прийшов протягом 5 хвилин, елемент знову потрапляє у видачу (attempt зростає).

Увага. Ідемпотентність на вашому боці обовʼязкова. Той самий queue_id (і той самий документ) може прийти повторно — якщо ack загубився, запізнився понад 5 хв або ви отримали відповідь pull, але впали до ack. Шукайте вже створений документ за ключем з payload (order_number, payment_number, arrival_id, request_id, trip_number/external_id) і не створюйте дубль; у ack відповідайте ok з тим самим external_id. Якщо ви вже відповідали на елемент помилкою — на повтор відповідайте тією самою помилкою.

operation:

ЗначенняЩо зробити
createСтворити документ (або знайти вже створений — повторна видача).
updateОновити раніше створений документ (рейс, логістична заявка зі зміненою датою, прихід або документ складу — подія завершення).
cancelДокумент скасовано в ApexSFA. Рекомендована поведінка: непроведений документ помітити на видалення; якщо документ уже проведено й скасувати автоматично не можна — відповісти ack з status=error, permanent=true і поясненням, щоб оператор розібрався вручну. Повторна відміна вже скасованого — ok.

Примітка. Якщо pull повернув 200, а ви не змогли обробити відповідь (обрив звʼязку) — нічого робити не треба: елементи повернуться у видачу через 5 хв.

Помилки: 401 — див. 3.2; 429 — зробіть паузу 5–60 с. Тримайте batch_size у межах 1..100, а results в ack — 1..200.

POST /api/v1/webhooks/1c/outbox/ack

Приклад тіла — examples/exchange-outbox-ack-request.json.

ПолеТипОбовʼязковеОпис
tenant_idUUIDніЯкщо немає заголовка X-Tenant-Id.
resultsarray, 1..200такРезультати по елементах.
results[].queue_idUUIDтакЗ pull.
results[].statusok | errorтакРезультат обробки.
results[].external_idstring, ≤200ніІдентифікатор створеного документа в обліковій системі (GUID). Зберігається при першому успішному ack.
results[].error_messagestring, ≤2000ніТекст помилки — його бачить оператор на порталі.
results[].permanentbool, за замовч. falseніtrue — остаточна помилка (дані невірні, автоматичний повтор не допоможе).
JSON
{
  "results": [
    {"queue_id": "0192f3a1-7c2e-7b10-9a4d-5e6f7a8b9c01", "status": "ok", "external_id": "d18f9a01-b2c3-11ee-9a0b-0050569a1b2c"},
    {"queue_id": "0192f3a1-7c2e-7b10-9a4d-5e6f7a8b9c02", "status": "error", "error_message": "Не знайдено точку", "permanent": false}
  ]
}

Відповідь 200: {"data": {"acked": 2, "synced": 1, "retrying": 1, "failed": 0, "not_found": 0}}

ЛічильникОпис
ackedСкільки результатів застосовано.
syncedСкільки елементів позначено доставленими (включно з повторними ok).
retryingПомилки, що будуть повторені за розкладом.
failedОстаточні помилки.
not_foundqueue_id не знайдено в черзі цього тенанта.

Відповідь завжди 200, навіть якщо частина результатів — помилки; дивіться лічильники.

Що відбувається після ack:

  • ok → елемент «доставлено»; документ у ApexSFA отримує позначку синхронізації й ваш external_id. Для заявки з кабінету покупця (B2B) перший ok — це підтвердження: покупець отримує повідомлення «Підтверджено».
  • error без permanent → повтор за розкладом (60 с, 5 хв, 30 хв, 2 год, далі 12 год); після вичерпання спроб (6) — стан «помилка», далі елементом займається оператор на порталі.
  • error з permanent=true → стан «помилка», оператор бачить ваш текст.
  • Повторний ack для вже доставленого елемента — безпечний.

3.5. Що лежить у черзі: payload за типами

Загальні правила:

  • Суми, кількості, ціни, ставки ПДВ — рядки з десятковою крапкою ("1250.00"). Виняток — у delivery_trip вага, сума й відстань — числа.
  • *_code_1c, *_external_id, external_id_1c — ідентифікатори (GUID) обʼєктів облікової системи, які раніше прийшли в ApexSFA конвертами. Якщо обʼєкт заведено лише в ApexSFA — null.
  • Payload — знімок на момент постановки в чергу.
entity_typeКоли потрапляє в чергуoperationКлюч для пошуку дубля
orderзаявку торгового погоджено; повторна відправка з порталу; скасування погодженої заявкиcreate, cancelorder_number
logistics_orderлогістичну заявку погоджено; зміна дати документа після заборуcreate, update, cancelorder_number
order_paymentоплату прийнято; оплату скасованоcreate, cancelpayment_number
arrivalприхід товару завершеноupdatearrival_id
stock_documentінвентаризацію завершеноupdatedocument_id / external_id
equipment_requestзаявку на встановлення або зняття обладнання виконаноcreaterequest_id
delivery_tripрейс передано в облік із порталу; зміна складу рейсу; скасуванняcreate, update, cancelexternal_id, далі trip_number

Заявки, що прийшли з облікової системи, назад у чергу не потрапляють.

order — заявка торгового (продаж або повернення)

ПолеТипОпис
order_idUUIDІдентифікатор заявки в ApexSFA.
order_numberstringНомер заявки (напр. ORD-20261027-0042). Ключ ідемпотентності.
order_typestringsale — продаж, return — повернення від покупця.
return_basisobject|nullДля повернення: документ-підстава — {debt_document_id, external_id_1c, doc_number, doc_date}.
payment_formstring|nullВид оплати: cash | non_cash | null.
commentstring|nullКоментар торгового.
order_datedateДата заявки, YYYY-MM-DD.
requested_delivery_datedate|nullБажана дата доставки.
client_idUUIDТочка в ApexSFA.
client_code_1cstring|nullІдентифікатор точки в обліковій системі.
agent_idUUIDТорговий — автор заявки.
agentobject|null{name, code_1c} — ПІБ і ідентифікатор торгового в обліковій системі.
price_listobject|nullПрайс заявки: {id, name, code}.
statusstringСтатус заявки на момент постановки в чергу (approved; для cancel — cancelled).
subtotal, discount_amount, vat_amount, total_amountdecimal-stringСуми заявки.
currencystringВалюта, напр. UAH.
warehouseobject|nullСклад відвантаження: {id, name, code_1c}.
manufacturerobject|nullВиробник першого рядка: {id, name, code_1c}.
manufacturersarrayУсі виробники рядків (без повторів).
multi_manufacturerbooltrue, якщо в заявці кілька виробників.
lines[]arrayРядки (див. нижче).

Рядок lines[]:

ПолеТипОпис
line_numberintНомер рядка.
product_idUUID|nullТовар у ApexSFA.
logistics_product_idUUID|nullПозиція каталогу логістики (для логістичних заявок).
product_code_1cstring|nullІдентифікатор номенклатури в обліковій системі.
product_skustring|nullАртикул.
product_namestringНазва.
quantitydecimal-stringКількість (в одиницях).
pack_sizeintКількість в упаковці (1, якщо не задано).
packagesdecimal-string|nullКількість упаковок (для позицій каталогу логістики).
unit_pricedecimal-stringЦіна.
vat_ratedecimal-stringСтавка ПДВ, %.
line_totaldecimal-stringСума рядка.
is_promoboolАкційний рядок.
promo_namestring|nullНазва акції.
manufacturer_name, manufacturer_code_1cstring|nullВиробник рядка.
price_listobject|nullПрайс, з якого взято ціну рядка: {id, name, code}.

Для operation=cancel payload має той самий формат (повний знімок заявки на момент скасування).

logistics_order — логістична заявка

Формат як у order (без manufacturers/multi_manufacturer), плюс:

ПолеТипОпис
logisticsboolЗавжди true.
manufacturerobject|nullВиробник — власник вантажу: {id, name, code_1c}.
source_documentobjectДля заявок із файлів заказів: {kind: "network_order_file", file, template, doc_number, doc_date, po_number, po_date, counterparty_name, counterparty_egrpou, delivery_point_code, contract}.

Логістичні заявки зручно забирати окремим викликом pull з entity_types: ["logistics_order"].

order_payment — оплата (прибутковий касовий ордер)

ПолеТипОпис
payment_idUUIDІдентифікатор оплати.
payment_numberstringНомер оплати. Ключ ідемпотентності.
client_idUUIDТочка.
client_code_1cstring|nullІдентифікатор точки в обліковій системі.
agent_idUUIDХто прийняв оплату.
amountdecimal-stringСума.
currencystringВалюта.
methodstringcash | bank_transfer | card | check | other.
collected_atdatetime|nullМомент прийому оплати, ISO 8601, UTC.
referencestring|nullДовідковий номер (чек, платіжка).
statusstringcompleted, для cancel — cancelled.
allocations[]arrayРозподіл на заявки ApexSFA: {order_id, order_date, amount}.
debt_documents[]arrayРозподіл на документи боргу облікової системи: {external_id_1c, doc_number, amount}.

arrival — прихід товару

ПолеТипОпис
eventstringarrival.completed.
arrival_idUUIDІдентифікатор приходу. Ключ ідемпотентності.
arrival_datedate|nullДата приходу.
branch_external_idstring|nullІдентифікатор філії в обліковій системі.
warehouse_external_idstring|nullСклад.
supplier_external_id, supplier_point_external_idstring|nullПостачальник і його точка.
price_type_external_idstring|nullТип цін.
completed_atdatetime|nullМомент завершення (UTC).
lines[]array{line_number, product_id, product_code, product_article, product_name, barcode, quantity, price, batch_id, manufacture_date}.

stock_document — результат інвентаризації

ПолеТипОпис
eventstringstock_document.completed.
document_idUUIDДокумент у ApexSFA.
external_idstring|nullІдентифікатор вихідного документа облікової системи (якщо інвентаризацію передано з обліку).
doc_number, doc_typestring|nullНомер і вид документа.
kindstringdetailed — до партій і дат; simple — поштучно по рядках.
branch_external_id, warehouse_external_idstring|nullФілія й склад.
inventory_datedate|nullДата інвентаризації.
completed_atdatetime|nullМомент завершення (UTC).
sourcestring|nullДжерело документа.
unmatched_scansarrayВідскановані коди, яких не знайдено.
lines[]array{line_number, product_code, product_article, product_name, barcode, batch_id, batch_document, batch_date, quantity, price, fact_quantity, transfer_quantity, write_off_quantity, transfer_date}.

equipment_request — заявка на встановлення або зняття обладнання

ПолеТипОпис
request_idUUIDІдентифікатор заявки. Ключ ідемпотентності.
kindstringinstall | remove.
client_code_1cstring|nullТочка.
type_external_idstring|nullТип обладнання.
type_namestring|nullНазва типу.
unit_external_idstring|nullКонкретна одиниця обладнання.
quantitystringЗавжди "1".
commentstring|nullКоментар.
requested_at, approved_atdatetime|nullСтворення й погодження (UTC).

delivery_trip — рейс

Потрапляє в чергу, якщо для тенанта увімкнено передачу рейсів в облік і диспетчер натиснув у рейсі «Передати в облік».

ПолеТипОпис
trip_idUUIDРейс у ApexSFA.
trip_numberstring, ≤15Номер рейсу для облікової системи. Ключ ідемпотентності в межах філії (разом з external_id).
portal_numberstringНомер рейсу на порталі.
trip_date, shipping_datedateДата рейсу / відвантаження.
branch_codestring|nullІдентифікатор філії в обліковій системі.
warehouse_codestring|nullСклад.
vehicle_external_idstring|nullАвтомобіль.
driver_external_id, forwarder_external_idstring|nullВодій, експедитор.
commentstringПримітка.
distance_kmnumber|nullПлановий пробіг.
weight, amountnumberЗагальні вага й сума.
external_idstringЛише якщо рейс уже має ваш GUID з попереднього ack.
points[]arrayЗупинки: {position, kind, document_external_id, doc_number, order_number, client_external_id, weight, amount}; kind = sale (документ продажу з обліку) або order (заявка ApexSFA).

cancel: {trip_id, trip_number, external_id}. Якщо рейс змінили після забору, зміна приходить окремим елементом update.


3.6. Повтори й журнал

  • Облікова система → ApexSFA: сервер не повторює за вас. Відхилений конверт (accepted=false) виправте й надішліть ще раз — повтор з тим самим external_id безпечний. На 429 — повторіть пакет після Retry-After; на 5xx або обрив — повторіть пакет цілком.
  • ApexSFA → облікова система: див. поведінку після ack вище.
  • Журнал обміну видно на порталі (розділ «Журнал дій», фільтр «Обмін з обліковою системою»): скільки прийнято й відхилено за типами, номери забраних документів, тексти помилок.
  • Якщо черга не порожня, а успішного обміну не було понад 30 хв, адміністратору приходить лист «Обмін з обліковою системою зупинився».
  • На порталі можна повторно поставити заявку в чергу («Повторно в облікову систему») і передати рейс в облік.

За потреби можливий режим, коли ApexSFA сам надсилає документи на HTTPS-адресу вашої системи (push) замість pull, — узгоджується під час підключення.

3.7. Ліміти

ЩоЗначення
Запитів на /api/v1/webhooks/1c/*600 за 60 с на пару «тенант + IP»
Конвертів у пакеті inbound/batchдо 5000 (рекомендовано 1000)
external_id конвертадо 50 символів
outbox/pull → batch_size1..100 (за замовч. 50)
outbox/pull → entity_typesдо 20 типів
outbox/ack → results1..200
ack.external_id / ack.error_message≤200 / ≤2000 символів
Розбіжність часу підпису±300 с
Повторна видача без ackчерез 5 хв
Спроб доставки елемента6
Перекриття ключів при ротації24 год

3.8. Дати й часові пояси

У черзі (ApexSFA → облік):

  • Дати без часу (order_date, requested_delivery_date, arrival_date, trip_date, inventory_date) — YYYY-MM-DD, календарна дата.
  • Моменти часу (collected_at, completed_at, requested_at, approved_at) — ISO 8601 у UTC (2026-10-27T09:41:12.512000+00:00). Переводьте в локальний час на своєму боці.

У конвертах (облік → ApexSFA):

  • Дати без часу — YYYY-MM-DD.
  • Дата й час — завжди з явним зсувом: 2026-10-27T12:00:00+03:00 або …Z.
  • Періоди date_from/date_to (у goods_period, cash_period) — від 00:00 першого до 24:00 останнього дня за київським часом.

3.9. Приклади

Надіслати пакет довідників

BASE="https://api.apexsfa.com/api/v1"
TENANT="3f2a9c1e-5b7d-4e2a-9c1f-0a1b2c3d4e5f"
SECRET="<ключ обміну>"

BODY='{"partial":true,"items":[{"entity_type":"warehouse","operation":"upsert","external_id":"8d3b4c5e-6f70-11ee-9a0b-0050569a1b2c","payload":{"name":"Склад Основний"}}]}'
TS=$(date +%s)
SIG=$(printf '%s' "$TS.$BODY.$SECRET" | sha256sum | cut -d' ' -f1)
curl -sS -X POST "$BASE/webhooks/1c/inbound/batch" \
  -H "Content-Type: application/json" \
  -H "X-Tenant-Id: $TENANT" \
  -H "X-Signature: t=$TS,v2=$SIG" \
  --data-binary "$BODY"

Забрати заявки й підтвердити

BODY="{\"batch_size\":100,\"entity_types\":[\"order\",\"order_payment\"],\"request_nonce\":\"$(uuidgen)\"}"
TS=$(date +%s)
SIG=$(printf '%s' "$TS.$BODY.$SECRET" | sha256sum | cut -d' ' -f1)
curl -sS -X POST "$BASE/webhooks/1c/outbox/pull" \
  -H "Content-Type: application/json" -H "X-Tenant-Id: $TENANT" \
  -H "X-Signature: t=$TS,v2=$SIG" --data-binary "$BODY"

BODY='{"results":[{"queue_id":"0192f3a1-7c2e-7b10-9a4d-5e6f7a8b9c01","status":"ok","external_id":"d18f9a01-b2c3-11ee-9a0b-0050569a1b2c"}]}'
TS=$(date +%s)
SIG=$(printf '%s' "$TS.$BODY.$SECRET" | sha256sum | cut -d' ' -f1)
curl -sS -X POST "$BASE/webhooks/1c/outbox/ack" \
  -H "Content-Type: application/json" -H "X-Tenant-Id: $TENANT" \
  -H "X-Signature: t=$TS,v2=$SIG" --data-binary "$BODY"

Використовуйте --data-binary (а не -d), щоб curl не змінив тіло після підпису. На macOS замість sha256sum — shasum -a 256. Повний мінімальний клієнт на Python — examples/exchange_client.py.