Документация

Запросы и ответы

Заголовки, язык ответа, вид денег и дат, постраничная выдача списков с примерами листания на curl, PHP и JavaScript.

На этой странице

У всех адресов API одни и те же заголовки, один вид значений и одна постраничная выдача для всех списков. Листание достаточно написать один раз.

Адрес и метод

Все адреса начинаются с https://<домен магазина>/api/v1/. Данные отдают адреса с методом GET (и HEAD). Методом POST работают только два адреса: POST /orders/check считает заказ, а POST /orders создаёт его. Тело у них — объект JSON. Запрос другим методом к существующему адресу получает ответ 405 со списком допустимых методов в заголовке Allow.

Заголовки запроса

Заголовок Нужен Что даёт
Authorization: Bearer <ключ> всегда вход по ключу
Accept: application/json желательно ответ и так приходит в JSON, но заголовок делает намерение явным
Accept-Language: uk нет язык текстов ошибок, подробнее ниже
Content-Type: application/json у POST, желательно тело запроса — JSON. Магазин читает тело заказа как JSON и без этого заголовка
Idempotency-Key: <строка> у POST /orders всегда ключ повтора: по нему магазин узнаёт повторный запрос и не создаёт заказ дважды. Без него ответ 428. Подробно на странице «Заказы партнёра»
curl https://shop.example.ua/api/v1/storefronts \
  -H "Authorization: Bearer <ключ>" \
  -H "Accept: application/json" \
  -H "Accept-Language: uk"

Заголовки ответа

Заголовок Что значит
Content-Type application/json у обычного ответа, application/problem+json у ошибки
X-Request-Id код запроса из восьми латинских букв и цифр, например G7PHUMGK. Приходит в каждом ответе, в том числе в ответе с ошибкой. Его нет только у ответов, которые магазин даёт ещё до API: когда тело запроса слишком большое и пока магазин закрыт на обслуживание. Нет его и у описания API /api/v1/openapi.json
X-RateLimit-Limit предел запросов в минуту для этого ключа. Не приходит, если магазин отказал раньше, чем проверил предел: на неверный или отозванный ключ, чужой IP-адрес, закрытый магазин
X-RateLimit-Limit у описания API у /api/v1/openapi.json свой предел, 60 запросов в минуту с одного IP-адреса. С пределом вашего ключа он не связан
X-RateLimit-Remaining сколько запросов осталось в текущей минуте. Приходит вместе с X-RateLimit-Limit
Retry-After через сколько секунд можно повторить запрос. Приходит с ответом 429 и с ответами 409 у создания заказа, когда заказ ещё оформляется или магазин занят: idempotency_in_progress, external_id_in_progress, order_busy
Location у ответа 201 на создание заказа: полный адрес заказа, например https://shop.example.ua/api/v1/orders/5120
Idempotent-Replayed: true у ответа на повторный POST /orders с тем же Idempotency-Key: заказ был создан раньше, и магазин отдал его, не создавая второй

Код из X-Request-Id совпадает с полем request_id в ответе об ошибке и с кодом запроса в журнале магазина. Записывайте его у себя рядом с ошибкой: владелец магазина найдёт по нему запрос.

Язык ответа

Язык выбирается из языков, включённых в магазине:

  1. Параметр ?locale=uk в адресе, если такой язык в магазине есть.
  2. Иначе первый язык из заголовка Accept-Language, который есть в магазине. Регион не учитывается: uk-UA означает uk.
  3. Иначе основной язык магазина.

Язык влияет на поля title и detail в ответах об ошибках и на пояснения в поле errors, а у заказов ещё на тексты message в причинах отказа и предупреждениях. Язык самого заказа задаётся отдельно, полем locale в теле заказа. Поля, которые в магазине переводятся (названия статусов заказа, видов контактов, способов оплаты и доставки, названия и описания товаров, категорий, брендов и характеристик), всегда приходят сразу на всех языках, как описано ниже. Остальные названия приходят так, как они записаны в магазине.

У товаров от языка ответа зависят ещё три вещи: язык ссылок url на товары, категории и бренды, названия и значения характеристик в поле features и название бренда в поле brand. Они приходят так, как их видит покупатель витрины на этом языке. Если у витрины такого языка нет, ссылки ведут на её основной язык.

Какие языки включены в магазине и какой из них основной, отдаёт GET /api/v1/shop в полях locales и main_locale.

Вид значений

Ответ с одной записью приходит в поле data:

{ "data": { "name": "CartiX Демо", "base_currency": "UAH" } }

Список приходит в data массивом, рядом лежат meta и links, о них ниже.

Деньги и другие точные числа приходят строкой с точкой, а не числом: "1234.50". Так в ответе нет ошибок округления, которые даёт число с плавающей точкой. Число знаков после точки у каждого поля своё и описано в справочнике адресов: у курса валюты шесть знаков ("41.250000"), у ставки налога три ("20.000"). Валюта суммы всегда приходит отдельным полем.

Количество товара приходит так же, строкой с тремя знаками после точки ("2.250"). Так приходят остатки, шаг и наименьшее количество в заказе. Товары в магазине бывают дробными, их продают метрами и килограммами.

Даты приходят в формате ISO 8601 в UTC с буквой Z на конце: "2026-12-31T21:59:59Z". Переводить их в местное время нужно у себя. Часовой пояс магазина отдаёт /shop в поле timezone.

Переводимые поля приходят объектом, где ключ — код языка:

"name": { "ru": "Новый", "uk": "Новий", "en": "New" }

У статусов заказа, видов контактов и всего каталога (товаров, артикулов, категорий, брендов, типов товаров и характеристик) языка, на котором поле не заполнено, в объекте нет. Если поле не заполнено ни на одном языке, приходит null. Берите значение на нужном языке, а если его нет, на основном языке магазина.

У способов оплаты и доставки в объекте есть все языки магазина. Если перевода на какой-то язык нет, на нём приходит основное название способа, то есть то, что видит покупатель на витрине.

Пустые значения приходят как null, флаги как true или false, номера записей как целые числа.

В теле заказа деньги и количества можно передать и строкой, и числом: "2.5" и 2.5 магазин поймёт одинаково. Номера записей принимаются целым числом или строкой из цифр. Сколько знаков после точки допускает каждое поле, описано на странице «Заказы: адреса и поля».

Постраничная выдача

Все списки отдаются страницами. Страница задаётся двумя параметрами:

Параметр По умолчанию Допустимо
limit 50 целое от 1 до 200
offset 0 целое от 0

limit — сколько записей вернуть, offset — сколько записей пропустить с начала списка.

curl "https://shop.example.ua/api/v1/order_statuses?limit=2&offset=2" \
  -H "Authorization: Bearer <ключ>"
{
    "data": [
        { "id": 3, "code": "shipped", "...": "..." },
        { "id": 4, "code": "delivered", "...": "..." }
    ],
    "meta": { "total": 5, "limit": 2, "offset": 2 },
    "links": {
        "next": "https://shop.example.ua/api/v1/order_statuses?limit=2&offset=4",
        "prev": "https://shop.example.ua/api/v1/order_statuses?limit=2&offset=0"
    }
}
Поле Что значит
meta.total сколько записей в списке всего
meta.limit сколько записей на странице, как в запросе
meta.offset сколько записей пропущено, как в запросе
links.next полный адрес следующей страницы. null, если эта страница последняя
links.prev полный адрес предыдущей страницы. null на первой странице

Ссылки сохраняют остальные параметры запроса, например locale. Удобнее всего идти по links.next, пока он не станет null, а не считать смещение самому.

Порядок записей в каждом списке постоянный и не меняется от запроса к запросу. Какой он у каждого списка, указано в справочнике адресов.

Неверные значения не подменяются молча. На limit=0, limit=201, limit=abc, offset=-1 или offset=1.5 магазин отвечает 422 с кодом validation_failed и объяснением в поле errors:

{
    "code": "validation_failed",
    "errors": {
        "limit": ["limit — целое число от 1 до 200."],
        "offset": ["offset — целое число от 0."]
    }
}

Смещение за концом списка ошибкой не считается. Ответ придёт с пустым data и links.next: null, а links.prev поведёт на последнюю непустую страницу. Например, при семи записях, limit=3 и offset=100 ссылка prev ведёт на offset=4. Смещение длиннее девяти цифр тоже даёт пустую страницу, а в meta.offset тогда приходит 999999999.

У адресов каталога, цен и остатков свои пределы и параметры. Список номеров /products/ids отдаёт по умолчанию 5000 записей, самое большее 10 000, а /prices с параметром sku_ids отдаёт по умолчанию 100. Если адрес каталога получил параметр, которого не знает, он отвечает 422, а не пропускает его молча. Подробно об этом на странице «Каталог: видимость и синхронизация».

Листание по after_id

Длинные списки можно листать не по смещению, а по номеру записи: /products, /products/ids, /skus, /prices, /inventory и /orders. Для этого вместо offset передайте after_id. В ответ попадут записи с номером больше указанного, по возрастанию номера. Первую страницу просите с after_id=0.

curl "https://shop.example.ua/api/v1/products?after_id=0&limit=100" \
  -H "Authorization: Bearer <ключ>"
{
    "data": [
        { "id": 1040, "...": "..." },
        { "id": 1045, "...": "..." }
    ],
    "meta": { "total": 204, "limit": 100, "after_id": 0 },
    "links": {
        "next": "https://shop.example.ua/api/v1/products?limit=100&after_id=1311",
        "prev": null
    }
}
Поле Что значит
meta.total сколько записей во всём списке с этими фильтрами, без учёта after_id. По нему видно, сколько всего предстоит забрать
meta.limit сколько записей на странице
meta.after_id номер из запроса. Поля offset в таком ответе нет
links.next адрес следующей страницы: в нём after_id равен номеру последней записи этой страницы. null, если записей дальше нет
links.prev всегда null. Назад по номеру не листают

Ссылка next сохраняет остальные параметры запроса, например фильтры и storefront_id. Идите по ней, пока она не станет null.

Листать по номеру надёжнее, чем по смещению. Если между запросами владелец снимет товар с продажи, при листании по offset все следующие товары сдвинутся на одну позицию назад, и один из них вы пропустите. При листании по after_id следующая страница начинается сразу после последнего полученного номера, поэтому записи не теряются и не повторяются. Число в meta.total при этом может меняться от страницы к странице.

Ошибка 422 Поле в errors Сообщение
after_id не целое число от 0 after_id «after_id — номер записи, целое число от 0.»
after_id вместе с offset, даже с offset=0 offset «offset и after_id вместе не передаются. Листайте страницы чем-то одним.»

У справочников (/categories, /brands, /product_types, /features и справочников магазина) листания по after_id нет. Адреса каталога отвечают на этот параметр 422 как на неизвестный, а справочники магазина его пропускают.

Листание на curl и jq

url="https://shop.example.ua/api/v1/warehouses?limit=200"

while [ "$url" != "null" ]; do
    page=$(curl -s "$url" -H "Authorization: Bearer $CARTIX_KEY")
    echo "$page" | jq -c '.data[]'
    url=$(echo "$page" | jq -r '.links.next')
done

Листание на PHP

function cartixList(string $url, string $key): array
{
    $items = [];

    while ($url !== null) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer '.$key,
                'Accept: application/json',
            ],
        ]);
        $body = json_decode(curl_exec($ch), true);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($status !== 200) {
            throw new RuntimeException("{$body['code']}: {$body['detail']} (request_id {$body['request_id']})");
        }

        array_push($items, ...$body['data']);
        $url = $body['links']['next'];
    }

    return $items;
}

$warehouses = cartixList('https://shop.example.ua/api/v1/warehouses?limit=200', getenv('CARTIX_KEY'));

Листание на JavaScript (Node.js 18 и новее)

async function cartixList(url, key) {
    const items = [];

    while (url) {
        const response = await fetch(url, {
            headers: { Authorization: `Bearer ${key}`, Accept: 'application/json' },
        });
        const body = await response.json();

        if (!response.ok) {
            throw new Error(`${body.code}: ${body.detail} (request_id ${body.request_id})`);
        }

        items.push(...body.data);
        url = body.links.next;
    }

    return items;
}

const statuses = await cartixList('https://shop.example.ua/api/v1/order_statuses?limit=200', process.env.CARTIX_KEY);

Если данные меняются во время листания

Страницы считаются по смещению, поэтому список, который меняется прямо во время листания, может сдвинуться. Если между запросами в магазине добавили запись, одна запись на стыке страниц придёт дважды. Если удалили, одна запись на стыке пропадёт.

  • Каталог, цены, остатки и заказы листайте по after_id, тогда сдвиг вам не грозит.
  • Справочники короткие. Просите их одним запросом с limit=200, тогда листать не придётся.
  • Складывайте записи у себя по id, а не дописывайте в конец. Повтор тогда просто перезапишет ту же запись.
  • Если meta.total на последней странице отличается от первой, список менялся. Пройдите его ещё раз.
Обновлено 21 октября 2026