# Коды ошибок

Этот документ перечисляет все `error.code`, которые возвращают публичные маршруты `/v1` и `/checkout/v1`, с HTTP-статусом, который использует сервис, и тем, что делать клиенту. Прочитайте его, когда пишете обработку ошибок интеграции.

## Тело ошибки

Любой ответ API с ошибкой имеет одно и то же тело:

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

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

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

Описание OpenAPI, `GET /v1/openapi.json`, объявляет один список кодов для всех маршрутов сервиса, включая внутренние. Публичный маршрут возвращает только коды из перечня ниже.

Два вида ответов такого тела не имеют:

- HTTPS-прокси отвечает на путь вне `/v1/` кодом `404` с пустым телом;
- во время остановки API отвечает `503` с телом в другом формате.

Ответ, тело которого не в формате выше, обрабатывайте по HTTP-статусу.

## Как обрабатывать ошибки

- **`400`, `413`, `415`** — запрос неверный. Исправьте его: тот же запрос получит ту же ошибку.
- **`401`** — проверьте ключ и имя заголовка.
- **`403`** — мерчант отключён или не одобрен. Запросы будут отклоняться, пока это не изменит владелец или оператор.
- **`404`** — проверьте путь и идентификатор.
- **`409`** — запрос конфликтует с существующим объектом: ключ идемпотентности (`order_id`, `reference`) использован повторно с другими данными, или выбор токена на странице оплаты уже сделан либо уже невозможен. Не повторяйте тот же запрос.
- **`422`** — запрос корректен, но его отклоняет бизнес-правило; код называет правило. Тот же запрос получит ту же ошибку, пока не изменится настройка, стоящая за правилом.
- **`429`** — повторите через число секунд из заголовка `retry-after`.
- **`500`, `503`** — проблема сервиса. Повторите позже с растущей задержкой и тем же ключом идемпотентности: `order_id` для инвойса, `reference` для выплаты. Повтор не создаёт второй объект.
- **Кода нет в перечне** — обработайте ответ по классу HTTP-статуса: `4xx` — ошибка в запросе, `5xx` — проблема сервиса.

## Перечень

| HTTP | `error.code` | Маршруты | Когда | Что делать клиенту |
| --- | --- | --- | --- | --- |
| 400 | `validation_failed` | все | Тело, путь или параметры не проходят проверку: поле отсутствует, лишнее, `null` или неверного формата. Или не проходит более поздняя проверка поля: формат или число знаков суммы, точность цены, `external_user_id` в режиме `per_user` | Исправить поле, названное в `details` или в начале `message` |
| 400, 413, 415 | `bad_request` | маршруты с телом запроса | Тело — не JSON или пустое при `Content-Type: application/json` (400), больше 16 КиБ (413) или с неподдерживаемым `Content-Type` (415) | Отправлять JSON-объект с `Content-Type: application/json` |
| 401 | `unauthorized` | `/v1` | Нет заголовка `X-Api-Key`, либо ключ неверного формата, неизвестен, отозван или его секрет не совпадает | Проверить ключ. Причина намеренно не уточняется — см. [аутентификацию](https://stons.io/ru/docs/authentication.md) |
| 403 | `merchant_disabled` | `/v1` | Мерчант отключён — оператором или владельцем в кабинете | Включить проект в кабинете. Если проект выключил оператор, включить его может только оператор |
| 403 | `merchant_not_approved` | `POST /v1/invoices`, `POST /v1/addresses`, `POST /checkout/v1/invoices/{id}/select` | Мерчант ещё не одобрен после проверки или отклонён | Дождаться проверки. Причину отклонения владелец видит в кабинете и может исправить заявку и отправить её снова. Повтор с `order_id` существующего инвойса возвращает этот инвойс |
| 404 | `route_not_found` | любой путь под `/v1` или `/checkout/v1` | Нет маршрута с таким методом и путём | Проверить метод и путь |
| 404 | `invoice_not_found` | `GET /v1/invoices/{id}`, `GET /checkout/v1/invoices/{id}`, `POST /checkout/v1/invoices/{id}/select` | Инвойса нет или он принадлежит другому мерчанту. На маршрутах `/checkout/v1` ещё: мерчант отключён | Проверить идентификатор |
| 404 | `payout_not_found` | `GET /v1/payouts/{id}` | Выплаты нет или она принадлежит другому мерчанту | Проверить идентификатор |
| 404 | `deposit_not_found` | `GET /v1/deposits/{tx_hash}` | В транзакции нет перевода на адрес мерчанта: сервис её не знает, она шла только другим мерчантам или её блок ещё не просканирован | Проверить хэш. Для только что отправленной транзакции повторить позже — см. [проверку перевода](https://stons.io/ru/docs/addresses.md#проверить-перевод) |
| 409 | `address_mode_mismatch` | `POST /v1/addresses` | Мерчант работает в режиме `per_invoice` | Брать адрес из инвойса — см. [режимы](https://stons.io/ru/docs/addresses.md#режимы) |
| 409 | `order_id_conflict` | `POST /v1/invoices` | Инвойс с этим `order_id` есть с другим активом, суммой, `currencies`, `external_user_id`, `return_url` или `cancel_url` | Проверить, не используется ли `order_id` повторно для другого заказа, — см. [идемпотентность](https://stons.io/ru/docs/invoices.md#идемпотентность) |
| 409 | `reference_conflict` | `POST /v1/payouts` | Выплата с этим `reference` есть с другим активом, адресом или суммой | Проверить, не используется ли `reference` повторно для другой выплаты |
| 409 | `asset_already_selected` | `POST /checkout/v1/invoices/{id}/select` | У инвойса уже другой актив. Выбор окончательный | Показать покупателю инвойс с его токеном |
| 409 | `invoice_expired` | `POST /checkout/v1/invoices/{id}/select` | Дедлайн инвойса прошёл или инвойс уже не в статусе `created` | Показать покупателю состояние инвойса |
| 422 | `address_mode_disabled` | `POST /v1/addresses`, `POST /v1/invoices`, `POST /checkout/v1/invoices/{id}/select` | Платформа временно приостановила режим адресов мерчанта | Сообщить покупателю, что оплата сейчас недоступна, и повторить позже. Повтор с `order_id` существующего инвойса возвращает этот инвойс; переводы на уже выданные адреса зачисляются как обычно — см. [режим на паузе](https://stons.io/ru/docs/addresses.md#режим-на-паузе) |
| 422 | `mode_not_allowed` | `POST /v1/addresses`, `POST /v1/invoices`, `POST /checkout/v1/invoices/{id}/select` | Режим адресов этого проекта ему не разрешён: оператор разрешает режим по проектам (ADR-0066) | Обратиться к оператору. Переводы на уже выданные адреса зачисляются как обычно |
| 422 | `asset_not_supported` | `POST /v1/invoices`, `POST /checkout/v1/invoices/{id}/select` | Актив неизвестен или не принимается к оплате | Использовать актив из [таблицы активов](https://stons.io/ru/docs/invoices.md#активы), который оплачивает инвойсы |
| 422 | `asset_not_payable` | `POST /v1/payouts` | В этом активе сервис не делает выплат: он неизвестен, неактивен или только для приёма | Использовать актив из [таблицы активов](https://stons.io/ru/docs/invoices.md#активы), в котором есть выплаты |
| 422 | `asset_payments_disabled` | `POST /v1/invoices`, `POST /checkout/v1/invoices/{id}/select` | Оператор временно выключил приём в активе | Предложить другой актив или повторить позже. Повтор с `order_id` существующего инвойса возвращает этот инвойс |
| 422 | `asset_disabled_by_merchant` | `POST /v1/invoices`, `POST /checkout/v1/invoices/{id}/select` | Приём в активе выключен в настройках проекта | Предложить другой актив или включить токен в разделе проекта «Токены» в кабинете. Повтор с `order_id` существующего инвойса возвращает этот инвойс |
| 422 | `asset_payouts_disabled` | `POST /v1/payouts` | Оператор временно выключил выплаты в активе | Повторить позже с тем же `reference`. Выплаты, созданные раньше, исполняются как обычно |
| 422 | `amount_below_minimum` | `POST /v1/invoices`, `POST /checkout/v1/invoices/{id}/select` | Сумма или цена меньше минимального депозита актива | Увеличить сумму или выбрать другой актив |
| 422 | `asset_required` | `POST /v1/invoices` | Нет `asset`, а в установке нет страницы оплаты, где покупатель выбрал бы токен | Передать `asset` |
| 422 | `no_payable_assets` | `POST /v1/invoices` | Нет ни `asset`, ни `currencies`, а мерчант сейчас не принимает ни одного актива, у которого есть курс | Включить токен в настройках проекта или передать `asset` |
| 422 | `asset_not_offered` | `POST /v1/invoices`, `POST /checkout/v1/invoices/{id}/select` | Актив нельзя предложить для цены в долларах США: у сервиса нет для него курса; на маршруте выбора ещё: его нет в `currencies` инвойса | Выбрать другой актив |
| 422 | `redirect_url_not_allowed` | `POST /v1/invoices` | `return_url` или `cancel_url` не на сайте мерчанта | Использовать адрес на сайте проекта или на сайте адреса проекта того же вида — см. [адреса возврата](https://stons.io/ru/docs/checkout.md#адреса-возврата) |
| 422 | `address_not_whitelisted` | `POST /v1/payouts` | Адреса нет в вайтлисте мерчанта по этому активу, или задержка перед его первым использованием ещё не прошла. Сообщение называет, какой из двух случаев | См. [куда можно платить](https://stons.io/ru/docs/payouts.md#куда-можно-платить). Ответ не зависит от суммы |
| 429 | `rate_limited` | `/v1`, `/checkout/v1` | Превышен лимит запросов | Повторить через число секунд из заголовка `retry-after` — см. [лимиты запросов](https://stons.io/ru/docs/authentication.md#лимиты-запросов) |
| 500 | `internal_error` | все | Необработанная ошибка сервиса. Подробности пишутся в лог сервиса, а не в ответ | Повторить позже с тем же ключом идемпотентности. При повторении сообщить `X-Request-Id` |
| 503 | `service_unavailable` | `POST /v1/addresses`, `POST /v1/invoices`, `POST /checkout/v1/invoices/{id}/select` | Сервис не может вывести новый адрес в текущей конфигурации; сообщение называет причину | Повторить позже. Если не проходит, обратиться к оператору |
| 503 | `addresses_exhausted` | `POST /v1/addresses`, `POST /v1/invoices`, `POST /checkout/v1/invoices/{id}/select` | Закончились индексы деривации адресов: заняты все 2^31. Практически недостижимо | Обратиться к оператору |
| 503 | `network_fee_quote_unavailable` | `POST /v1/invoices` | У проекта осталась прежняя настройка «комиссию сети платит покупатель», поэтому новый инвойс не создаётся. Повтор с `order_id` существующего инвойса возвращает этот инвойс | Владелец сохраняет «Кто платит комиссии» в настройках проекта в кабинете; затем повторить |
| 503 | `rate_unavailable` | `POST /v1/invoices`, `POST /checkout/v1/invoices/{id}/select` | Сейчас нет свежего курса: при создании инвойса без `asset` — ни у одного разрешённого токена, при выборе — у монеты выбранного актива. Ничего не создаётся и не записывается. Повтор с `order_id` существующего инвойса возвращает этот инвойс | Повторить позже с растущей задержкой и тем же `order_id`. На странице оплаты — предложить другой токен или попросить покупателя попробовать позже. Если не проходит, обратиться к оператору |
| 503 | `payouts_halted` | `POST /v1/payouts` | Выплаты остановлены — по этому активу или целиком; сообщение говорит, какой из двух случаев. Причина не раскрывается | Повторить позже с тем же `reference`. Повтор существующего `reference` возвращает эту выплату и во время остановки |

## Примеры

Несуществующий путь:

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

`404`:

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

Запрос к `POST /v1/invoices` с `"expires_in": 60` не проходит проверку по схеме, и `details` перечисляет все проблемные поля:

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

Запрос с `"asset": "USDT_TRON"` и `"amount": "49.9000001"` проходит проверку по схеме, но не проходит проверку числа знаков, поэтому `details` нет:

```json
{
  "error": {
    "code": "validation_failed",
    "message": "amount: amount has more than 6 digits after the decimal point (USDT_TRON has 6 decimals)"
  }
}
```
