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

Ошибки и их коды

Формат ответа об ошибке, каждый код с причиной и тем, что делать. Предел запросов, Retry-After и закрытый магазин.

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

Магазин сообщает об ошибке честным HTTP-статусом. Ответы 200 и 201 (у создания заказа) всегда означают успех, и искать признак ошибки внутри «успешного» ответа не нужно. Тело ошибки у всех адресов одинаковое, по полю code программа понимает, что случилось.

Формат ошибки

Ошибка приходит с заголовком Content-Type: application/problem+json в формате RFC 9457 (Problem Details):

{
    "type": "https://cartixx.com/docs/api/errors#missing-scope",
    "title": "Доступ запрещён",
    "status": 403,
    "detail": "У ключа нет права «shop:read». Добавить его может владелец магазина в настройках ключа.",
    "code": "missing_scope",
    "request_id": "C3TOWCVB",
    "scope": "shop:read"
}
Поле Что значит
type адрес описания этой ошибки на этой странице. Подчёркивание из кода в адресе заменено дефисом: #missing-scope. У кодов http_<статус> адрес ведёт на эту страницу целиком
title короткое название ошибки по HTTP-статусу, на языке запроса. Если у статуса своего названия нет, здесь повторяется code, например http_413
status HTTP-статус, тот же, что у ответа
detail объяснение для человека на языке запроса: что не так и что сделать
code код ошибки. Программа должна опираться на него, а не на текст
request_id код запроса, тот же, что в заголовке X-Request-Id и в журнале магазина

У некоторых ошибок есть дополнительные поля: errors у ошибки проверки данных, scope у ошибки нехватки права, problems у отказа принять заказ. Они описаны у своих кодов.

Тексты title и detail приходят на языке, выбранном по ?locale= или Accept-Language (см. «Запросы и ответы»). Текст может меняться от выпуска к выпуску, а code остаётся постоянным.

Все коды

HTTP code Когда возникает
400 bad_json тело запроса на заказ не объект JSON
400 bad_idempotency_key заголовок Idempotency-Key неверного вида
401 unauthenticated ключ не передан, передан не целиком или такого ключа нет: его заменили новым, передали другому контакту или удалили
401 key_revoked владелец отозвал ключ или истёк срок ключа
403 ip_not_allowed запрос пришёл с адреса, которого нет в списке разрешённых у ключа
403 missing_scope у ключа нет права, которое нужно этому адресу
403 forbidden действие ключу недоступно по другой причине
404 not_found такого адреса в API нет или запись не найдена
405 method_not_allowed адрес есть, но не для этого метода
409 conflict запрос противоречит текущему состоянию данных
409 idempotency_in_progress заказ с этим Idempotency-Key ещё оформляется
409 idempotency_order_hidden заказ по этому Idempotency-Key создан, но ключ API с тех пор привязан к другому контакту
409 external_id_taken заказ с этим external_id уже принят
409 external_id_in_progress заказ с этим external_id ещё оформляется
409 total_mismatch итог заказа не совпал с expected_total
409 order_busy магазин в эту секунду оформляет другие заказы с теми же товарами
422 validation_failed параметры или данные запроса не прошли проверку
422 cannot_place заказ нельзя собрать ровно так, как его прислали
422 idempotency_key_reused с этим Idempotency-Key уже пришёл заказ с другим телом
423 shop_locked магазин закрыт, потому что подписка не активна
428 idempotency_key_required в запросе на создание заказа нет заголовка Idempotency-Key
429 rate_limited превышен предел запросов ключа или предел неудачных входов с адреса
500 server_error ошибка внутри магазина
503 not_installed магазин ещё не установлен
503 unavailable магазин временно недоступен, например закрыт на обслуживание
другой http_<статус> редкие ответы HTTP, у которых нет своего кода, например http_413

В каком порядке проверяется запрос

Если в запросе несколько проблем, ответ называет первую по порядку:

  1. Магазин доступен: не закрыт на обслуживание (unavailable), установлен (not_installed) и не закрыт из-за подписки (shop_locked).
  2. Ключ передан и действует (unauthenticated, key_revoked), запрос пришёл с разрешённого адреса (ip_not_allowed).
  3. Не превышен предел запросов ключа (rate_limited).
  4. Текст в теле запроса записан в UTF-8 (validation_failed). Тело проверяется у всех запросов, кроме GET и HEAD.
  5. Адрес и метод существуют (not_found, method_not_allowed).
  6. У ключа есть нужное право (missing_scope).
  7. Параметры запроса верны (validation_failed). Адреса каталога, цен, остатков и заказов сначала ищут неизвестные параметры, а затем проверяют значения остальных.

Поэтому запрос без ключа к несуществующему адресу получит 401, а не 404, а закрытый магазин ответит 423 даже на запрос без ключа.

Дальше адреса заказов проверяют ключ повтора, тело и сам заказ. Этот порядок описан на странице «Заказы: адреса и поля».

Коды по порядку

bad_json

400. Тело запроса POST /orders или POST /orders/check не объект JSON: непустой список, строка, данные формы или JSON с ошибкой разметки. Отправьте заказ объектом, например {"items": [...]}. Пустое тело и пустой список [] ошибкой не считаются: магазин проверит их как пустой объект и ответит validation_failed с ошибкой в поле items: позиций нет.

bad_idempotency_key

400. Заголовок Idempotency-Key длиннее 255 знаков или в нём есть знаки вне печатных ASCII: кириллица, перевод строки, табуляция. Используйте латиницу, цифры и знаки препинания, удобнее всего UUID. Подробнее о ключе повтора на странице «Заказы партнёра».

unauthenticated

401. Ключа нет в заголовке Authorization, он скопирован не целиком или такого ключа в магазине нет. В ответе есть заголовок WWW-Authenticate: Bearer.

Проверьте, что заголовок выглядит как Authorization: Bearer 12|ctx_… и ключ передан вместе с номером и вертикальной чертой.

Этот же ответ получает прежний ключ после того, как владелец нажал «Выдать новый ключ» или «Выдать ключ снова» либо передал ключ другому контакту. Прежний ключ перестаёт работать в ту же секунду, и магазин его больше не узнаёт. Так же отвечает ключ, который владелец удалил, и ключ контакта, которого удалили из магазина: вместе с контактом удаляются и его ключи.

Сеанс сотрудника в панели API не открывает. Запрос из браузера, где выполнен вход в панель, но без ключа, тоже получит этот ответ.

key_revoked

401. Магазин узнал ключ, но ключ больше не работает: владелец его отозвал или прошёл срок, указанный в поле «Действует до». В ответе есть заголовок WWW-Authenticate: Bearer. Попросите владельца магазина выдать ключ снова. Работать будет новый ключ, который он вам передаст, а прежний так и останется нерабочим.

Так же отвечает ключ, у которого не выбран контакт. Попросите владельца выбрать контакт в карточке ключа и передать вам новый ключ.

ip_not_allowed

403. У ключа задан список разрешённых IP-адресов, и адреса, с которого пришёл запрос, в нём нет. Сообщите владельцу адрес своего сервера, чтобы он добавил его в ограничения ключа. Если сервер выходит в интернет через разные адреса, удобнее указать подсеть, например 203.0.113.0/24.

missing_scope

403. Ключу не отмечено право, которое нужно этому адресу. Код права приходит в дополнительном поле scope:

{ "code": "missing_scope", "scope": "shop:read" }

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

forbidden

403. Действие ключу недоступно по причине, у которой нет отдельного кода. Причина написана в detail.

not_found

404. Такого адреса в API нет или запрошенной записи нет в магазине. Ответ приходит в формате API, а не страницей витрины. Неопубликованного товара для API нет, поэтому /products/{id} черновика, скрытого товара или товара в архиве тоже отвечает 404. Сверьте адрес со справочником адресов: слова в адресах разделяются подчёркиванием (order_statuses), а не дефисом.

method_not_allowed

405. Адрес есть, но принимает другой метод. Допустимые методы перечислены в заголовке ответа Allow, например Allow: GET, HEAD. Все адреса API принимают GET, кроме /orders/check, который принимает только POST. Адрес /orders принимает и GET, и POST.

conflict

409. Запрос противоречит текущему состоянию данных в магазине. Подробность в detail. Сейчас ни один адрес не отвечает этим кодом. Адреса заказов отвечают 409 со своими кодами, они описаны ниже.

idempotency_in_progress

409. Запрос POST /orders с этим Idempotency-Key уже пришёл и ещё оформляется. Так бывает, если программа повторила запрос, не дождавшись ответа на первый. В ответе есть заголовок Retry-After: 2. Повторите тот же запрос с тем же ключом через две секунды: если первый запрос создал заказ, магазин отдаст его.

Если первый запрос оборвался на стороне магазина и так и не завершился, через 10 минут ключ освобождается, и повтор оформит заказ заново.

idempotency_order_hidden

409. Заказ по этому Idempotency-Key уже создан, но потом владелец привязал ключ API к другому контакту. При смене контакта магазин выдаёт новый ключ, а прежний отвечает 401. Новому ключу заказ прежнего контакта не виден, и магазин его не отдаёт. Не отправляйте заказ ещё раз с новым ключом повтора, иначе в магазине появится второй такой же. Спросите у владельца, что с ним делать.

external_id_taken

409. Заказ с таким external_id уже принят по этому ключу. Ответ называет ваш номер в поле external_id, а номер заказа в магазине в поле order_id:

{ "code": "external_id_taken", "external_id": "A-1024", "order_id": 5120 }

Скорее всего, вы отправили тот же заказ второй раз. Посмотреть его можно в /orders/{order_id}. Номера принадлежат ключу API, поэтому после привязки ключа к другому контакту номер может оказаться занят заказом прежнего контакта, и тогда этот адрес ответит 404. Если владелец удалит заказ в панели, номер освободится.

external_id_in_progress

409. Заказ с таким external_id прямо сейчас оформляется другим запросом. В ответе есть заголовок Retry-After: 2 и ваш номер в поле external_id. Повторите запрос через две секунды. Если первый запрос создаст заказ, ответом будет external_id_taken, а номер этого заказа придёт в поле order_id.

total_mismatch

409. Итог заказа по расчёту магазина не совпал с expected_total из запроса, и заказ не создан. Ваш итог приходит в поле expected_total, итог магазина в поле actual_total:

{ "code": "total_mismatch", "expected_total": "400.00", "actual_total": "416.64" }

Проверьте заказ через /orders/check: могли измениться цены или скидки. Если новый итог вас устраивает, отправьте заказ с ним.

order_busy

409. Магазин в эту секунду оформлял другие заказы с теми же товарами, и оформить ваш заказ не получилось даже с третьей попытки. Заказ не создан. В ответе есть заголовок Retry-After: 2. Повторите тот же запрос с тем же Idempotency-Key через две секунды.

validation_failed

422. Параметры или данные запроса неверны. В поле errors для каждого неверного поля приходит список сообщений:

{
    "type": "https://cartixx.com/docs/api/errors#validation-failed",
    "title": "Данные не прошли проверку",
    "status": 422,
    "detail": "Данные не прошли проверку. Подробности в поле errors.",
    "code": "validation_failed",
    "request_id": "C3TOWCVB",
    "errors": {
        "limit": ["limit — целое число от 1 до 200."],
        "offset": ["offset — целое число от 0."]
    }
}

Так отвечают все списки на неверные limit и offset. Адреса каталога, цен и остатков так же отвечают на неизвестный параметр, неверный номер витрины, неверное значение фильтра, неверную дату в updated_since и after_id вместе с offset. Сообщения для каждого параметра перечислены в описаниях адресов, например у /products. Этот же ответ приходит, если в теле запроса есть текст не в кодировке UTF-8: магазин называет поле, в котором его нашёл.

Адреса заказов отвечают так на ошибки в теле заказа. Ключом в errors стоит путь к полю, например items.2.qty или items.0.product_id, а ошибки приходят все сразу. Сообщения для каждого поля перечислены на странице «Заказы: адреса и поля».

cannot_place

422. Заказ нельзя собрать ровно так, как его прислали, и он не создан. Причины приходят списком problems, у каждой есть поле запроса, код и объяснение:

{
    "code": "cannot_place",
    "problems": [
        { "field": "items.0.qty", "code": "out_of_stock", "product_id": 1045, "sku_id": 2108, "available": "2.000", "message": "На складе меньше, чем в заказе: доступно 2.000." }
    ]
}

Все причины и что с ними делать описаны в разделе «Причины отказа». Заранее те же причины показывает POST /orders/check.

idempotency_key_reused

422. С этим Idempotency-Key уже пришёл заказ, но с другим телом. Скорее всего, новому заказу по ошибке достался ключ прежнего. Дайте новому заказу новый ключ. Если же это повтор того же заказа, отправьте то же тело, что в первый раз. Что магазин считает тем же телом, описано на странице «Заказы партнёра».

Если первый запрос с этим ключом ещё оформляется, этот код не приходит: магазин отвечает idempotency_in_progress, какое бы ни было тело.

shop_locked

423. Магазин закрыт, потому что его подписка не активна. Пока магазин закрыт, API не отвечает ни на один запрос. Повторять запрос часто смысла нет: магазин откроется, когда владелец продлит подписку.

idempotency_key_required

428. В запросе POST /orders нет заголовка Idempotency-Key. Без него магазин не создаёт заказ: если ответ потеряется, повтор создал бы второй такой же заказ. Придумайте для заказа уникальную строку, например UUID, и передавайте её в этом заголовке при каждом повторе этого заказа. POST /orders/check заголовок не нужен.

rate_limited

429. Запросов слишком много. В ответе есть заголовок Retry-After с числом секунд, через которое можно повторить запрос, и это же число названо в detail.

Такой ответ бывает в двух случаях.

Превышен предел ключа. У каждого ключа свой предел запросов в минуту, по умолчанию 120. Владелец может изменить его в поле «Запросов в минуту». Сколько запросов осталось в текущей минуте, видно по заголовку X-RateLimit-Remaining каждого успешного ответа. У ответа 429 также есть X-RateLimit-Limit, X-RateLimit-Remaining: 0 и X-RateLimit-Reset (время в секундах Unix, когда счётчик обнулится).

Слишком много неудачных входов с одного адреса. Если с одного IP-адреса за минуту пришло 30 отказов во входе (unauthenticated, key_revoked, ip_not_allowed), дальнейшие неудачные запросы с этого адреса получают 429 вместо 401 или 403. У такого ответа есть только заголовок Retry-After, без X-RateLimit-*. Адреса IPv6 считаются по сети /64. Действующий ключ этот предел не задевает: даже когда с того же адреса идёт перебор чужих ключей, запросы с правильным ключом проходят.

Что делать: дождаться времени из Retry-After и повторить запрос. Не повторяйте запрос сразу в цикле, каждый такой повтор тоже расходует предел. Если предела постоянно не хватает, попросите владельца поднять его для вашего ключа.

server_error

500. Внутри магазина произошла ошибка. Подробностей в ответе нет, они записаны в журнал магазина под кодом из request_id. Сообщите этот код владельцу магазина, как описано ниже.

not_installed

503. Магазин на этом адресе ещё не установлен.

unavailable

503. Магазин временно не принимает запросы, например закрыт на обслуживание. Если в ответе есть заголовок Retry-After, повторите запрос через указанное время.

Коды http_

Редкие ответы HTTP, у которых нет своего кода, приходят с кодом из http_ и номера статуса, например http_413, если тело запроса слишком большое. Адрес в type у них ведёт на эту страницу целиком. В title обычно повторяется тот же код, а detail коротко называет причину, например «Тело запроса слишком большое.». Программе лучше опираться на HTTP-статус.

Ответ http_413 магазин даёт ещё до того, как запрос дошёл до API. Поэтому у него нет заголовка X-Request-Id, и в журнал запросов он не попадает. Поле request_id в теле есть, но найти по нему запрос в журнале не получится. Так же устроен ответ unavailable, пока магазин закрыт на обслуживание.

Как сообщить о проблеме

Передайте владельцу магазина три вещи:

  • код из поля request_id или заголовка X-Request-Id;
  • время запроса;
  • название ключа, если ключей у вас несколько.

Владелец откроет журнал запросов в разделе «API магазина» и найдёт запрос, вписав код в поле «Код обращения». У запроса с ошибкой там видно тело запроса и ответ магазина. Если ошибка 500, подробности записаны в журнал ошибок магазина в разделе «Система → Журналы», где запись находится поиском по тому же коду. Как устроен журнал запросов, описано на странице «Журнал запросов».

Обновлено 21 октября 2026