# Отказы

Все отказы одной формы. Решение принимайте по `code`; `message` написан
по-русски для человека, который будет читать ваш журнал, и может меняться.

```json
{ "ok": false, "error": {
  "code": "quota_exceeded",
  "message": "Монеты на этот период кончились (израсходовано 1000 из 1000)…",
  "details": { "reason": "out_of_coins" } } }
```

`details` есть не у всех отказов.

| Код | HTTP | Что случилось | Что делать |
|---|---|---|---|
| `unauthorized` | 401 | Нет ключа, ключ неверен или отозван | Не повторять. Проверить ключ у мебельной компании |
| `forbidden` | 403 | Рабочее место в `/handoff` не принадлежит этой учётной записи | Не повторять. Уточнить название места |
| `not_found` | 404 | Нет такого разговора, хода или ссылки; либо в разговоре ещё ничего не собрано | Не повторять вслепую |
| `invalid_request` | 400 | Тело не по договору: пустой `text`, битый `b64`, больше двух картинок | Не повторять. Чинить запрос |
| `conflict_busy` | 409 | По разговору уже идёт ход; либо ход, который просят остановить, уже кончился | Дождаться вебхука. Повторять просьбу не надо |
| `quota_exceeded` | 402 | Кончились монеты или подписка | Не повторять. Сообщить мебельной компании |
| `payload_too_large` | 413 | Картинка больше 5 МБ | Пережать картинку |
| `rate_limited` | 429 | Слишком много ходов идёт разом | Повторить через минуту |
| `png_unsupported` | 501 | Запрошен `format=png` | Взять `svg` и сконвертировать у себя |
| `internal` | 500 | Беда на нашей стороне | Повторить один раз через минуту; не прошло — написать нам |

## Повторять или нет

Повторять имеет смысл **только** `rate_limited`, `internal` и сетевые обрывы.
Всё остальное повтором не лечится: 401 не станет 200 от третьей попытки,
а 409 означает, что работа уже идёт.

Повторяя `POST /turns` после обрыва сети, **обязательно** шлите тот же
`Idempotency-Key`: иначе вы купите второй ход за те же деньги.

## Подробности у `quota_exceeded`

```json
"details": { "reason": "out_of_coins" }
```

| `reason` | Что это значит |
|---|---|
| `out_of_coins` | Монеты периода кончились; можно добрать пакетом |
| `period_over` | Период кончился, подписка не продлена |
| `canceled` | Подписка отменена |
| `no_subscription` | У учётной записи нет подписки вовсе |

Всё это чинится в кабинете мебельной компании, а не вами: вы можете только
показать, что работа встала, и кому об этом сказать.

## Отказ — не то же, что неудачный ход

Отказ приходит **синхронно**, кодом HTTP, и означает, что ход не начался.
Неудачный ход приходит **вебхуком** со `status: "failed"` и означает, что
работа шла, но не сошлась. Деньги в первом случае не тратятся вовсе,
во втором — тоже не тратятся, но время потрачено.
