Инструкция по партнёрскому API
Каталог с вашими ценами и остатками и, если разрешено, заказы. Полное описание полей и ответов — в файле openapi.yaml (OpenAPI 3.0, открывается в Swagger Editor, Postman, Insomnia). Можно сразу попробовать запросы на странице Swagger — она отправляет настоящие запросы с вашим токеном.
1. Быстрый старт
- Откройте мини-приложение бота → Профиль → API и создайте токен: имя, срок действия, права («только каталог» или «каталог и заказы»). Раздел виден, только если менеджер включил вам API.
- Токен показывается один раз. Сохраните его в секретном хранилище; в репозиторий и клиентский код его класть нельзя. При утечке отзовите токен там же и выпустите новый.
- Передавайте токен в каждом запросе:
Authorization: Bearer fcp_….
export FC_API="https://<хост>/api/partner"
curl -H "Authorization: Bearer $FC_TOKEN" "$FC_API/me"
Базовый адрес ($FC_API в примерах) вам сообщит менеджер. API серверное: браузерные запросы (CORS) не поддерживаются, вызывайте его с вашего сервера.
2. Общие правила
- Цена — как у вас в боте (тип цены, наценка, валюта из карточки клиента). Цену вы не передаёте никогда: её определяет сервер, в том числе по шкале для количества.
- Склады берутся из вашей карточки клиента. Если склад один, параметр
warehouseможно не указывать. - Часы работы. Изменения заказов вне часов работы бота отклоняются (423
bot_closed); запросы каталога — если у вас включено соблюдение расписания. - Лимиты запросов задаёт менеджер. При превышении — 429
rate_limited: подождите и повторите. Много неверных токенов подряд с одного IP тоже ведут к 429. - Версии в пути нет. Несовместимые изменения оформляются новыми полями и эндпоинтами: игнорируйте неизвестные поля в ответах.
3. Формат ошибки
HTTP 403
{ "error": { "code": "scope_missing", "message": "…" } }
Ориентируйтесь на code, а не на текст message. Основные группы:
| HTTP | Коды | Что делать |
|---|---|---|
| 400 | bad_request, idempotency_key_required, idempotency_key_invalid | Исправить запрос |
| 401 | token_missing, token_invalid, token_revoked, token_expired | Проверить или перевыпустить токен |
| 403 | api_disabled, client_not_allowed, scope_missing, orders_disabled, forbidden, ip_not_allowed | Нужного права нет: обратитесь к менеджеру |
| 404 | not_found | Нет такой позиции или заказа |
| 409 | request_in_progress, order_closed, line_changed, last_line_use_cancel | См. раздел «Заказы» |
| 422 | order_rejected, onec_error, qty_limit_exceeded, qty_increase_unavailable, serial_line_qty_fixed, idempotency_key_reused | Запрос понятен, но не принят; причина в message |
| 423 | bot_closed | Бот закрыт по расписанию; повторить в рабочее время |
| 429 | rate_limited | Замедлиться и повторить |
| 503 | order_busy, onec_unavailable | Временный сбой; повторить с тем же Idempotency-Key |
4. Каталог (право catalog:read)
| Запрос | Что отдаёт |
|---|---|
GET /me | Ваши права после применения политики: склады, валюта, лимиты, права токена |
GET /brands | Бренды в наличии |
GET /categories?brand=… | Категории бренда |
GET /catalog | Позиции с ценами. Фильтры: brand, category, warehouse; страницы: limit (до 1000, по умолчанию 200) и cursor |
GET /catalog/{itemCode} | Одна позиция, в том числе шкала цен по количеству (tiers) |
Данные витрины обновляются раз в 30–60 секунд, чаще опрашивать смысла нет. Во всех примерах ниже $FC_API и $FC_TOKEN заданы, как в разделе «Быстрый старт».
Параметры GET /catalog
| Параметр | Значение | Пример |
|---|---|---|
brand | Бренд из GET /brands. Одно значение, регистр не важен | Apple |
category | Категория из GET /categories?brand=…. Одно значение, регистр не важен | iPhone |
warehouse | Код склада из GET /me; по умолчанию все ваши склады | 23 |
limit | Размер страницы: от 1 до 1000, по умолчанию 200 | 100 |
cursor | Значение nextCursor из предыдущего ответа | — |
Фильтры сочетаются по «И»: бренд и категория вместе дают позиции этой категории этого бренда. Названия с пробелами и кириллицей передавайте через --data-urlencode (с ключом -G curl подставит их в адрес сам). Если значение не найдено, ответ будет пустым списком "items": [], а не ошибкой.
Профиль и права: GET /me
curl -sS -H "Authorization: Bearer $FC_TOKEN" "$FC_API/me"
{
"client": {
"botKey": "dubai",
"currency": "USD",
"warehouses": [ { "code": "23" } ]
},
"token": {
"prefix": "Ab3dE9xQ",
"name": "my-erp",
"scopes": ["catalog:read", "orders:write"],
"createdAt": "2026-11-03T09:12:41.000Z",
"expiresAt": "2027-11-03T09:12:41.000Z",
"lastUsedAt": "2026-11-03T10:02:07.000Z"
},
"policy": {
"includeTiers": true,
"includeMarkdown": false,
"respectSchedule": true,
"rate": { "perMinute": 60, "perDay": 20000 },
"orders": {
"enabled": true,
"allowModify": true,
"allowDelete": false,
"allowCancel": true,
"maxLineQty": null
}
}
}
Из policy видно, что вам разрешено: показываются ли остатки (см. «Остатки» ниже), есть ли шкала цен, какие действия с заказами доступны.
Бренды: GET /brands
curl -sS -H "Authorization: Bearer $FC_TOKEN" "$FC_API/brands"
{ "brands": ["Apple", "Dyson", "Samsung", "Sony"] }
Категории бренда: GET /categories
curl -sS -G -H "Authorization: Bearer $FC_TOKEN" \
--data-urlencode "brand=Apple" \
"$FC_API/categories"
{ "categories": ["AirPods", "iPad", "iPhone", "MacBook"] }
Параметр brand обязателен, иначе 400 bad_request.
Каталог одного бренда
curl -sS -G -H "Authorization: Bearer $FC_TOKEN" \
--data-urlencode "brand=Apple" \
--data-urlencode "limit=2" \
"$FC_API/catalog"
{
"items": [
{
"itemCode": "24014",
"name": "iPhone 15 128GB Black",
"brand": "Apple",
"category": "iPhone",
"warehouse": "23",
"currency": "USD",
"price": 345,
"available": 40
},
{
"itemCode": "24159",
"name": "Apple AirPods Max 2 (USB-C) Blue 2026 MHWM4",
"brand": "Apple",
"category": "AirPods",
"warehouse": "23",
"currency": "USD",
"price": 100,
"available": 20
}
],
"nextCursor": "eyJrIjoiMjQxNTl8MjN8IiwibyI6Mn0"
}
nextCursor не null — значит есть следующая страница (см. «Постраничная выгрузка» ниже). В списке у позиции одна цена; шкала цен по количеству приходит только в карточке позиции.
Каталог бренда и категории
curl -sS -G -H "Authorization: Bearer $FC_TOKEN" \
--data-urlencode "brand=Apple" \
--data-urlencode "category=iPhone" \
--data-urlencode "limit=100" \
"$FC_API/catalog"
{
"items": [
{
"itemCode": "24014",
"name": "iPhone 15 128GB Black",
"brand": "Apple",
"category": "iPhone",
"warehouse": "23",
"currency": "USD",
"price": 345,
"available": 40
}
],
"nextCursor": null
}
Только один склад из нескольких — добавьте --data-urlencode "warehouse=23".
Несколько категорий
Параметр category принимает одно значение, поэтому для нескольких категорий делайте по запросу на каждую и объединяйте ответы у себя. Пример: позиции Apple из категорий iPhone, iPad и AirPods одним списком (нужен jq):
for CAT in iPhone iPad AirPods; do
curl -sS -G -H "Authorization: Bearer $FC_TOKEN" \
--data-urlencode "brand=Apple" \
--data-urlencode "category=$CAT" \
--data-urlencode "limit=1000" \
"$FC_API/catalog"
done | jq -s '[ .[].items[] ]'
[
{ "itemCode": "24014", "name": "iPhone 15 128GB Black", "category": "iPhone", "price": 345, "available": 40, "…": "…" },
{ "itemCode": "25101", "name": "iPad 10th gen 64GB Silver", "category": "iPad", "price": 289, "available": 15, "…": "…" },
{ "itemCode": "24159", "name": "Apple AirPods Max 2 (USB-C) Blue 2026 MHWM4", "category": "AirPods", "price": 100, "available": 20, "…": "…" }
]
Если в категории больше 1000 позиций, листайте каждую категорию по nextCursor (ниже). Все категории бренда без перебора — запрос без category (каталог бренда).
Постраничная выгрузка всего каталога
Подставляйте nextCursor из ответа в cursor, пока он не станет null:
CURSOR=""
while : ; do
RESP=$(curl -sS -G -H "Authorization: Bearer $FC_TOKEN" \
--data-urlencode "limit=500" \
${CURSOR:+--data-urlencode "cursor=$CURSOR"} \
"$FC_API/catalog")
echo "$RESP" | jq -c '.items[]'
CURSOR=$(echo "$RESP" | jq -r '.nextCursor // empty')
[ -z "$CURSOR" ] && break
done
Курсор — непрозрачная строка: не разбирайте и не меняйте её. Если витрина обновилась между страницами, выгрузка продолжится с ближайшей позиции без ошибки; для строгой сверки повторите выгрузку целиком.
Карточка позиции со шкалой цен: GET /catalog/{itemCode}
curl -sS -H "Authorization: Bearer $FC_TOKEN" "$FC_API/catalog/24014"
{
"items": [
{
"itemCode": "24014",
"name": "iPhone 15 128GB Black",
"brand": "Apple",
"category": "iPhone",
"warehouse": "23",
"currency": "USD",
"price": 345,
"available": 40,
"tiers": [
{ "qty": 1, "price": 345 },
{ "qty": 5, "price": 343 },
{ "qty": 10, "price": 341 }
]
}
]
}
items — массив. Шкала tiers читается так: «начиная с qty штук цена за штуку — price». Ступени выше доступного остатка скрыты. У уценённых позиций шкалы нет. Если позиции нет или она вам недоступна — 404 not_found.
Остатки
Остаток приходит в поле available в ответах GET /catalog и GET /catalog/{itemCode}. Отдельного запроса для остатков нет.
Право на остатки. Показ остатков включает менеджер. Если право не выдано, поля available в ответах нет, а каталог и заказы работают как обычно. Чтобы получать остатки, обратитесь к менеджеру.
Остаток одной позиции:
curl -sS -H "Authorization: Bearer $FC_TOKEN" "$FC_API/catalog/24014" \
| jq '.items[] | {itemCode, warehouse, available}'
{ "itemCode": "24014", "warehouse": "23", "available": 40 }
Остатки по бренду и категории (код, склад, остаток):
curl -sS -G -H "Authorization: Bearer $FC_TOKEN" \
--data-urlencode "brand=Apple" \
--data-urlencode "category=iPhone" \
--data-urlencode "limit=100" \
"$FC_API/catalog" \
| jq '.items[] | {itemCode, warehouse, available}'
{ "itemCode": "24014", "warehouse": "23", "available": 40 }
{ "itemCode": "24020", "warehouse": "23", "available": 12 }
Остатки меняются с каждым заказом. При добавлении в заказ сервер проверяет количество заново: если остатка не хватило, в ответе придут addedQty и capped: true (см. раздел «Заказы»).
Поля позиции каталога
| Поле | Что означает |
|---|---|
itemCode | Код позиции (передаётся в заказ) |
name, brand, category | Название, бренд, категория |
warehouse | Код склада |
currency, price | Валюта и ваша цена за штуку (с наценкой из карточки клиента) |
available | Остаток. Поля нет, если остатки вам не показываются |
tiers | Шкала цен: только в карточке и только если включена |
markdown, serial, comment | Уценка: признак, серийный номер, комментарий (только если уценка вам доступна; такая позиция продаётся по одной штуке) |
warehouseName | Название склада (только если разрешено) |
5. Заказы (право orders:write)
Доступны, только если менеджер включил вам заказы, а токен выпущен с правом «каталог и заказы». Заказ — тот же документ, что корзина в боте и мини-приложении.
| Запрос | Действие | Нужно разрешение |
|---|---|---|
POST /orders/lines | Добавить позицию в заказ | заказы включены |
GET /orders/current | Открытый заказ в текущей торговой сессии | заказы включены |
GET /orders, GET /orders/{id} | История и один заказ со статусом | заказы включены |
PATCH /orders/{id}/lines/{lineId} | Изменить количество строки (больше или меньше) | allowModify |
DELETE /orders/{id}/lines/{lineId} | Убрать строку | allowDelete |
POST /orders/{id}/cancel | Отменить заказ | allowCancel |
Добавить позицию
curl -X POST "$FC_API/orders/lines" \
-H "Authorization: Bearer $FC_TOKEN" \
-H "Idempotency-Key: 3f0c2a52-7b8e-4a43-9d1b-2f4a1c9e6b10" \
-H "Content-Type: application/json" \
-d '{"itemCode":"24014","warehouse":"23","qty":5}'
Ответ 200:
{
"line": {
"lineId": "ln_Qx3v9KpL2mZ1aBcD",
"itemCode": "24014",
"name": "iPhone 15 128GB Black",
"brand": "Apple",
"qty": 5,
"price": 343,
"sum": 1715,
"currency": "USD",
"addedAt": "2026-11-03T10:15:22.000Z"
},
"order": {
"id": "6ae9aefdd12dd7057d4bf68e",
"number": "",
"status": "created",
"currency": "USD",
"warehouse": "23",
"total": 1715,
"cancelled": false,
"readyToShip": false,
"editable": true,
"createdAt": "2026-11-03T10:15:22.000Z",
"updatedAt": "2026-11-03T10:15:22.000Z",
"lines": [ { "lineId": "ln_Qx3v9KpL2mZ1aBcD", "itemCode": "24014", "qty": 5, "price": 343, "sum": 1715, "…": "…" } ]
},
"created": true,
"requestedQty": 5,
"addedQty": 5,
"capped": false
}
Здесь цена 343 — это цена ступени шкалы для 5 штук (как в примере карточки выше). Если остатка или лимита «в одни руки» хватило не на всё, придёт, например, "requestedQty": 5, "addedQty": 3, "capped": true: в заказ добавлено то, что доступно. Если не добавить ничего — 422 order_rejected.
- Цену не передаёте. Цена порции определяется по шкале для добавляемого количества.
- Если в заказе уже есть строка с тем же товаром, серией и ценой, количество суммируется; если цена другая (например, сменилась ступень шкалы), добавляется отдельная строка.
- Остатка может не хватить: тогда в ответе
cappedи фактически добавленное количествоaddedQty(запрошенное —requestedQty). Успешный ответ — всегда 200;createdпоказывает, создан ли новый заказ. - Для уценённых позиций обязателен
serial, количество — 1.
Править и отменять
- Менять и отменять можно только текущий заказ, пока он не закрыт (готов к отгрузке, оформлена накладная, заблокирован или отменён). Иначе 409
order_closedс причинойclosedReasonи актуальным заказом в ответе. PATCHзадаёт новое количество строки ({"qty": 3}), больше или меньше текущего. Цена строки пересчитывается по шкале для нового количества, поэтомуlineIdможет смениться: берите его из ответа.- При увеличении разница добавляется с теми же проверками, что у
POST /orders/lines(остаток, лимит «в одни руки», квота, доступность позиции). Если можно добавить не всё, количество увеличивается на доступное, а в ответе приходит"capped": true; проверьте итоговоеqtyстроки. Если добавить нельзя ничего — 422order_rejected, заказ не меняется. - Строки с серийным номером (уценённый товар) менять нельзя — 422
serial_line_qty_fixed. Чтобы убрать строку, используйтеDELETE. lineIdберите из актуального заказа. Он зависит от содержимого строки и меняется вместе с ценой; если строка изменилась или удалена — 409line_changedи актуальный заказ в ответе.- Последнюю строку убрать нельзя (409
last_line_use_cancel) — отмените заказ.
Текущий заказ: GET /orders/current
curl -sS -H "Authorization: Bearer $FC_TOKEN" "$FC_API/orders/current"
{
"order": {
"id": "6ae9aefdd12dd7057d4bf68e",
"number": "",
"status": "created",
"currency": "USD",
"warehouse": "23",
"total": 502,
"cancelled": false,
"readyToShip": false,
"editable": true,
"createdAt": "2026-11-03T09:01:10.000Z",
"updatedAt": "2026-11-03T09:14:55.000Z",
"lines": [
{ "lineId": "ln_g_I2gvqZXVc9Y7DK", "itemCode": "24159", "name": "Apple AirPods Max 2 (USB-C) Blue 2026 MHWM4",
"brand": "Apple", "qty": 3, "price": 100, "sum": 300, "currency": "USD" },
{ "lineId": "ln_eDECuLIGBiTGCs9x", "itemCode": "23972", "name": "Apple AirPods Max 2 (USB-C) Midnight 2026 MHWK4",
"brand": "Apple", "qty": 2, "price": 101, "sum": 202, "currency": "USD" }
]
}
}
Если открытого заказа в текущей торговой сессии нет, придёт { "order": null }.
Список и один заказ: GET /orders, GET /orders/{id}
curl -sS -G -H "Authorization: Bearer $FC_TOKEN" --data-urlencode "limit=20" "$FC_API/orders"
curl -sS -H "Authorization: Bearer $FC_TOKEN" "$FC_API/orders/6ae9aefdd12dd7057d4bf68e"
Список отдаёт { "items": [ …заказы… ], "nextCursor": "…" | null } (листается так же, как каталог, limit до 100), один заказ — объект в том же виде, что order выше. Чужой или несуществующий заказ — 404 not_found.
Поля заказа
| Поле | Что означает |
|---|---|
id, number | Идентификатор заказа; номер документа в учётной системе (пустая строка, пока не присвоен) |
status | Статус заказа (например, created; у отменённого — «Отменён») |
currency, warehouse, total | Валюта, код склада, итог по строкам |
cancelled, readyToShip | Отменён; готов к отгрузке |
editable, closedReason | Можно ли менять заказ через API; если нельзя — причина (cancelled, shipped, ready_to_ship, collecting, closed, unsupported) |
lines[] | Строки: lineId, itemCode, name, brand, qty, price, sum, currency, serial (для уценки), addedAt |
Изменить количество строки: PATCH
Увеличить строку 23972 с 2 до 5 штук (ORDER — id заказа, LINE — lineId из GET /orders/current):
curl -X PATCH "$FC_API/orders/$ORDER/lines/$LINE" \
-H "Authorization: Bearer $FC_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"qty":5}'
{
"order": {
"id": "6ae9aefdd12dd7057d4bf68e",
"total": 800,
"editable": true,
"lines": [
{ "lineId": "ln_g_I2gvqZXVc9Y7DK", "itemCode": "24159", "qty": 3, "price": 100, "sum": 300, "…": "…" },
{ "lineId": "ln_xLW2qtzgku-2nn6t", "itemCode": "23972", "qty": 5, "price": 100, "sum": 500, "…": "…" }
],
"…": "…"
},
"changed": true
}
В примере цена строки изменилась со 101 до 100: для 5 штук действует другая ступень шкалы, и по ней пересчитана вся строка. Поэтому изменился и lineId — для следующих запросов берите его из ответа. Строка остаётся одной: добавленные штуки склеиваются с ней, а не создают вторую строку.
Если остатка или лимита «в одни руки» хватило не на всё (запросили 500, можно 10), строка вырастет до доступного количества, а в ответе будет "capped": true:
{ "order": { "…": "…", "lines": [ { "itemCode": "23972", "qty": 10, "price": 99, "…": "…" } ] }, "changed": true, "capped": true }
Уменьшение работает так же ({"qty":3}), а резерв остатка возвращается. Запрос с тем же количеством вернёт "changed": false.
Убрать строку: DELETE
curl -X DELETE "$FC_API/orders/$ORDER/lines/$LINE" \
-H "Authorization: Bearer $FC_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
{ "order": { "id": "6ae9aefdd12dd7057d4bf68e", "total": 500, "lines": [ { "itemCode": "23972", "qty": 5, "…": "…" } ], "…": "…" }, "changed": true }
Отменить заказ: POST /orders/{id}/cancel
curl -X POST "$FC_API/orders/$ORDER/cancel" \
-H "Authorization: Bearer $FC_TOKEN" \
-H "Idempotency-Key: $(uuidgen)"
{ "order": { "id": "6ae9aefdd12dd7057d4bf68e", "status": "Отменён", "cancelled": true, "editable": false, "closedReason": "cancelled", "…": "…" }, "changed": true }
Примеры ошибок при работе с заказом
Устаревший lineId (строку уже изменили или удалили): 409, в ответе актуальный заказ — возьмите из него новый lineId и повторите с новым ключом.
HTTP 409
{
"error": { "code": "line_changed", "message": "Строка заказа изменилась, перечитайте заказ" },
"order": { "id": "6ae9aefdd12dd7057d4bf68e", "lines": [ "…" ], "…": "…" }
}
Нет остатка или исчерпан лимит (запрошенное количество недоступно совсем): 422, заказ не меняется.
HTTP 422
{ "error": { "code": "order_rejected", "message": "Товар недоступен в нужном количестве" } }
Действие не разрешено вам (например, удаление строк выключено менеджером): 403.
HTTP 403
{ "error": { "code": "forbidden", "message": "Удаление строк заказа через API отключено для этого клиента" } }
Последняя строка: 409 last_line_use_cancel. Нет заголовка Idempotency-Key: 400 idempotency_key_required. qty меньше 1: 400 bad_request (чтобы убрать строку, используйте DELETE).
Числа, коды товаров, цены и идентификаторы в примерах условные: ваши значения зависят от вашего каталога и настроек клиента.
6. Idempotency-Key
Все изменяющие запросы (POST, PATCH, DELETE) требуют заголовок Idempotency-Key. Без него — 400 idempotency_key_required.
- Один ключ — одно действие. UUID v4 или ключ от вашего документа; 8–128 символов
A-Za-z0-9_.:-. - Если ответ не получен (обрыв, таймаут), повторите тот же запрос с тем же ключом: действие не выполнится дважды, вернётся сохранённый ответ с заголовком
Idempotent-Replay: true. Не создавайте новый ключ на каждый повтор. - Ключ уникален в пределах токена и хранится 24 часа.
- Тот же ключ с другим запросом — 422
idempotency_key_reused. - Тот же ключ, пока первый запрос ещё выполняется, — 409
request_in_progress; подождите (заголовокRetry-After) и повторите. Если такой ответ не проходит больше минуты (первый запрос оборвался на нашей стороне), прочитайтеGET /orders/currentи, если нужно, повторите действие с новым ключом. - Бизнес-отказы (409, 422) запоминаются и повторяются. Временные (423, 429, 503) и внутренние ошибки — нет: их можно повторять с тем же ключом.
7. Частые вопросы
- 403
api_disabled/orders_disabled. API или заказы не включены вам менеджером. - 422
order_rejected«Товар недоступен в нужном количестве». Нет остатка, исчерпана квота или позиция вам недоступна — причина вmessage. - 422
onec_error. Учётная система не приняла изменение, заказ не изменён. Обратитесь к менеджеру, если повторяется. - Токен перестал работать. Проверьте срок и отзыв в Профиль → API; код ошибки 401 говорит, что именно произошло.