Запросы и ответы
Заголовки, язык ответа, вид денег и дат, постраничная выдача списков с примерами листания на 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 в ответе об ошибке и с
кодом запроса в журнале магазина. Записывайте его у себя рядом с ошибкой:
владелец магазина найдёт по нему запрос.
Язык ответа
Язык выбирается из языков, включённых в магазине:
- Параметр
?locale=ukв адресе, если такой язык в магазине есть. - Иначе первый язык из заголовка
Accept-Language, который есть в магазине. Регион не учитывается:uk-UAозначаетuk. - Иначе основной язык магазина.
Язык влияет на поля 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на последней странице отличается от первой, список менялся. Пройдите его ещё раз.