# Ошибки

Модель, не знающая формы ошибки, не умеет её обработать — и человек тоже.
Поэтому ошибки описаны наравне с успехом, а не в примечании.

Эта страница — одна из трёх в разделе, которые описаны **по-настоящему
и не изменятся**: форма ошибки задана целиком, и реализация её не поменяет.

## Код отвечает за результат, тело — за детали

Никаких `200 OK` с `{"success": false}` внутри. Если запрос не выполнен, это видно
по коду HTTP, а не по разбору тела.

```json
{
  "error": {
    "type": "validation_error",
    "code": "invalid_field",
    "message": "Поле email имеет неверный формат",
    "field": "email",
    "doc_url": "https://docs-school.sersidteh.ru/api/errors/",
    "request_id": "req_01HQZXA7M3PK9E"
  }
}
```

| Поле | Что это |
|---|---|
| `type` | Класс ошибки. По нему ветвится обработчик |
| `code` | Конкретная ошибка внутри класса |
| `message` | Текст на языке школы. **Для человека, а не для условия в коде** |
| `field` | Поле, вызвавшее ошибку валидации |
| `doc_url` | Ссылка на объяснение |
| `request_id` | Идентификатор запроса. Он же в заголовке `X-Request-Id` |

Ветвиться в коде надо по `type` и `code`, а не по `message`: текст сообщения
переводится и уточняется, коды — нет.

## Полный список кодов

| Код | Когда | Что делать |
|---|---|---|
| `400 bad_request` | Синтаксис запроса неверен | Чинить запрос. Повтор без правки бессмыслен |
| `401 unauthorized` | Токен отсутствует, недействителен или отозван | Проверить заголовок и не отозван ли ключ. Повтор бессмыслен |
| `403 forbidden` | Токену не хватает права | В ответе сказано, какого именно. Выдать право или использовать другой ключ |
| `404 not_found` | Объекта нет **или он вне видимости токена** | Различить это снаружи нельзя намеренно: иначе перебором можно узнать, какие объекты существуют |
| `409 conflict` | Конфликт состояния: заказ уже оплачен, доступ уже выдан | Прочитать текущее состояние объекта и решить. Повтор даст тот же ответ |
| `422 unprocessable` | Синтаксис верен, бизнес-правило нарушено | Читать `code`: причина в нём |
| `429 rate_limited` | Превышен лимит | Ждать столько, сколько сказано в `Retry-After`, и повторить |
| `500 internal_error` | Наша ошибка | Повторить позже. `request_id` обязателен в ответе — с ним и приходить |
| `503 unavailable` | Плановые работы или перегрузка | Повторить позже, уважая `Retry-After`, если он есть |

**Разница между 400 и 422** стоит того, чтобы её знать. `400` — запрос не разобрался:
не тот тип, сломанный JSON, отсутствует обязательное поле. `422` — разобрался и не
прошёл по правилу: заказ уже отменён, доступ уже отозван, сумма возврата больше
платежа. Первое чинится в коде клиента, второе — решением, что делать дальше.

## Ошибок может быть несколько

Возвращать по одной ошибке валидации и заставлять клиента исправлять их по очереди —
плохой тон: за пять полей клиент сделает пять запросов и получит пять отказов.

Поэтому у ошибок валидации в теле массив:

```json
{
  "errors": [
    {
      "type": "validation_error",
      "code": "invalid_field",
      "message": "Поле email имеет неверный формат",
      "field": "email",
      "request_id": "req_01HQZXA7M3PK9E"
    },
    {
      "type": "validation_error",
      "code": "required",
      "message": "Поле first_name обязательно",
      "field": "first_name",
      "request_id": "req_01HQZXA7M3PK9E"
    }
  ]
}
```

`request_id` одинаковый: это один запрос с двумя претензиями к нему.

## Идентификатор запроса

`request_id` есть **в каждом ответе**, включая успешные, в заголовке `X-Request-Id`.
По нему в журнале находится полная трассировка запроса.

Практически: логируйте его на своей стороне рядом с каждым вызовом. Именно это
превращает разбор случая «в четверг у клиента не выдался доступ» из гадания в работу —
без него остаётся сравнивать время и надеяться.

## Что вернётся в четырёх частых случаях

**Прав не хватило.** `403`, и в ответе сказано, какого права не хватило. Не «доступ
запрещён» вообще, а конкретика: иначе интегратор перебирает права наугад.

**Объект удалён.** `404`. Мягко удалённый объект от несуществующего снаружи
неотличим — и это намеренно.

**Превышен лимит.** `429` и заголовок `Retry-After` в секундах. Клиент обязан его
уважать; в наших клиентских библиотеках это встроено. Игнорирование `Retry-After`
и повтор в цикле — самый быстрый способ получить более жёсткий лимит.

**Тело не прошло проверку.** `400` или `422` — см. разницу выше, — и, если ошибок
несколько, все сразу.

## Обработчик на клиенте

Минимум, который стоит написать один раз:

```bash
# Повторять можно: 429, 500, 503 — с задержкой из Retry-After или с ростом.
# Повторять бессмысленно: 400, 401, 403, 404, 409, 422 — ответ не изменится.
```

Разделение простое: повторять имеет смысл там, где виноваты нагрузка или мы.
Там, где виноват запрос, повтор даст тот же ответ и потратит лимит.
