# Коды ошибок

Любой ответ API с кодом `4xx` или `5xx` имеет одно и то же тело:

```json
{ "error": { "code": "route_not_found", "message": "Route not found" } }
```

| Поле | Тип | Смысл |
| --- | --- | --- |
| `error.code` | string | Машиночитаемый код из таблицы ниже. Коды не переименовываются и не удаляются; новые появляются в [журнале изменений](https://stons.io/docs/changelog.md) |
| `error.message` | string | Пояснение для человека на английском. Текст может меняться: не разбирайте его в коде |
| `error.details` | array | Только у `validation_failed`: список проблем, у каждой `path` — какое поле — и `message` |

Каждый ответ, включая ошибки, содержит заголовок `X-Request-Id`. Сообщайте его при обращении в поддержку: по нему находится запрос в логах. Если клиент сам передал `X-Request-Id` из латинских букв, цифр и символов `.`, `_`, `:`, `-` длиной до 128 символов, сервис использует его, иначе генерирует UUID.

## Перечень

| HTTP | `error.code` | Когда возникает | Что делать клиенту |
| --- | --- | --- | --- |
| 400 | `validation_failed` | Тело, путь или параметры запроса не проходят проверку: поле отсутствует, лишнее или неверного формата. `details` перечисляет проблемы | Исправить запрос. Повтор без изменений даст ту же ошибку |
| 400–499 | `bad_request` | Запрос некорректен на уровне HTTP: неверный JSON, неподдерживаемый `Content-Type`, тело больше 16 КиБ. HTTP-код ответа уточняет причину | Исправить запрос |
| 401 | `unauthorized` | Нет заголовка `X-Api-Key`, ключ неверный, неизвестный или отозванный. В административном API и API кабинета — нет своего токена или он неверный | Проверить ключ. Причина намеренно не уточняется |
| 403 | `merchant_disabled` | Мерчант выключен — оператором или владельцем в кабинете | Включить проект в кабинете или обратиться к оператору |
| 403 | `merchant_not_approved` | Проект ещё не одобрен после проверки или отклонён: `POST /v1/invoices` и `POST /v1/addresses` не принимают платежей. Ключи работают, чтение инвойсов тоже | Дождаться проверки; причину отклонения владелец видит в кабинете. Повтор с `order_id` уже созданного инвойса возвращает этот инвойс |
| 404 | `route_not_found` | Нет маршрута с таким методом и путём | Проверить метод и путь по документации |
| 404 | `invoice_not_found` | Инвойса нет или он принадлежит другому мерчанту | Проверить идентификатор |
| 404 | `payout_not_found` | Выплаты нет или она принадлежит другому мерчанту | Проверить идентификатор |
| 404 | `merchant_not_found` | Только административный API и API кабинета: мерчанта нет, а в API кабинета — или он принадлежит другой учётной записи | Проверить идентификатор |
| 404 | `api_key_not_found` | Только административный API и API кабинета: у мерчанта нет ключа с таким `key_id` | Проверить идентификатор ключа и мерчанта |
| 404 | `delivery_not_found` | Только административный API и API кабинета: доставки вебхука нет или она принадлежит проекту другой учётной записи | Проверить идентификатор |
| 404 | `asset_not_found` | Только административный API: актива нет в каталоге | Проверить идентификатор актива |
| 409 | `delivery_not_failed` | Только административный API: повторно отправляется только доставка в состоянии `failed` | Дождаться окончания попыток; доставленное событие повторно не отправляется |
| 409 | `asset_flag_locked` | Только административный API: каталог активов не разрешает операцию, которую пытаются переключить | Переключать можно только то, что разрешает каталог |
| 409 | `address_mode_mismatch` | Операция не подходит к режиму адресации мерчанта, например постоянный адрес для мерчанта в режиме `per_invoice` | Использовать операции своего режима |
| 409 | `order_id_conflict` | Инвойс с этим `order_id` уже существует для другой суммы, актива или пользователя | Проверить, не используется ли `order_id` повторно для другого заказа |
| 409 | `asset_already_selected` | Только маршрут страницы оплаты: у инвойса уже выбран другой актив. Выбор окончательный | Показать покупателю инвойс с выбранным токеном |
| 409 | `invoice_expired` | Только маршрут страницы оплаты: срок инвойса вышел, выбрать токен уже нельзя | Показать покупателю исход инвойса |
| 409 | `reference_conflict` | Выплата с этим `reference` уже существует для другой суммы, актива или адреса | Проверить, не используется ли `reference` повторно для другой выплаты |
| 422 | `asset_required` | Инвойс без `asset` — в `POST /v1/invoices` или в счёте кабинета, — а страницы оплаты у сервиса нет: выбрать токен покупателю негде | Передать `asset` |
| 422 | `no_payable_assets` | Инвойс без `asset` и без `currencies`, а проект сейчас не принимает ни одного токена, привязанного к доллару | Включить токен в настройках проекта или передать `asset` |
| 422 | `asset_not_offered` | Актив нельзя предложить для цены в долларах: он не привязан к доллару, его знаки не вмещают цену или — на маршруте страницы оплаты — его нет в списке инвойса | Выбрать другой актив |
| 422 | `asset_not_supported` | Актив неизвестен или не принимается к оплате. В административном API — фиксированная сумма приёма задаётся такому активу | Выбрать актив из списка `assets` |
| 422 | `asset_not_payable` | В этом активе сервис не делает выплат: он неизвестен, отключён или предназначен только для приёма. В административном API — комиссия сети задаётся такому активу | Выбрать актив, в котором сервис платит |
| 422 | `asset_payments_disabled` | Оператор временно выключил приём в этом активе | Повторить позже или предложить пользователю другой актив из списка `assets`. Повтор с `order_id` уже созданного инвойса возвращает этот инвойс |
| 422 | `asset_disabled_by_merchant` | Приём в этом активе выключен в настройках проекта | Предложить пользователю другой актив из списка `assets` или включить актив в кабинете. Повтор с `order_id` уже созданного инвойса возвращает этот инвойс |
| 422 | `asset_payouts_disabled` | Оператор временно выключил выплаты в этом активе | Повторить позже с тем же `reference` или обратиться к оператору. Выплаты, созданные раньше, исполняются как обычно |
| 422 | `amount_below_minimum` | Сумма меньше минимального депозита актива | Увеличить сумму |
| 422 | `network_fee_payer_not_supported` | Только API кабинета: `network_fee_payer` = `customer`. Отдельной комиссии сети у счёта нет: её затраты входят в комиссию сервиса, и покупатель её не несёт. Запрос отклоняется целиком, ничего не записывается | Оставить `merchant` |
| 422 | `service_fee_confirmation_required` | Только административный API: ставка комиссии сервиса выше 3% без `confirm_high_rate: true` | Проверить ставку и повторить с подтверждением |
| 422 | `service_fee_valid_from_in_past` | Только административный API: момент начала действия ставки уже прошёл | Указать момент в будущем или не указывать его — ставка начнёт действовать сразу |
| 422 | `payout_fee_confirmation_required` | Только административный API: у комиссии за выплату сумма больше 5 единиц актива без `confirm_high_amount: true` или процент выше 3% без `confirm_high_rate: true`. Сообщение называет, какое из чисел | Проверить сумму и процент и повторить с нужным подтверждением |
| 422 | `payout_fee_valid_from_in_past` | Только административный API: момент начала действия комиссии за выплату уже прошёл | Указать момент в будущем или не указывать его — комиссия начнёт действовать сразу |
| 422 | `invoice_fee_confirmation_required` | Только административный API: фиксированная сумма приёма больше 5 единиц актива без `confirm_high_amount: true` | Проверить сумму и повторить с подтверждением |
| 422 | `invoice_fee_valid_from_in_past` | Только административный API: момент начала действия фиксированной суммы приёма уже прошёл | Указать момент в будущем или не указывать его — сумма начнёт действовать сразу |
| 422 | `redirect_url_not_allowed` | Origin `return_url` или `cancel_url` инвойса (на `t.me` и `telegram.me` — и имя бота или канала) не совпадает ни с адресом проекта того же вида, ни с сайтом проекта из заявки, либо у проекта нет ни того, ни другого | Использовать адрес на одном из этих сайтов или задать адрес в настройках проекта — см. [страницу оплаты](https://stons.io/docs/checkout.md#адреса-возврата) |
| 422 | `address_not_whitelisted` | Адреса нет в вайтлисте мерчанта по этому активу, либо задержка перед его первым использованием ещё не прошла. Сообщение называет, какой из двух случаев | Добавить адрес через оператора и дождаться окончания задержки. Ответ не зависит от суммы: без вайтлиста выплата невозможна |
| 429 | `rate_limited` | Превышен лимит запросов по ключу или по IP-адресу | Повторить через время из заголовка `Retry-After` |
| 500 | `internal_error` | Необработанная ошибка сервиса. Подробности пишутся в лог сервиса и в ответ не попадают | Повторить позже с тем же ключом идемпотентности; при повторении сообщить `X-Request-Id` |
| 503 | `service_unavailable` | Сервис временно не может обслуживать запросы: например, недоступна база данных | Повторить позже с экспоненциальной задержкой |
| 503 | `payouts_halted` | Выплаты остановлены — по этому активу или целиком. Причина остановки наружу не раскрывается | Повторить позже с тем же `reference`: повтор не создаёт вторую выплату. Если остановка длится, обратиться к оператору |
| 503 | `addresses_exhausted` | Закончились индексы деривации адресов. Практически недостижимо | Сообщить оператору |
| 503 | `network_fee_quote_unavailable` | У проекта осталась прежняя настройка «комиссию сети платит покупатель», а такой комиссии у инвойса нет: инвойс не создаётся. Повтор с `order_id` уже созданного инвойса возвращает этот инвойс | Сохранить в кабинете настройки «Кто платит комиссии» или обратиться к оператору |

Если клиент получил код, которого нет в таблице, он должен обработать ответ по классу HTTP-статуса: `4xx` — ошибка в запросе, `5xx` — временная проблема сервиса.

## Примеры

```sh
curl -s https://api.stons.io/v1/unknown
```

```json
{"error":{"code":"route_not_found","message":"Route not found"}}
```

Ошибка проверки перечисляет все проблемные поля:

```json
{
  "error": {
    "code": "validation_failed",
    "message": "Request validation failed",
    "details": [{ "path": "body.expires_in", "message": "Too small: expected number to be >=300" }]
  }
}
```
