Справочник ошибок
Тело ошибки в общем случае имеет форму {"error":"code"}. Ниже — каждый код, который сервер реально возвращает партнёрскому клиенту (internal/httpapi/server.go, internal/auth/hmac.go), с HTTP-статусом, причиной и рекомендуемым действием.
Ошибки аутентификации (до вызова бизнес-логики)
| HTTP | error | Причина | Что делать |
|---|---|---|---|
401 | unauthorized | неизвестный X-API-Key; отсутствует/пуст один из заголовков X-Timestamp/X-Signature/X-Request-ID; X-Request-ID не валидный UUID; X-Timestamp расходится с временем сервера больше чем на 60 c; подпись не совпала; либо этот X-Request-ID уже был использован этим API-ключом раньше | проверить ключ, синхронизацию времени, формат X-Request-ID, формулу подписи (см. Аутентификацию); секрет никогда не логировать |
503 | authentication_unavailable | сервис не смог проверить одноразовость X-Request-ID (временная недоступность зависимости, сам запрос ещё не принят) | безопасно повторить тот же запрос с тем же X-Request-ID позже |
413 (текст, не JSON) | — (request body too large, Content-Type: text/plain) | тело запроса больше лимита: 2 MiB для всех обычных маршрутов | уменьшить тело; это плагинский лимит для PUT /v1/plugin/streets (8 MiB) партнёра не касается |
413 — единственный случай, где ответ не в форме {"error":"code"}: это стандартный текстовый ответ net/http, разбирайте его отдельной веткой, если парсите тело ошибки как JSON.
Ошибки уровня приложения
| HTTP | error | Причина | Что делать |
|---|---|---|---|
400 | invalid_request | JSON не прошёл строгий разбор (неизвестное поле, лишнее значение в теле), не прошла валидация домена (например schemaVersion не 1, total не совпадает с суммой позиций, невалидный телефон/ID, currency не RUB), либо id в пути — не UUID | поправить запрос на своей стороне; не повторять без изменений |
403 | forbidden | точка, источник (source) или scope (organization/terminalGroup — актуально только для плагинских маршрутов) не разрешены вашему credential | не пытаться обойти scope; для партнёра — типичная причина: externalPointId не входит в список externalPoints вашего credential |
404 | not_found | заказ не существует, либо существует, но вне point-scope вашего credential (сервер намеренно отвечает так же, как на несуществующий объект — не 403 — чтобы не раскрывать чужие заказы через «оракул существования») | не считать это доказательством, что заказа не было — перепроверить externalOrderId/id и scope; гостю показывать нейтральный текст, не раскрывать, какой из двух случаев произошёл |
Исключение: GET /v1/orders/{id}?source= (эндпоинт, существовавший ещё до этого раздела спецификации) проверяет только source вашего credential, но не point-scope (AllowsPoint) — это оставлено ради байт-совместимости с уже развёрнутыми интеграциями. Практически это значит, что по UUID чужого заказа в пределах вашего source можно получить 200 вместо 404, если вы каким-то образом узнали его id. Два новых чтения, которые этот портал документирует как основные — GET /v1/orders?externalOrderId= и POST /v1/orders/{id}/cancel — уже проверяют point-scope полностью и отвечают 404 за его пределами. | 409 | idempotency_conflict | тот же externalOrderId отправлен повторно с другим содержимым (создание заказа) | остановить автоматический retry, разобраться, какое содержимое верное — см. Идемпотентность | | 409 | cancel_rejected | предыдущая попытка отмены уже была отклонена плагином (ресторан не смог отменить доставку — она перешла дальше New) | показать гостю «свяжитесь с рестораном», не повторять отмену автоматически — см. Отмену заказа | | 422 | route_mapping_not_found | для этой точки нет активного маршрута (tenantId + source + externalPointId) в delivery_route_mappings | скрыть/заблокировать точку в вашем UI до исправления маршрутизации на стороне AploDelivery | | 422 | product_mapping_not_found | у одной из позиций заказа нет активного товарного маппинга для точки | убрать блюдо без маппинга из checkout (проверяйте заранее через GET /v1/points/{id}/products, см. Быстрый старт) или сообщить об отсутствующем маппинге |
Коды, которые партнёр может увидеть только косвенно (плагинский протокол)
Эти коды принадлежат эндпоинтам /v1/plugin/*, которые партнёрский backend не вызывает напрямую — но они объясняют, почему заказ может «зависнуть» в промежуточном состоянии при поллинге:
| HTTP | error | Где возникает |
|---|---|---|
409 | invalid_or_expired_claim | плагин пытается подтвердить заказ по просроченному или неверному claimToken |
409 | invalid_state_transition | плагин пытается перевести заказ в состояние, недопустимое из текущего |
409 | stable_id_mismatch | плагин прислал ACK с другими iikoOrderId/iikoDeliveryId, чем у заказа |
500 | scope_violation | внутренняя защитная проверка (заявленный claim оказался вне scope credential) — признак бага, не пользовательская ошибка |
Общесистемные ошибки
| HTTP | error | Причина | Что делать |
|---|---|---|---|
500 | internal_error | необработанная паника на сервере (перехвачена defer-recover) | ретраить с новым X-Request-ID по стандартной backoff-политике; если повторяется — обратиться в поддержку с externalOrderId/id и временем запроса |
Общее правило
Неизвестный/незнакомый HTTP-статус или код error, которого нет в этой таблице, не считайте успехом: сохраните техническую ошибку без предположений о результате операции и, если операция не идемпотентна по своей природе (см. Идемпотентность), не повторяйте её вслепую. Go SDK (pkg/ingressclient) уже даёт типизированные ошибки ForbiddenError / NotFoundError / ConflictError / UnprocessableError для 403/404/409/422; остальные статусы приходят как HTTPError{StatusCode, Code}.