Partner API guide

Catalog with your prices and stock and, if enabled, orders. Full field and response reference: the openapi.yaml specification file (OpenAPI 3.0; opens in Swagger Editor, Postman, Insomnia), attached to this guide. Error message texts are in Russian; rely on code.

1. Quick start

  1. Open the bot's mini app → Profile → API and create a token: name, expiry, permissions ("catalog only" or "catalog and orders"). The section is visible only if a manager has enabled API access for you.
  2. The token is shown once. Keep it in a secrets store; never put it in a repository or client-side code. If it leaks, revoke it there and issue a new one.
  3. Send the token with every request: Authorization: Bearer fcp_….
export FC_API="https://<host>/api/partner"
curl -H "Authorization: Bearer $FC_TOKEN" "$FC_API/schedule"

Your manager will give you the base address ($FC_API in the examples). The API is server-to-server: browser requests (CORS) are not supported, call it from your backend.

2. General rules

3. Error format

HTTP 403
{ "error": { "code": "scope_missing", "message": "…" } }
HTTPCodesWhat to do
400bad_request, idempotency_key_required, idempotency_key_invalidFix the request
401token_missing, token_invalid, token_revoked, token_expiredCheck or reissue the token
403api_disabled, client_not_allowed, scope_missing, orders_disabled, forbidden, ip_not_allowedPermission missing: contact your manager
404not_foundNo such item or order
409request_in_progress, order_closed, line_changed, last_line_use_cancelSee "Orders"
422order_rejected, onec_error, qty_limit_exceeded, qty_increase_unavailable, serial_line_qty_fixed, idempotency_key_reusedRequest understood but not accepted; reason in message
423bot_closedBot is closed by schedule; retry during working hours
429rate_limitedSlow down and retry
503order_busy, onec_unavailableTemporary failure; retry with the same Idempotency-Key

4. Catalog (catalog:read)

RequestReturns
GET /scheduleWhether the bot is open now and today's working hours
GET /brandsBrands in stock
GET /categories?brand=…Categories of a brand
GET /catalogItems with prices. Filters: brand, category, warehouse; paging: limit (up to 1000, default 200) and cursor
GET /catalog/{itemCode}One item, including the quantity price tiers (tiers)

The storefront data is refreshed every 30–60 seconds; polling more often is pointless. In all examples below $FC_API and $FC_TOKEN are set as in "Quick start".

GET /catalog parameters

ParameterValueExample
brandA brand from GET /brands. One value, case-insensitiveApple
categoryA category from GET /categories?brand=…. One value, case-insensitiveiPhone
warehouseYour warehouse code (from the client card); all your warehouses by default23
limitPage size: 1 to 1000, default 200100
cursornextCursor value from the previous response—

Filters are combined with AND: brand plus category returns the items of that category of that brand. Pass names with spaces or non-Latin letters via --data-urlencode (with -G curl puts them into the URL itself). If nothing matches, the response is an empty list "items": [], not an error.

Today's working hours: GET /schedule

This request always answers, including when the bot is closed, and does not depend on schedule compliance in your policy. A valid token is all it needs.

curl -sS -H "Authorization: Bearer $FC_TOKEN" "$FC_API/schedule"
{
  "open": false,
  "timezone": "Asia/Dubai",
  "today": { "opensAt": "10:00", "closesAt": "18:00" }
}
FieldMeaning
opentrue if the bot is accepting requests right now
timezoneTime zone the working hours are given in
today.opensAt, today.closesAtOpening and closing time today, HH:MM
todaynull if there are no working hours today (for example, a day off) or they are not set. Then rely on open.

Working hours for other days are not returned by this request. If the bot is closed and will not open again today, repeat the request tomorrow.

Brands: GET /brands

curl -sS -H "Authorization: Bearer $FC_TOKEN" "$FC_API/brands"
{ "brands": ["Apple", "Dyson", "Samsung", "Sony"] }

Categories of a brand: GET /categories

curl -sS -G -H "Authorization: Bearer $FC_TOKEN" \
  --data-urlencode "brand=Apple" \
  "$FC_API/categories"
{ "categories": ["AirPods", "iPad", "iPhone", "MacBook"] }

The brand parameter is required, otherwise 400 bad_request.

Catalog of one brand

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"
}

A non-null nextCursor means there is a next page (see "Paging through the whole catalog" below). In a list each item has a single price; the quantity price tiers come only in the item card.

Catalog of a brand and category

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
}

To narrow to one of several warehouses, add --data-urlencode "warehouse=23".

Several categories

The category parameter accepts one value, so for several categories make one request per category and merge the responses on your side. Example: Apple items from iPhone, iPad and AirPods as a single list (requires 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, "…": "…" }
]

If a category has more than 1000 items, page through each category by nextCursor (below). To get all categories of a brand without looping, omit category (catalog of the brand).

Paging through the whole catalog

Pass nextCursor from each response as cursor until it is 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

The cursor is an opaque string: do not parse or modify it. If the storefront was refreshed between pages, the export continues from the nearest item without an error; for a strict reconciliation repeat the whole export.

Item card with price tiers: 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 is an array. Read tiers as "from qty pieces the price per piece is price". Tiers above the available stock are hidden. Discounted items have no tiers. If the item does not exist or is not available to you — 404 not_found.

Stock

Stock is returned in the available field of GET /catalog and GET /catalog/{itemCode}. There is no separate stock request.

Stock permission. Showing stock is enabled by your manager. If it is not granted, available is absent from responses, while the catalog and orders work as usual. To receive stock, contact your manager.

Stock of one item:

curl -sS -H "Authorization: Bearer $FC_TOKEN" "$FC_API/catalog/24014" \
  | jq '.items[] | {itemCode, warehouse, available}'
{ "itemCode": "24014", "warehouse": "23", "available": 40 }

Stock for a brand and category (code, warehouse, stock):

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 }

Stock changes with every order. When you add to an order the server checks the quantity again: if stock was not enough, the response has addedQty and capped: true (see "Orders").

Catalog item fields

FieldMeaning
itemCodeItem code (used in orders)
name, brand, categoryName, brand, category
warehouseWarehouse code
currency, priceCurrency and your price per piece (with the markup from your client card)
availableStock. Absent if stock is not shown to you
tiersPrice tiers: only in the item card and only if enabled
markdown, serial, commentDiscounted item: flag, serial number, comment (only if discounted items are available to you; sold one piece at a time)
warehouseNameWarehouse name (only if allowed)

5. Orders (orders:write)

Available only if a manager has enabled orders for you and the token was issued with "catalog and orders". An order is the same document as the cart in the bot and the mini app.

RequestActionRequires
POST /orders/linesAdd an item to the orderorders enabled
GET /orders/currentOpen order of the current trading sessionorders enabled
GET /orders, GET /orders/{id}History and a single order with statusorders enabled
PATCH /orders/{id}/lines/{lineId}Change a line's quantity (up or down)allowModify
DELETE /orders/{id}/lines/{lineId}Remove a lineallowDelete
POST /orders/{id}/cancelCancel the orderallowCancel

Add an item

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}'

Response 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
}

Here the price 343 is the tier price for 5 pieces (as in the item card example above). If stock or the per-buyer limit was not enough for everything, you get, for example, "requestedQty": 5, "addedQty": 3, "capped": true: what is available was added to the order. If nothing can be added — 422 order_rejected.

Edit and cancel

Current order: 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" }
    ]
  }
}

If there is no open order in the current trading session, the response is { "order": null }.

List and single order: 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"

The list returns { "items": [ …orders… ], "nextCursor": "…" | null } (paged like the catalog, limit up to 100); a single order is an object in the same shape as order above. Someone else's or a non-existent order — 404 not_found.

Order fields

FieldMeaning
id, numberOrder identifier; document number in the accounting system (empty string until assigned)
statusOrder status (for example created; a cancelled order has a cancelled status)
currency, warehouse, totalCurrency, warehouse code, total of the lines
cancelled, readyToShipCancelled; ready to ship
editable, closedReasonWhether the order can be changed through the API; if not — the reason (cancelled, shipped, ready_to_ship, collecting, closed, unsupported)
lines[]Lines: lineId, itemCode, name, brand, qty, price, sum, currency, serial (discounted items), addedAt

Line identifier: lineId

lineId is the address of an order line in PATCH and DELETE requests. It is created by the server: you do not invent it, you take it from a response (lines[].lineId in POST /orders/lines, GET /orders/current, GET /orders, GET /orders/{id} and in responses to changes).

Change a line's quantity: PATCH

Increase line 23972 from 2 to 5 pieces (ORDER is the order id, LINE is the lineId from 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
}

In the example the line price changed from 101 to 100: a different tier applies for 5 pieces, and the whole line is repriced by it. That is why lineId changed too — take it from the response for the next requests. The line stays a single line: the added pieces are merged into it, no second line is created.

If stock or the per-buyer limit was not enough for everything (you asked for 500, only 10 are possible), the line grows to the available quantity and the response has "capped": true:

{ "order": { "…": "…", "lines": [ { "itemCode": "23972", "qty": 10, "price": 99, "…": "…" } ] }, "changed": true, "capped": true }

Decreasing works the same way ({"qty":3}) and the stock reservation is released. A request with the same quantity returns "changed": false.

Remove a line: 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 }

Cancel an order: 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 }

Order error examples

Stale lineId (the line was already changed or removed): 409 with the current order in the response — take the new lineId from it and repeat with a new key.

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

No stock or limit exhausted (the requested quantity is not available at all): 422, the order is unchanged.

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

Action not allowed for you (for example removing lines is switched off by your manager): 403.

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

Last line: 409 last_line_use_cancel. No Idempotency-Key header: 400 idempotency_key_required. qty below 1: 400 bad_request (to remove a line use DELETE).

Numbers, item codes, prices and identifiers in the examples are illustrative: your values depend on your catalog and client settings. The message texts are in Russian; rely on code.

6. Idempotency-Key

Why it exists. If a request is interrupted (network, timeout) and you did not get a response, you cannot tell whether the action was applied. Retrying blindly is dangerous: a line may be added twice. Idempotency-Key solves this: a retry with the same key is not executed a second time, and the response of the first request is returned instead.

Which requests. All order-modifying requests require the Idempotency-Key header: POST /orders/lines (add an item), PATCH …/lines/{lineId} (change quantity), DELETE …/lines/{lineId} (remove a line) and POST …/cancel (cancel the order). Without it: 400 idempotency_key_required. GET requests (catalog, reading orders) do not need a key.

Who creates the key. The key is created by your system before the request is sent; the server does not issue keys. Use a new key for each new action (for example a UUID v4: uuidgen on Linux, or your language's UUID function), or a key derived from your own document, such as order-1042-line-3, so that re-sending the same action from your system automatically gets the same key. Store the key together with the action until you get a response: you need it for a retry.

KEY=$(uuidgen)
curl -sS -X PATCH "$FC_API/orders/$ORDER/lines/$LINE" \
  -H "Authorization: Bearer $FC_TOKEN" \
  -H "Idempotency-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"qty": 5}'
# no response received — retry: same $KEY, same body
curl -sS -i -X PATCH "$FC_API/orders/$ORDER/lines/$LINE" \
  -H "Authorization: Bearer $FC_TOKEN" \
  -H "Idempotency-Key: $KEY" \
  -H "Content-Type: application/json" \
  -d '{"qty": 5}'
# → 200 and header Idempotent-Replay: true, the quantity was not added a second time

7. FAQ