> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.voodoo.center/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.voodoo.center/_mcp/server.

# Ошибки

## Конверт ошибки

Ошибки API возвращают стандартный JSON-конверт с читабельным полем `detail` и
стабильным, машиночитаемым `code`:

```json title="Ответ с ошибкой"
{
  "detail": "Insufficient balance",
  "code": "insufficient_balance"
}
```

Разветвляйте обработку ошибок по `code` (стабильному), а не по `detail` (тексту
для человека, который может меняться).

Эндпоинт **обмена токена** (`POST /api/v1/auth/token/client`) использует более
простую форму для сбоев аутентификации: `{"error": "Invalid credentials"}` при
`401`. Каждый другой эндпоинт использует конверт `detail` / `code`, приведённый выше.

## Коды статусов

| HTTP  | `code`                 | Когда                                                                                                                                                                                     |
| ----- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | `validation_error`     | Некорректное или отсутствующее поле, неизвестный/недоступный товар (в том числе отсутствие в наличии на момент создания), дублированный `merchant_order_id` или количество вне диапазона. |
| `400` | `insufficient_balance` | Баланс вашего аккаунта не покрывает заказ.                                                                                                                                                |
| `401` | `not_authenticated`    | Отсутствующий, недействительный или просроченный токен доступа. Повторно [обменяйте свой API-ключ](/authentication).                                                                      |
| `404` | `not_found`            | Заказ не существует или не принадлежит вашему аккаунту.                                                                                                                                   |
| `409` | `already_exists`       | Конфликт с существующим ресурсом.                                                                                                                                                         |
| `5xx` | —                      | Ошибка бэкенда. Повторяйте с отсрочкой; используйте `merchant_order_id`, чтобы повторные попытки оставались идемпотентными.                                                               |

### Примеры

```json title="400 — ошибка валидации"
{ "detail": "Item is not available", "code": "validation_error" }
```

```json title="400 — недостаточный баланс"
{ "detail": "Insufficient balance", "code": "insufficient_balance" }
```

```json title="401 — не аутентифицирован"
{ "detail": "Authentication credentials were not provided.", "code": "not_authenticated" }
```

```json title="404 — не найдено"
{ "detail": "Not found.", "code": "not_found" }
```

## Ошибки при создании против асинхронных сбоев

Существует два разных вида «сбоя», и они проявляются в разных местах:

* **Ошибки при создании (HTTP)** — запрос **отклоняется**, и ничего не
  списывается. Они возвращаются как `4xx`/`5xx` с конвертом ошибки в момент,
  когда вы вызываете `POST /api/v1/orders`. Пример: товар, которого **нет в
  наличии на момент создания**, возвращает `400 validation_error` («Item is not
  available»).

* **Асинхронные сбои (терминальный статус)** — заказ был **принят** (`201`,
  `status: "pending"`), и ваш баланс был списан, но выполнение позже дало сбой
  или было успешным лишь частично. Это **не** HTTP-ошибка. Вместо этого заказ
  завершается терминальным `status` `failed` (с `error` / `error_message`,
  например `OUT_OF_STOCK`, и полным `refund_amount`) или `partial` (с
  `refund_amount` за недоставленные единицы). Вы узнаёте об этом из своего
  [webhook](/webhooks) или `GET /api/v1/orders/{id}`.

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

## Безопасное повторение попыток

Всегда отправляйте уникальный `merchant_order_id` в `POST /api/v1/orders`. Он
уникален в пределах аккаунта, поэтому если повторная попытка (после таймаута или
`5xx`) использует то же значение, вы случайно не разместите дублированный заказ
— дубликат отклоняется с `400 validation_error`.

Исправляйте `401`, повторно обменивая свой API-ключ на свежий токен.

Куда доставляются асинхронные результаты `failed` / `partial`.