Каталог: видимость и синхронизация
Какие товары, категории и бренды видит партнёр, от лица какой витрины собирается ответ, как забрать каталог и потом только изменения.
На этой странице
Адреса каталога, цен и остатков отдают товары магазина так, как их видит покупатель витрины. На этой странице описано, что именно попадёт к вам, от лица какой витрины собирается ответ, как забрать каталог в первый раз и как потом забирать только изменения.
| Адрес | Право | updated_since |
after_id |
Описание |
|---|---|---|---|---|
GET /api/v1/categories |
catalog:read |
да | нет | категории |
GET /api/v1/brands |
catalog:read |
нет | нет | бренды |
GET /api/v1/product_types |
catalog:read |
нет | нет | типы товаров |
GET /api/v1/features |
catalog:read |
нет | нет | характеристики |
GET /api/v1/products |
catalog:read |
да | да | товары |
GET /api/v1/products/{id} |
catalog:read |
нет | нет | один товар |
GET /api/v1/products/ids |
catalog:read |
да | да | номера всех товаров |
GET /api/v1/skus |
catalog:read |
да | да | артикулы |
GET /api/v1/prices |
prices:read |
нет | да | цены |
GET /api/v1/inventory |
inventory:read |
да | да | остатки |
Адреса /prices и /inventory не требуют права catalog:read. Ключу, который
только сверяет цены, достаточно права prices:read.
Что вы видите
API показывает ровно то, что видит покупатель витрины. Всё, что владелец скрыл от покупателей, скрыто и от вас.
Товары. Приходят только опубликованные товары. Черновик, скрытый товар и
товар в архиве не приходят ни в одном списке. Адрес /products/{id} отвечает
на них 404 с кодом not_found, а их артикулы не находятся ни в /skus, ни в
/prices, ни в /inventory. Все витрины магазина продают одни и те же
товары, поэтому набор товаров от витрины не зависит.
Артикулы. У опубликованного товара приходят все артикулы, в том числе
снятые с продажи. У такого артикула is_available равно false, и купить его
покупатель не может.
Категории. Категория видна, если она включена, доступна этой витрине и
видны все её родители. Доступна она тогда, когда в её настройках не выбрано ни
одной витрины или выбрана именно эта. Раздел внутри выключенного раздела не
приходит, потому что на сайте он тоже не открывается. В поле category_ids
товара остаются только видимые категории. Если видимых нет, товар всё равно
приходит, но с пустым category_ids.
Бренды. Если владелец выключил страницу бренда или отдал её другой
витрине, бренда нет в /brands, поле brand у товара равно null, а фильтр
brand_id по такому бренду возвращает пустой список.
Характеристики. Служебные характеристики, которые покупатель не видит, не
приходят ни в /features, ни у товара. Исключение составляют опции покупки,
по которым покупатель выбирает вариант (цвет, размер). Они есть в /features
всегда, потому что на них ссылаются артикулы в поле options.
Фото. Приходят только фото, которые показываются на этой витрине.
Закупочную цену API не отдаёт ни в одном адресе.
Витрина ответа
У магазина может быть несколько витрин со своим доменом, валютой, языками и
складами. Ответ адресов каталога магазин всегда собирает от лица одной
витрины. Её номер передайте в параметре storefront_id, а список витрин
отдаёт GET /api/v1/storefronts.
Если storefront_id не передан, магазин берёт основную витрину. Если основная
витрина выключена, он берёт первую включённую по номеру.
От витрины зависит:
- валюта цен в блоке
priceи в/prices; - склады, с которых считаются остатки, если владелец не выбрал ключу свои склады;
- какие категории и бренды видны;
- домен и язык в ссылках
urlна товары, категории и бренды; - какие фото товара приходят.
Типы товаров и характеристики от витрины не зависят, но неверный
storefront_id эти адреса тоже не принимают.
| Ошибка | Когда |
|---|---|
422, поле storefront_id: «Витрины с таким номером нет или она выключена» |
витрины с таким номером нет, она выключена или номер записан не целым положительным числом |
422, поле storefront_id: «У магазина нет ни одной включённой витрины» |
storefront_id не передан, а включённых витрин в магазине нет |
Общие параметры
Эти параметры принимает каждый адрес каталога, цен и остатков:
| Параметр | Что делает |
|---|---|
limit, offset |
размер страницы и смещение, как у всех списков. Подробно на странице «Запросы и ответы». Адрес одного товара /products/{id} страниц не листает, и на эти параметры отвечает 422 |
locale |
язык ответа. От него зависят язык ссылок url, названия характеристик и их значений у товара и название бренда в поле brand |
storefront_id |
витрина ответа, описана выше |
Остальные параметры у каждого адреса свои и перечислены в его описании.
Неизвестный параметр считается ошибкой. Если в адресе есть параметр, которого
этот адрес не знает, магазин отвечает 422 и перечисляет допустимые параметры.
Так опечатка ?categry_id=5 не вернёт вам молча весь каталог вместо одной
категории:
{
"code": "validation_failed",
"errors": {
"categry_id": ["Такого параметра у этого адреса нет. Допустимые параметры: limit, offset, locale, storefront_id, category_id, brand_id, type_id, sku, barcode, updated_since, after_id."]
}
}
Пустое значение считается непереданным: ?category_id= равносильно
запросу без этого параметра.
Неверные значения параметров дают ответ 422 с кодом validation_failed.
Сообщения для каждого параметра перечислены в описании адреса. Если ошибок
несколько, магазин сначала перечисляет все неизвестные параметры. Неверные
значения он называет по одному, и следующую ошибку вы увидите, когда исправите
предыдущую.
Первая выгрузка
- Заберите справочники:
/categories,/brands,/product_types,/features. Они короткие, их удобно просить сlimit=200и идти поlinks.next. - Запомните текущее время в UTC. С него начнётся следующая сверка изменений.
- Заберите товары:
/products?after_id=0&limit=100, а затем идите поlinks.next, пока он не станетnull. Каждый товар приходит со всеми артикулами, фото и характеристиками, а при правахprices:readиinventory:readещё с ценами и остатками. - Складывайте товары у себя по
id. Товар, который пришёл повторно, перезапишет ту же запись.
Для каталога листайте по after_id, а не по offset. Если во время выгрузки
владелец снимет товар с продажи, при листании по смещению следующий за ним
товар сдвинется на прошлую страницу, и вы его пропустите. При листании по
after_id такого не бывает. Подробно этот способ описан на странице
«Запросы и ответы».
Изменения: updated_since
Параметр updated_since оставляет в списке только записи, изменённые начиная
с указанного момента. Его принимают /categories, /products,
/products/ids, /skus и /inventory.
curl "https://shop.example.ua/api/v1/products?updated_since=2026-10-17T09:00:00Z&after_id=0&limit=100" \
-H "Authorization: Bearer <ключ>"
Формат. Дата и время в ISO 8601 с часовым поясом: 2026-10-17T09:00:00Z,
2026-10-17T12:00:00+03:00, 2026-10-17T12:00+0300. Секунды и доли секунды
можно не писать. Дату без времени (2026-10-17) и время без пояса магазин не
принимает, потому что непонятно, в каком поясе их считать. На них приходит
ответ 422 с сообщением «Нужны дата и время в формате ISO 8601, например
2026-10-17T09:00:00Z» в поле updated_since.
Знак + в адресе нужно экранировать как %2B. Если вы его не экранировали и
он пришёл пробелом (…12:00:00 03:00), магазин всё равно поймёт время
правильно.
Граница включается. В ответ попадают записи, изменённые в указанную секунду и позже. Время изменения магазин хранит с точностью до секунды.
Что считается изменением товара. Товар попадает в ответ, если владелец или сотрудник:
- поменял в карточке товара то, что приходит в ответе: статус, адрес, тип, главную категорию, единицу продажи, шаг или минимальное количество;
- поменял перевод названия, описания или SEO-полей на любом языке;
- добавил, поменял или удалил значение характеристики товара;
- добавил, удалил, переставил или отредактировал фото;
- перенёс товар в другую категорию, в том числе массовым действием;
- поменял любой артикул товара: цену, код, штрихкод, подпись варианта или закупочную цену. Закупочной цены в ответе нет, но от неё может зависеть цена, например когда дополнение «Оптовые цены» не даёт продавать ниже закупки;
- провёл движение остатка по любому артикулу: приход, списание, резерв под заказ, отгрузку, возврат, перемещение, инвентаризацию;
- переименовал склад, на котором лежит артикул, включил или выключил его, сделал объединённым, отдал другой витрине, поменял порядок складов или удалил склад. Если склад выбран у какого-нибудь ключа, изменёнными считаются все артикулы магазина и их товары.
Если владелец поправил рейтинг, число продаж, запрет индексации, ставку налога, дату поступления или настройки рекомендаций, товар изменённым не считается, потому что этого в ответе нет.
Импорт каталога, переезд с другой платформы и объединение характеристик в панели отмечают товары по тем же правилам. Если владелец повторно загрузил тот же файл, товар, в котором ничего не поменялось, изменённым не считается. Товары с фото по ссылкам всё равно придут, потому что такие фото магазин скачивает заново при каждом импорте.
Импорт и переезд идут долго, поэтому магазин отмечает их записи временем, когда загрузка закончилась, а не временем каждой строки. Если вы спросили изменения, пока импорт ещё шёл, его записи придут при следующей сверке.
Товар считается изменённым и тогда, когда сам товар никто не открывал, но поменялось то, что видно у него в ответе:
- началась или закончилась акция каталога, в том числе по расписанию, или владелец поправил её условия, и от этого изменилась цена товара;
- у валюты сменился курс, вручную в настройках или при обновлении курсов из банка. Изменёнными считаются товары, у которых цена артикула задана в этой валюте. Если в этой валюте продаёт какая-нибудь витрина, изменённым считается весь каталог, потому что на этой витрине меняются все цены;
- владелец сменил основную валюту магазина или валюту витрины. Изменённым считается весь каталог;
- владелец переименовал характеристику или её значение, сменил цвет значения, а у характеристики код, единицу, порядок или видимость. Изменёнными считаются все товары, у которых эта характеристика заполнена. Если владелец удалил характеристику, изменёнными считаются товары, у которых она была заполнена;
- владелец поменял скидку категории покупателей, ступени цен или настройки в дополнении «Оптовые цены» версии 1.3.1 и новее. После правки ступени изменёнными считаются артикулы её товара, а после правки скидки категории или настроек дополнения изменёнными считаются все артикулы и все товары;
- владелец поменял у типа товара единицу продажи, шаг или минимальное количество, добавил, убрал или переставил характеристики типа. Изменёнными считаются все товары этого типа. Названия типа в ответе товара нет, поэтому после переименования типа товары не приходят;
- владелец переименовал бренд, завёл, включил или выключил страницу бренда, отдал её другой витрине или удалил. Изменёнными считаются все товары этого бренда. Если владелец выбрал брендом другую характеристику, изменёнными считаются все товары;
- владелец включил или выключил категорию, перенёс в другой раздел, поменял
её адрес (
slug), витрины или удалил. Изменёнными считаются эта категория, все её подкатегории и все товары из них; - владелец поменял у любой витрины то, от чего зависит ответ: сделал её основной, включил или выключил, поменял домен, язык в адресе, основной язык, языки витрины или способ адресов страниц. Сюда же относится удаление витрины и новая витрина, которая сразу стала основной или единственной включённой. Изменёнными считаются все категории, все товары и все артикулы. Название витрины, тема оформления и реквизиты на ответ не влияют, и их правка ничего не отмечает;
- владелец сменил основной язык магазина, включил, выключил или удалил язык или поменял его префикс в адресе. Изменёнными считаются все категории, все товары и все артикулы;
- владелец включил или выключил настройку «Разрешить заказ, когда товара
нет». От неё зависит поле
preorder, поэтому изменёнными считаются все артикулы и все товары.
Курсы из банка магазин обновляет сам раз в сутки и отмечает только товары в тех валютах, курс которых поменялся. Если витрина продаёт не в основной валюте магазина, при каждом сдвиге её курса к вам придёт весь каталог. Банк обычно меняет курс каждый рабочий день, так что весь каталог будет приходить почти ежедневно.
Артикул в /skus и /inventory считается изменённым, если:
- поменялся он сам или его перевод, в том числе закупочная цена;
- по нему было движение остатка;
- склад, на котором он лежит, переименовали, включили или выключили, сделали объединённым, отдали другой витрине, переставили или удалили;
- так же поменялся склад, выбранный у какого-нибудь ключа. Тогда изменёнными считаются все артикулы;
- его цена изменилась из-за акции, курса, валюты витрины или правки в дополнении «Оптовые цены»;
- владелец поменял витрину или языки магазина так, как описано выше;
- владелец переключил настройку «Разрешить заказ, когда товара нет».
В /skus приходят ещё артикулы товаров, которые изменились по правилам выше,
например после правки характеристик, фото или бренда. От товара зависят опции
и фото артикула, поэтому такой артикул вы получите заново. В /inventory этих
артикулов нет, потому что их остатки не изменились.
Категория в /categories считается изменённой, если у неё поменяли название,
перевод, адрес, тип или витрины, а также если её саму или раздел, в котором она
лежит, включили, перенесли или дали другой адрес. Настроек фильтра, условий
подборки, сортировки товаров и запрета индексации в ответе нет, и их правка
категорию изменённой не делает.
Все категории сразу считаются изменёнными, если у любой витрины или у языков
магазина поменялось то, что описано выше, потому что от этого зависят ссылки.
То же бывает, когда владелец добавляет, переносит, переставляет или удаляет
любую категорию. Поле sort хранит место в дереве, и после такой правки оно
сдвигается сразу у многих категорий.
Выключенные, удалённые и скрытые от этой витрины категории в /categories не
приходят, поэтому их пропажу вы заметите, только сверив полный список.
Что не отмечается. Цену вашему контакту может считать и другое дополнение
цен. Если оно не сообщает магазину о своих правках, цены в ответе меняются, а
товары изменёнными не считаются. «Оптовые цены» сообщают о правках начиная с
версии 1.3.1. Перевод вашего контакта в другую категорию покупателей товары
изменёнными тоже не делает. Вместо этого сдвигается время в
settings_changed_at, об этом написано ниже.
Цены целиком сверяйте через
/prices, листая по after_id. Если цены
вашему контакту считает дополнение, которое не сообщает о правках, или вы не
знаете, какое дополнение их считает, делайте это раз в час. Такие правила
владелец меняет вручную и редко, поэтому час задержки почти ничего не стоит, а
сверка каждые несколько минут только расходует предел запросов. Каталог в
10 000 артикулов проходится за 50 запросов по 200 записей, это меньше половины
стандартного предела в 120 запросов в минуту.
Если ваши цены совпадают с
ценами витрины или их задают только «Оптовые цены», все изменения цен приходят
через updated_since. Тогда полной сверки раз в сутки хватит на случай, если
что-то было пропущено. Какие дополнения считают ваши цены, вам скажет владелец
магазина.
Справочники короткие, поэтому их проще забирать целиком при каждой сверке. Так вы заметите и удалённые категории.
Настройки ключа. Когда владелец меняет права вашего ключа, вид остатков,
склады или вид описания, сами товары остаются прежними, а ответы меняются.
Например, с новым правом prices:read у товаров появляется блок price,
которого в забранных раньше товарах нет. Так же меняются все цены, когда ваш
контакт переводят в другую категорию покупателей, вручную или автоматически по
условиям категорий.
Время последней такой смены приходит в поле
settings_changed_at ответа
GET /api/v1/me. Проверяйте его при
каждой сверке. Если оно позже прошлой сверки, заберите каталог, цены и
остатки заново. Название ключа и ограничения (число запросов в минуту,
разрешённые IP-адреса, срок действия) это время не сдвигают.
Время берите с запасом. Отправляйте в updated_since время начала прошлой
сверки минус несколько минут, например пять. Часы вашего сервера и магазина
могут расходиться, и запас защищает от этой разницы. Из-за запаса часть
записей придёт повторно. Складывайте записи по id, тогда повтор перезапишет
ту же запись.
Поле updated_at в ответе показывает, когда товар или его артикулы в
последний раз изменились по правилам выше. Это не время правки в панели. После
смены курса оно сдвигается, хотя товар никто не открывал, а после правки
рейтинга остаётся прежним. Движение остатка в это поле не входит, поэтому
товар, у которого изменился только остаток, может прийти с updated_at раньше
вашей границы.
Снятые с продажи и удалённые товары
Через updated_since вы узнаете о новых и изменённых товарах, но не о
пропавших. Товар, который сняли с продажи или удалили, больше нигде не
приходит. Чтобы найти такие товары, сверяйте номера через
GET /api/v1/products/ids. Этот
адрес отдаёт номера всех видимых товаров по возрастанию, по умолчанию 5000 на
страницу, самое большее 10 000. Товар, которого нет в этом списке, уберите у
себя.
Артикул тоже может пропасть, если владелец удалил его у товара. Поэтому, когда товар пришёл в ответе, заменяйте у себя весь список его артикулов, а не дописывайте новые к старым.
Пример сверки на PHP
function cartixGet(string $url, string $key): array
{
$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']})");
}
return $body;
}
$base = 'https://shop.example.ua/api/v1';
$key = getenv('CARTIX_KEY');
// Время прошлой сверки ($lastSync) и прошлой сверки цен ($lastPricesSync) хранится у вас; запас — пять минут
$startedAt = gmdate('Y-m-d\TH:i:s\Z');
$since = gmdate('Y-m-d\TH:i:s\Z', strtotime($lastSync) - 300);
// 0. Если после прошлой сверки владелец поменял права или настройки ключа
// либо перевёл ваш контакт в другую категорию покупателей, забираем всё.
// Без updated_since придут все товары (http_build_query пропускает null)
$me = cartixGet($base.'/me', $key)['data'];
if ($me['settings_changed_at'] !== null && $me['settings_changed_at'] >= $lastSync) {
$since = null;
}
// 1. Изменённые товары
$url = $base.'/products?'.http_build_query(['updated_since' => $since, 'after_id' => 0, 'limit' => 100]);
while ($url !== null) {
$page = cartixGet($url, $key);
foreach ($page['data'] as $product) {
saveProduct($product); // запись по id, артикулы заменяются целиком
}
$url = $page['links']['next'];
}
// 2. Пропавшие товары
$visible = [];
$url = $base.'/products/ids?'.http_build_query(['after_id' => 0, 'limit' => 10000]);
while ($url !== null) {
$page = cartixGet($url, $key);
array_push($visible, ...$page['data']);
$url = $page['links']['next'];
}
removeProductsExcept($visible);
// 3. Цены целиком раз в час, если их считает дополнение, которое не сообщает о правках
if (time() - strtotime($lastPricesSync) >= 3600) {
$url = $base.'/prices?'.http_build_query(['after_id' => 0, 'limit' => 200]);
while ($url !== null) {
$page = cartixGet($url, $key);
foreach ($page['data'] as $price) {
savePrice($price); // запись по sku_id
}
$url = $page['links']['next'];
}
$lastPricesSync = $startedAt;
}
$lastSync = $startedAt;