Перейти до основного вмісту

Integration API

Модуль Integration API дозволяє складській програмі, 1С, CRM або вашому скрипту працювати з магазином через HTTPS: читати каталог, виставляти залишки по складах, змінювати ціни, читати замовлення, змінювати їхній статус і оплату, записувати номер накладної (ТТН) і отримувати вебхук, коли замовлення змінилося.

Це закритий API для звʼязку сервер із сервером. Кожен виклик потребує токена з окремими правами, кожен запис підписується й іде з дозволеної IP-адреси, а кожна зміна потрапляє в журнал аудиту.

Модуль встановлюється з Модулі → Магазин модулів. Потрібен білд ядра eCMS9 526 або новіший (для імпорту товарів і статей — 600).

Це не Headless API

Headless API обслуговує вітрину: каталог, кошик, оформлення й вхід покупця. Він може бути відкритим і ніколи не змінює ціни чи залишки. Integration API — навпаки: нічого публічного, а його робота — записувати дані обліку. Якщо каталог читається, а ціну чи кількість змінити не виходить, ви, найімовірніше, звертаєтесь до Headless API, а не до цього модуля.

З чого почати​

Базова адреса — https://ваш-магазин/integration_api/v1.

  • GET /ping — перевіряє токен і повертає його назву, права та час сервера. Без чинного токена кожна адреса відповідає 401 UNAUTHORIZED; це нормальний стан магазину, якому ще не видали токен.
  • GET /openapi.yaml — повний контракт OpenAPI-файлом, який розуміють Postman, Insomnia й генератори коду. Те саме посилання — кнопка OpenAPI на екрані модуля.

Отримати токен​

Відкрийте Налаштування → API інтеграції (екран модуля називається «API інтеграції»). У блоці Токени:

  1. Дайте токену назву, наприклад «Облік — синхронізація залишків». Один токен на одну інтеграцію: так видно її запити, її можна вимкнути або замінити, не чіпаючи інші.
  2. Позначте потрібні права. Давайте мінімум.
  3. Для кожного права на запис вкажіть дозволені IP-адреси: одну адресу або мережу (не ширшу за /16, для IPv6 — не ширшу за /32). Токен із правом на запис без цього списку не створюється.
  4. За бажанням задайте дату, до якої токен діє.

Після натискання Створити токен екран один раз покаже два значення:

  • токен ik_…, який іде в кожному запиті;
  • секрет підпису, яким підписуються записи.

Магазин зберігає лише відбиток токена, а секрет не зберігає взагалі, тому скопіюйте обидва значення одразу й передайте розробнику захищеним каналом. Якщо значення втрачено чи витекло, натисніть Замінити токен: видається нова пара, а стара перестає працювати одразу; назва, права, список IP та історія запитів лишаються.

ПравоДозволяє
catalog:readтовари, варіанти, категорії, бренди
stock:read / stock:writeчитати / змінювати залишки по складах
prices:read / prices:writeчитати / змінювати ціни
orders:readчитати замовлення без персональних даних покупців
orders:writeзмінювати статус, позначку оплати й номер накладної
customers:piiімʼя, телефон і адреса покупця в замовленнях
webhooks:manageпідписуватись на події замовлень
products:writeімпортувати товар чи статтю блогу з іншого магазину

Права, що пишуть (stock:write, prices:write, orders:write, webhooks:manage, products:write), вимагають списку IP. Персональні дані — окреме право свідомо: без нього в замовленні немає імені, телефону й адреси.

Зробити запит​

Для читання потрібен лише токен:

curl -sS https://ваш-магазин/integration_api/v1/stock?sku=RC-1234 \
-H "Authorization: Bearer ik_xxxxxxxx"

Токен приймається лише в заголовку Authorization: Bearer, ніколи в адресі. Звичайний HTTP відхиляється.

Підписуйте кожен запис​

Запис несе ще два заголовки:

X-Timestamp: <unix-секунди>
X-Signature: hex(HMAC-SHA256(секрет, METHOD + "\n" + PATH + "\n" + QUERY + "\n" + X-Timestamp + "\n" + hex(SHA256(тіло))))
  • PATH починається з /, наприклад /integration_api/v1/stock.
  • QUERY — рядок запиту точно в тому вигляді, в якому він іде в мережу, без ?; порожній, якщо його немає.
  • Тіло хешується байт у байт, як відправлено; порожнє тіло — це SHA-256 порожнього рядка.
  • Секрет береться так, як його показано (шістнадцятковий текст, не декодований).
  • Мітка часу має відрізнятися від часу сервера не більше ніж на ±300 секунд, а підпис приймається один раз. Два однакові запити в ту саму секунду дістануть SIGNATURE_REPLAYED.

Повний приклад (пробний прогін зміни залишку):

BASE=https://ваш-магазин/integration_api/v1
TOKEN=ik_xxxxxxxx
SECRET=ваш-секрет-підпису
BODY='{"dry_run":true,"items":[{"sku":"RC-1234","warehouse_id":1,"quantity":12}]}'

TS=$(date +%s)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.* //')
SIG=$(printf 'PUT\n/integration_api/v1/stock\n\n%s\n%s' "$TS" "$BODY_HASH" \
| openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl -sS -X PUT "$BASE/stock" \
-H "Authorization: Bearer $TOKEN" \
-H "X-Timestamp: $TS" -H "X-Signature: $SIG" \
-H "Idempotency-Key: stock-$TS" \
-H "Content-Type: application/json" \
--data-binary "$BODY"

Спершу перевірте підпис через POST /ping: він має бути підписаний, а його помилки не рахуються до блокування IP. У модулі є й невеликий PHP-клієнт із таким самим підписом — Resources/examples/client.php.

Що вміє API​

НапрямЕндпоінти
Довідники/warehouses, /currencies, /price-types, /order-statuses, /payment-methods, /delivery-methods
Каталог/products (з варіантами, посторінково), /products/{id}, /variants (пошук за id, SKU, штрихкодом чи зовнішнім id), /categories, /brands
ЗалишкиGET /stock; PUT /stock виставляє абсолютні кількості по складах; POST /stock/adjust змінює їх на дельту
ЦіниGET /prices; PUT /prices виставляє базову ціну, стару ціну або ціну типу цін
ЗамовленняGET /orders, GET /orders/{id}; PATCH /orders/{id}/status, /paid, /ttn
ЗміниGET /changes — що змінилося після вашого курсора
ВебхукиGET, POST /webhooks, DELETE /webhooks/{id}

Зміна статусу замовлення робить те саме, що зміна статусу в адмінці, разом із побічними діями, налаштованими у вашому магазині: синхронізація з CRM, фіскальний чек, SMS чи Telegram-сповіщення, дії над залишками, привʼязані до статусу. Спершу пробуйте з dry_run.

Залишки й ціни​

  • Варіант задається рівно одним ідентифікатором: variant_id, sku, barcode або external_id. Значення, що збігається з кількома варіантами, відхиляється з AMBIGUOUS; для таких використовуйте variant_id.
  • В одному запиті до 1000 рядків. Коректні рядки записуються однією транзакцією. З all_or_nothing: true один поганий рядок відхиляє весь запит.
  • price — у власній валюті варіанта, так, як його вводять у формі товару. Ціна в основній валюті перераховується з неї.
  • Запис залишку чи ціни проходить тим самим шляхом, що й форма в адмінці: цінові фіди, пошук і кеш сторінок бачать цю зміну.
  • Магазин, який не використовує модуль «Склади», має один склад із id 1 (його показує GET /warehouses). Передавайте "warehouse_id": 1; кількість записується прямо в залишок варіанта. Доступно з версії модуля 1.3.1.

Замовлення й номери накладних​

GET /orders ділиться на сторінки через cursor; для інкрементальної синхронізації використовуйте updated_since. PATCH /orders/{id}/ttn записує номер накладної. Для Нової пошти номер потрапляє в модуль Нової пошти (14 цифр, далі його статус відстежує сам модуль); для інших доставок — у ручне поле відстеження ядра. Поточне значення повертається як tracking у GET /orders/{id}.

Позначка paid запускає все, що запускає позначка оплати в магазині, зокрема фіскальний чек, якщо фіскальний модуль увімкнено. Спершу перевіряйте через dry_run.

Синхронізація через стрічку змін​

GET /changes?cursor=… повертає, що змінилося після курсора, у вигляді id та назв подій (order.status_changed, stock.updated, price.changed, …). Збережіть next_cursor і питайте знову. Рядки зберігаються 30 днів і фільтруються за правами вашого токена. Звузити можна параметром entity=order,stock.

Стрічка бачить зміни, що проходять через події магазину. Залишок, який рухає щось інше (щогодинний імпорт файлу, пряма синхронізація з базою), у ній не зʼявляється. Час від часу проходьте GET /stock повністю як страховку.

Вебхуки​

Підпишіться через POST /webhooks на order.created, order.status_changed, order.paid_changed або order.tracking_changed. Адреса має бути HTTPS на порту 443 чи 8443 і вказувати на публічний хост; до 5 активних на токен. Секрет повертається один раз.

Доставка — це POST із заголовками X-Webhook-Event, X-Webhook-Id, X-Webhook-Timestamp і X-Webhook-Signature = hex(HMAC-SHA256(webhook_secret, timestamp + "." + body)). Відповідайте 2xx протягом 10 секунд; інакше буде повтор через 1 хв, 5 хв, 30 хв, 2 год, 6 год, 12 год і 24 год. Тіло містить лише id та стани, тож деталі замовлення дістаньте окремим запитом, а дублі відсійте за X-Webhook-Id.

Вбудовані запобіжники​

  • dry_run — "dry_run": true у тілі показує, що зміниться, і нічого не записує. Прапорець, який не є справжнім булевим значенням, відхиляється й ніколи не сприймається як бойовий запис.
  • Idempotency-Key — ключ довжиною 8–64 символи на запис: повторний запит повертає збережену відповідь замість повторного застосування (зберігається 24 години).
  • Запобіжник ціни — зміна більше ніж на 50 % і нульова ціна відхиляються з PRICE_GUARD, якщо в рядку немає "force": true.
  • Ліміти запитів — 300 читань і 60 записів на хвилину на токен; понад ліміт відповідь 429 із Retry-After.
  • Блокування IP — повторні невдалі автентифікації з однієї адреси блокують її на 15 хвилин.
  • Аудит — кожен застосований запис фіксується в журналі аудиту адмінки з автором «API: назва токена», IP-адресою та старим і новим значеннями.
  • Журнал запитів — кожен запит видно в блоці Журнал запитів на екрані модуля; за замовчуванням він зберігається 90 днів.

Усе це налаштовується в блоці Безпека й ліміти на екрані модуля: перемикач Лише читання (усі записи відповідають 503, читання працює), вимога HTTPS, ліміти, розмір пакета, відсоток запобіжника ціни, вікно підпису та сповіщення в Telegram про новий токен і блокування IP.

Відповіді й помилки​

Кожна відповідь має однакову обгортку:

{"success": true, "data": {}, "error": null}
{"success": false, "data": null, "error": {"code": "UNAUTHORIZED", "message": "…"}}

Типові коди: UNAUTHORIZED, IP_NOT_ALLOWED, FORBIDDEN (токену бракує права), SIGNATURE_MISSING, SIGNATURE_INVALID, SIGNATURE_EXPIRED, SIGNATURE_REPLAYED, RATE_LIMITED, TOO_MANY_FAILURES, READ_ONLY, VALIDATION_FAILED, IDEMPOTENCY_CONFLICT, INVALID_FIELD, AMBIGUOUS, PRICE_GUARD. Повний перелік — в openapi.yaml. Час у відповідях — UTC ISO-8601 із Z; в updated_since передавайте Z або зсув.

Часті запитання​

Кожен виклик відповідає 401. Токена ще немає або він не там. Передавайте його як Authorization: Bearer ik_…; з адреси він не читається.

Запис відповідає 401 SIGNATURE_INVALID. Звірте те, що підписуєте, з правилами вище: шлях починається з /, запит підписується як відправлено, тіло хешується як відправлено, секрет — шістнадцятковий текст. Перевірте через POST /ping.

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

Кілька товарів мають однаковий SKU. Такі рядки відповідають AMBIGUOUS. Знайдіть варіанти через GET /variants?sku=… і пишіть за variant_id.

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

Чи можна зробити токен лише для читання? Так. Позначте тільки права …:read; такому токену не потрібні ні список IP, ні підпис.