Инструкция по партнёрскому API

Каталог с вашими ценами и остатками и, если разрешено, заказы. Полное описание полей и ответов — в файле спецификации openapi.yaml (OpenAPI 3.0, открывается в Swagger Editor, Postman, Insomnia), он приложен к этой инструкции.

1. Быстрый старт

  1. Откройте мини-приложение бота → Профиль → API и создайте токен: имя, срок действия, доступ («только каталог» или «каталог и заказы») и при необходимости канал (склад + валюта, как направления в боте). Раздел виден, только если менеджер включил вам API.
  2. Общий токен — все каналы, которые разрешил менеджер. Узкий — один выбранный канал: витрина и заказы только по этому складу и валюте.
  3. Токен показывается один раз. Сохраните его в секретном хранилище; в репозиторий и клиентский код его класть нельзя. При утечке отзовите токен там же и выпустите новый.
  4. Передавайте токен в каждом запросе: Authorization: Bearer fcp_….
export FC_API="https://api.unimtrn.com/api/partner"
curl -H "Authorization: Bearer $FC_TOKEN" "$FC_API/schedule"

Базовый адрес API: https://api.unimtrn.com/api/partner. API серверное: браузерные запросы (CORS) не поддерживаются, вызывайте его с вашего сервера.

2. Общие правила

  • Цена — как у вас в боте (тип цены, наценка, валюта из карточки клиента). Цену вы не передаёте никогда: её определяет сервер, в том числе по шкале для количества.
  • Склады и валюта — из каналов токена (карточка клиента ∩ политика менеджера ∩ выбор при выпуске). Если склад один, параметр warehouse можно не указывать. Узкий токен с одной валютой отдаёт цены только в ней.
  • Часы работы. У бота есть расписание: он принимает заказы только в рабочие часы. Часы на сегодня можно узнать запросом GET /schedule (он отвечает всегда, в том числе когда бот закрыт); менеджер тоже сообщит их.
    • Изменения заказа (добавить позицию, изменить количество, убрать строку, отменить заказ) вне рабочих часов не принимаются: ответ 423 bot_closed. Это действует всегда.
    • Каталог (/catalog, /brands, /categories) вне рабочих часов закрыт только в том случае, если менеджер включил вам соблюдение расписания; тогда он тоже отвечает 423 bot_closed. Если не включил, каталог доступен круглосуточно.
    • 423 bot_closed — не ошибка запроса. Повторите его в рабочее время; при повторе используйте тот же Idempotency-Key: отказ по расписанию не запоминается, и действие выполнится заново.
    • Если время работы бота на сегодня известно, оно приходит прямо в ответе, в поле error.details: время открытия и закрытия и часовой пояс, в котором оно указано. Если известно только, что бот сейчас не принимает запросы, поля details в ответе нет. Пример:
      HTTP 423
      {
        "error": {
          "code": "bot_closed",
          "message": "Бот закрыт. Сегодня работает с 10:00 до 18:00 (Asia/Dubai)",
          "details": { "opensAt": "10:00", "closesAt": "18:00", "timezone": "Asia/Dubai" }
        }
      }
  • Лимиты запросов задаёт менеджер. При превышении — 429 rate_limited: подождите и повторите. Много неверных токенов подряд с одного IP тоже ведут к 429.
  • Версии в пути нет. Несовместимые изменения оформляются новыми полями и эндпоинтами: игнорируйте неизвестные поля в ответах.

3. Формат ошибки

HTTP 403
{ "error": { "code": "scope_missing", "message": "…" } }

Ориентируйтесь на code, а не на текст message. Основные группы:

HTTPКодыЧто делать
400bad_request, idempotency_key_required, idempotency_key_invalidИсправить запрос
401token_missing, token_invalid, token_revoked, token_expiredПроверить или перевыпустить токен
403api_disabled, client_not_allowed, scope_missing, orders_disabled, forbidden, ip_not_allowedДействие недоступно: обратитесь к менеджеру
404not_foundНет такой позиции или заказа
409request_in_progress, order_closed, line_changed, last_line_use_cancelСм. раздел «Заказы»
422order_rejected, onec_error, qty_limit_exceeded, qty_increase_unavailable, serial_line_qty_fixed, idempotency_key_reusedЗапрос понятен, но не принят; причина в message
423bot_closedБот закрыт по расписанию; повторить в рабочее время
429rate_limitedЗамедлиться и повторить
503order_busy, onec_unavailableВременный сбой; повторить с тем же Idempotency-Key

4. Каталог

ЗапросЧто отдаёт
GET /scheduleРаботает ли бот сейчас и часы работы на сегодня
GET /brandsБренды в наличии
GET /categories?brand=…Категории бренда
GET /catalogПозиции с ценами. Фильтры: brand, category, warehouse; страницы: limit (до 1000, по умолчанию 200) и cursor
GET /catalog/{itemCode}Одна позиция, в том числе шкала цен по количеству (tiers)

Данные витрины обновляются раз в 30–60 секунд, чаще опрашивать смысла нет. Во всех примерах справа базовый адрес уже подставлен; токен передавайте в заголовке Authorization.

Фильтры и пагинация — в Query params у эндпоинта «Список позиций». Названия с пробелами передавайте через --data-urlencode.

GET https://api.unimtrn.com/api/partner/schedule

Часы работы на сегодня

Запрос отвечает всегда, в том числе когда бот закрыт. Нужен только действующий токен.

ПолеЗначение
opentrue, если бот сейчас принимает запросы
timezoneЧасовой пояс, в котором указаны часы работы
today.opensAt, today.closesAtВремя открытия и закрытия сегодня в формате ЧЧ:ММ
todaynull, если сегодняшних часов нет (выходной) или они не заданы. Тогда ориентируйтесь на open.

Часы на другие дни этим запросом не отдаются.

GET https://api.unimtrn.com/api/partner/brands

Бренды

Список брендов, доступных вам на витрине.

GET https://api.unimtrn.com/api/partner/categories

Категории бренда

Без brand — 400 bad_request.

Query params

ПараметрОбязательныйТипОписание
brandдаstringБренд из списка брендов. Регистр не важен
GET https://api.unimtrn.com/api/partner/catalog

Список позиций

В списке у позиции одна цена; шкала — только в карточке. nextCursor не null — есть следующая страница. Курсор непрозрачный.

Query params

ПараметрОбязательныйТипОписание
brandнетstringФильтр по бренду (одно значение, регистр не важен)
categoryнетstringФильтр по категории (одно значение). Несколько категорий — отдельные запросы
warehouseнетstringКод склада; по умолчанию все ваши склады
limitнетintegerРазмер страницы: 1–1000, по умолчанию 200
cursorнетstringЗначение nextCursor из предыдущего ответа. Непрозрачная строка
GET https://api.unimtrn.com/api/partner/catalog/{itemCode}

Карточка позиции

Path params

ПараметрОбязательныйТипОписание
itemCodeдаstringКод позиции

items — массив. Шкала tiers: «начиная с qty штук цена за штуку — price». Ступени выше доступного остатка скрыты. У уценённых позиций шкалы нет. Если позиции нет или она вам недоступна — 404 not_found.

GET https://api.unimtrn.com/api/partner/catalog/{itemCode}

Остатки

Остаток приходит в поле available в ответах GET /catalog и GET /catalog/{itemCode}. Отдельного запроса для остатков нет.

Показ остатков включает менеджер. Если он не включён, поля available в ответах нет, а каталог и заказы работают как обычно.

Остатки меняются с каждым заказом. При добавлении в заказ сервер проверяет количество заново: если остатка не хватило, в ответе придут addedQty и capped: true.

Поля позиции каталога

ПолеЧто означает
itemCodeКод позиции (передаётся в заказ)
name, brand, categoryНазвание, бренд, категория
warehouseКод склада
currency, priceВалюта и ваша цена за штуку
availableОстаток. Поля нет, если остатки вам не показываются
tiersШкала цен: только в карточке и только если включена
markdown, serial, commentУценка: признак, серийный номер, комментарий
warehouseNameНазвание склада (только если разрешено)

5. Заказы

Доступны, только если менеджер включил вам заказы, а токен выпущен с доступом «каталог и заказы». Заказ — тот же документ, что корзина в боте и мини-приложении.

ЗапросДействие
POST /orders/linesДобавить позицию в заказ
GET /orders/currentОткрытый заказ в текущей торговой сессии
GET /orders, GET /orders/{id}История и один заказ со статусом
PATCH /orders/{id}/lines/{lineId}Изменить количество строки
DELETE /orders/{id}/lines/{lineId}Убрать строку
POST /orders/{id}/cancelОтменить заказ

Отдельные действия (изменить количество, убрать строку, отменить) менеджер может отключить — тогда API ответит 403 forbidden.

POST https://api.unimtrn.com/api/partner/orders/lines

Добавить позицию

Если остатка или лимита «в одни руки» хватило не на всё: "capped": true и фактическое addedQty. Если не добавить ничего — 422 order_rejected. Та же позиция, серия и цена — количество суммируется; иначе новая строка. Успех всегда 200; created — создан ли новый заказ.

Headers

ЗаголовокОбязательныйОписание
AuthorizationдаBearer <токен>
Idempotency-KeyдаУникальный ключ действия (8–128 символов A-Za-z0-9_.:-). Повтор — тот же ключ
Content-Typeдаapplication/json

Body (JSON)

ПолеОбязательноеТипОписание
itemCodeдаstringКод позиции из каталога
qtyдаintegerКоличество, целое ≥ 1. Для уценки с serial — только 1
warehouseусловноstringКод склада. Обязателен, если складов несколько; иначе берётся единственный
serialусловноstringСерийный номер. Обязателен для уценённой позиции

Цену в теле передавать нельзя — её считает сервер.

GET https://api.unimtrn.com/api/partner/orders/current

Текущий заказ

Если открытого заказа в текущей торговой сессии нет, придёт { "order": null }.

Менять и отменять можно только текущий заказ, пока он не закрыт. Иначе 409 order_closed.

Query params

ПараметрОбязательныйТипОписание
warehouseнетstringКод склада. Если склад один — можно не указывать
GET https://api.unimtrn.com/api/partner/orders

Список заказов

Ответ: { "items": [ … ], "nextCursor": "…" | null }.

Query params

ПараметрОбязательныйТипОписание
limitнетintegerРазмер страницы: 1–100, по умолчанию 20
cursorнетstringЗначение nextCursor из предыдущего ответа

Один заказ

Тот же объект, что order в текущем заказе. Чужой или несуществующий — 404.

Path params

ПараметрОбязательныйТипОписание
order_idдаstringId заказа
GET https://api.unimtrn.com/api/partner/orders/{order_id}

Поля заказа

ПолеЧто означает
id, numberИдентификатор; номер документа (пустая строка, пока не присвоен)
statusСтатус (например, created; у отменённого — «Отменён»)
currency, warehouse, totalВалюта, склад, итог
cancelled, readyToShipОтменён; готов к отгрузке
editable, closedReasonМожно ли менять; если нет — причина
lines[]lineId, itemCode, name, brand, qty, price, sum, currency, serial, addedAt

Идентификатор строки: lineId

lineId — адрес строки в PATCH и DELETE. Его формирует сервер: берите из ответа (lines[].lineId).

  • Непрозрачная строка: не разбирайте и не собирайте сами.
  • Зависит от содержимого строки и меняется вместе с ценой. После PATCH берите новый из ответа.
  • Одинаковые строки не склеиваются; у повторов суффикс ~2 — передавайте целиком.
  • Устаревший lineId → 409 line_changed и актуальный заказ в ответе; повторите с новым ключом.
PATCH https://api.unimtrn.com/api/partner/orders/{order_id}/lines/{line_id}

Изменить количество строки

Цена пересчитывается по шкале, поэтому lineId может смениться. При нехватке остатка — "capped": true. Серийные строки менять нельзя — 422 serial_line_qty_fixed.

Headers

ЗаголовокОбязательныйОписание
AuthorizationдаBearer <токен>
Idempotency-KeyдаУникальный ключ действия (8–128 символов A-Za-z0-9_.:-). Повтор — тот же ключ
Content-Typeдаapplication/json

Path params

ПараметрОбязательныйТипОписание
order_idдаstringId текущего заказа
line_idдаstringId строки из ответа API (не придумывайте сами)

Body (JSON)

ПолеОбязательноеТипОписание
qtyдаintegerНовое количество строки, целое ≥ 1. Чтобы убрать строку — DELETE
DELETE https://api.unimtrn.com/api/partner/orders/{order_id}/lines/{line_id}

Убрать строку

Последнюю строку убрать нельзя (409 last_line_use_cancel) — отмените заказ. Тела запроса нет.

Headers

ЗаголовокОбязательныйОписание
AuthorizationдаBearer <токен>
Idempotency-KeyдаУникальный ключ действия (8–128 символов A-Za-z0-9_.:-). Повтор — тот же ключ

Path params

ПараметрОбязательныйТипОписание
order_idдаstringId текущего заказа
line_idдаstringId строки из ответа API (не придумывайте сами)
POST https://api.unimtrn.com/api/partner/orders/{order_id}/cancel

Отменить заказ

После отмены cancelled: true, editable: false.

Headers

ЗаголовокОбязательныйОписание
AuthorizationдаBearer <токен>
Idempotency-KeyдаУникальный ключ действия (8–128 символов A-Za-z0-9_.:-). Повтор — тот же ключ

Path params

ПараметрОбязательныйТипОписание
order_idдаstringId текущего заказа

Тела запроса нет.

Примеры ошибок при работе с заказом

Устаревший lineId:

HTTP 409
{
  "error": { "code": "line_changed", "message": "Строка заказа изменилась, перечитайте заказ" },
  "order": { "id": "6ae9aefdd12dd7057d4bf68e", "lines": [ "…" ] }
}

Нет остатка:

HTTP 422
{ "error": { "code": "order_rejected", "message": "Товар недоступен в нужном количестве" } }

Действие отключено менеджером:

HTTP 403
{ "error": { "code": "forbidden", "message": "Удаление строк заказа через API отключено для этого клиента" } }

Числа, коды товаров, цены и идентификаторы в примерах условные.

6. Отгрузки

Накладные (расходные) по вашим заказам. Доступны, если менеджер включил вам заказы. Список — только заказы с номером накладной (deliveryNumber).

ЗапросЧто отдаёт
GET /shipmentsСписок отгрузок (страницы: limit до 100, cursor)
GET /shipments/{deliveryNumber}Накладная по номеру: JSON (по умолчанию) или PDF (format=pdf)
GET /orders/{id}/shipmentТа же накладная, но по id заказа

В JSON у позиций есть price/sum (цена клиента из заказа) и serials — серийные номера из накладной. Общая сумма — поле total. Если накладной нет или она недоступна — 404 / 422 shipment_unavailable.

GET https://api.unimtrn.com/api/partner/shipments

Список отгрузок

Только заказы с номером накладной. Листается через nextCursor.

Query params

ПараметрОбязательныйТипОписание
limitнетintegerРазмер страницы: 1–100, по умолчанию 20
cursorнетstringЗначение nextCursor из предыдущего ответа
GET https://api.unimtrn.com/api/partner/shipments/{deliveryNumber}

Накладная по номеру

Path params

ПараметрОбязательныйТипОписание
deliveryNumberдаstringНомер расходной накладной

По умолчанию JSON с позициями и serials.

Query params

ПараметрОбязательныйТипОписание
formatнетstringjson (по умолчанию) или pdf — бинарный файл накладной

Накладная по заказу

Тот же документ по id заказа. Если заказ ещё не отгружен — 404. Query params те же (format).

Path params

ПараметрОбязательныйТипОписание
order_idдаstringId заказа
GET https://api.unimtrn.com/api/partner/orders/{order_id}/shipment

7. Вебхуки статусов

Это исходящие уведомления: мы шлём POST на ваш HTTPS URL, когда статус заказа меняется. Это не эндпоинт Partner API — URL задаётся в политике (менеджер в админке или вы в миниаппе → Профиль → API, если самовыпуск разрешён).

Уведомления идут по всем заказам клиента (бот, миниприложение, API), кроме тестовых и реферальных. Повтор того же статуса по одному заказу не отправляется.

statusКогда
registeredЗаказ создан / зарегистрирован
cancelledЗаказ отменён
shippedПоявился номер накладной (отгрузка)
  • URL только https.
  • Заголовки: Content-Type: application/json, User-Agent: FC-Partner-Webhook/1.0, X-FC-Delivery-Id (uuid доставки).
  • Если задан секрет (8–256 символов) — ещё X-FC-Signature: sha256=<hex>: HMAC-SHA256 от сырого тела запроса.
  • Отвечайте быстро 2xx. При ошибках сеть/5xx/429 мы повторим до 3 раз; 4xx (кроме 429) — без повтора.

Тело уведомления

event всегда order.status. Поле deliveryNumber есть только при shipped.

Проверка подписи (Node.js): посчитайте HMAC-SHA256 от raw body секретом и сравните с заголовком X-FC-Signature (строка вида sha256=…) в constant-time.

const crypto = require('crypto');
function ok(secret, rawBody, header) {
  const want = 'sha256=' + crypto
    .createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(want);
  const b = Buffer.from(String(header || ''));
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

8. Idempotency-Key

Зачем. Если ответ не получен, повтор «вслепую» опасен. Ключ гарантирует: повтор с тем же ключом вернёт ответ первого запроса, а не выполнит действие дважды.

Где нужен. Все изменяющие запросы заказов: POST /orders/lines, PATCH, DELETE, POST …/cancel. Без него — 400 idempotency_key_required. Для GET не нужен.

Кто создаёт. Ваша система, перед запросом (например UUID v4). Новое действие — новый ключ. Сохраните ключ до получения ответа.

KEY=$(uuidgen)
curl -sS -X PATCH "https://api.unimtrn.com/api/partner/orders/$ORDER/lines/$LINE" \
  -H "Authorization: Bearer $FC_TOKEN" \
  -H "Idempotency-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"qty": 5}'
# ответ не получен — повтор с тем же $KEY
# → 200 и заголовок Idempotent-Replay: true
  • Один ключ — одно действие. 8–128 символов: A-Za-z0-9_.:-.
  • Тот же ключ с другим запросом — 422 idempotency_key_reused.
  • Пока первый запрос выполняется — 409 request_in_progress; подождите и повторите.
  • Бизнес-отказы (409, 422) запоминаются. Временные (423, 429, 503) — нет.

9. Частые вопросы

  • 403 api_disabled / orders_disabled. API или заказы не включены вам менеджером.
  • 422 order_rejected. Нет остатка, исчерпана квота или позиция недоступна — причина в message.
  • 422 onec_error. Учётная система не приняла изменение. Обратитесь к менеджеру, если повторяется.
  • Токен перестал работать. Проверьте срок и отзыв в Профиль → API; код ошибки 401 говорит, что именно произошло.