Ошибки и их коды
Формат ответа об ошибке, каждый код с причиной и тем, что делать. Предел запросов, Retry-After и закрытый магазин.
На этой странице
- Формат ошибки
- Все коды
- В каком порядке проверяется запрос
- Коды по порядку
- bad_json
- bad_idempotency_key
- unauthenticated
- key_revoked
- ip_not_allowed
- missing_scope
- forbidden
- not_found
- method_not_allowed
- conflict
- idempotency_in_progress
- idempotency_order_hidden
- external_id_taken
- external_id_in_progress
- total_mismatch
- order_busy
- validation_failed
- cannot_place
- idempotency_key_reused
- shop_locked
- idempotency_key_required
- rate_limited
- server_error
- not_installed
- unavailable
- Коды http_
- Как сообщить о проблеме
Магазин сообщает об ошибке честным 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 |
В каком порядке проверяется запрос
Если в запросе несколько проблем, ответ называет первую по порядку:
- Магазин доступен: не закрыт на обслуживание (
unavailable), установлен (not_installed) и не закрыт из-за подписки (shop_locked). - Ключ передан и действует (
unauthenticated,key_revoked), запрос пришёл с разрешённого адреса (ip_not_allowed). - Не превышен предел запросов ключа (
rate_limited). - Текст в теле запроса записан в UTF-8 (
validation_failed). Тело проверяется у всех запросов, кромеGETиHEAD. - Адрес и метод существуют (
not_found,method_not_allowed). - У ключа есть нужное право (
missing_scope). - Параметры запроса верны (
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, подробности записаны в журнал ошибок магазина в разделе «Система → Журналы», где запись находится поиском по тому же коду. Как устроен журнал запросов, описано на странице «Журнал запросов».