# Ошибки

## Как выглядит ошибка

Любая ошибка приходит с кодом ответа HTTP `4xx` или `5xx` и телом одного вида:

```json
{
  "code": "quota_exhausted",
  "message": "Квота отдела на смену 3 исчерпана (20 из 20). Добавить места может руководитель — обратитесь к нему",
  "request_id": "r8KfQ2mZx7LpT0aVn3Wd"
}
```

| Поле         | Что это                                                                                     |
| ------------ | ------------------------------------------------------------------------------------------- |
| `code`       | Машинный код. **Не меняется никогда** — по нему ваша CRM решает, что делать                  |
| `message`    | Текст для человека, по-русски. Можно показать менеджеру. **Не разбирайте его программой** — текст может меняться |
| `request_id` | Номер запроса (он же в заголовке `X-Request-Id`). Назовите его, когда пишете в лагерь: по нему найдут запрос в журнале |

## Что делать — по коду ответа

| Код ответа | Что значит                                   | Что делать                                                                     |
| ---------- | -------------------------------------------- | ------------------------------------------------------------------------------ |
| `400`      | Неверный запрос: поле, параметр, формат      | Исправить запрос. Повторять тот же запрос бесполезно                           |
| `401`      | Нет ключа или ключ недействителен            | Проверить ключ; если отозван — запросить новый у лагеря                         |
| `403`      | Нельзя: нет права или так запрещено правилами | Показать `message` менеджеру; при `scope_missing` — попросить право у лагеря    |
| `404`      | Записи нет **или она не вашего отдела**       | Проверить номер. Чужая запись неотличима от несуществующей                       |
| `405`      | Метод не поддерживается по этому адресу       | Проверить метод (`GET`/`POST`/`PATCH`/`DELETE`)                                 |
| `409`      | Конфликт с текущим состоянием (нет мест, уже есть счёт, уже отменена) | Показать `message` менеджеру; перечитать запись                 |
| `412`      | Запись изменили после вашего чтения (`If-Match`) | Перечитать запись, показать менеджеру, повторить                             |
| `413`      | Тело больше 1 МБ                              | Уменьшить запрос                                                                |
| `422`      | Тело не принято: незнакомое или неверное поле, или ключ повтора с другим телом | Исправить тело (имя поля — в `message`)                   |
| `429`      | Превышен лимит                                | Подождать `Retry-After` секунд и повторить (см. [Лимиты](/partners/limits))      |
| `500`      | Внутренняя ошибка лагеря                      | Повторить позже **с тем же `Idempotency-Key`**; если повторяется — написать в лагерь с `request_id` |
| `503`      | Сервер не ответил за 30 секунд или часть сервиса недоступна | Повторить **с тем же `Idempotency-Key`** — см. [Повтор запроса](/partners/idempotency) |

Ответы `401`, `403`, `429` и `503` возможны у любого адреса.

:::caution{title="Отказ запоминается вместе с ключом повтора"}

Если `POST` с `Idempotency-Key` получил отказ `4xx` (кроме `429`), повтор **с тем же ключом**
вернёт тот же отказ, не выполняя запрос заново. Исправили данные (добавили дату рождения, выбрали
другую смену) — отправляйте с **новым** ключом. Подробно — в [Повтор запроса](/partners/idempotency).

:::

## Коды API

### Запрос не разобран

| `code`                     | Статус | Когда                                                                 | Что делать                                         |
| -------------------------- | ------ | --------------------------------------------------------------------- | -------------------------------------------------- |
| `bad_request`              | 400    | Неверный JSON или параметр адреса                                     | Проверить, что тело — правильный JSON, параметры — нужного вида |
| `invalid_input`            | 422    | Поле тела не того вида, не заполнено или **незнакомо** (имя — в `message`) | Исправить имя или значение поля                |
| `too_large`                | 413    | Тело запроса больше 1 МБ                                              | Разбить на несколько запросов                      |
| `method_not_allowed`       | 405    | Метод не поддерживается по этому адресу                               | Проверить метод                                    |
| `not_found`                | 404    | Записи нет или она не вашего отдела; неизвестный адрес                | Проверить номер и адрес                            |

### Списки и параметры

| `code`              | Статус | Когда                                               | Что делать                                     |
| ------------------- | ------ | --------------------------------------------------- | ---------------------------------------------- |
| `bad_limit`         | 400    | `limit` вне допустимых границ                       | Обычно 1–200 (у ленты событий 1–500, у сверки 1–5000) |
| `bad_cursor`        | 400    | Неверный курсор                                     | Начать список заново без `cursor`              |
| `bad_period`        | 400    | Начало периода позже конца                          | Поменять даты местами                          |
| `bad_after`         | 400    | `after` — не номер события или больше последнего    | Передать сохранённый номер события; в начале — `0` |
| `bad_time`          | 400    | Время не в формате RFC 3339                         | Например `2027-05-14T00:00:00Z`                |
| `bad_status`        | 400    | Неверный фильтр `status`                            | `active` или `cancelled`                       |
| `bad_payment_state` | 400    | Неверный фильтр `payment_state`                     | `awaiting`, `secured` или `unpaid`             |

### Данные брони, семьи, ребёнка

| `code`                   | Статус | Когда                                                              | Что делать                                       |
| ------------------------ | ------ | ------------------------------------------------------------------ | ------------------------------------------------ |
| `birth_date_required`    | 400    | Нет даты рождения ребёнка. Обязательна при оформлении и при смене ФИО или телефона в брони без даты | Спросить дату у родителя (почему — в [Бронь и оплата](/partners/booking-and-payment)) |
| `empty_name`             | 400    | Пустое ФИО ребёнка или родителя                                    | Заполнить ФИО                                    |
| `bad_phone`              | 400    | Телефон родителя не разобран                                       | Казахстанский номер: `8 701 000 00 01` или `+7 701 000 00 01` |
| `bad_payer_phone`        | 400    | Телефон плательщика не разобран                                    | То же                                            |
| `unknown_city`           | 400    | Города нет в справочнике (оформление)                              | Взять номер из справочника городов лагеря        |
| `bad_city`               | 400    | Города нет в справочнике (правка семьи)                            | То же                                            |
| `unknown_transfer_point` | 400    | Точки посадки нет в справочнике                                    | Взять `transfer_point_id` из `transfer_prices` смены |
| `transfer_not_offered`   | 400    | Из этой точки на смену не возят, или трансфер нужен, а точка не указана | Выбрать точку из `transfer_prices` смены   |
| `bad_children`           | 400    | В оформлении семьи не 1–10 детей                                   | Разбить на несколько запросов                    |
| `unknown_booking`        | 400    | Бронь в `together_with` / `booking_ids` не найдена среди ваших     | Проверить номера: только ваши брони              |
| `reason_required`        | 400    | Не указана причина отмены                                          | Передать `reason`                                |
| `bad_external_id`        | 400    | `external_id` пуст или длиннее 255 знаков                          | Исправить номер                                  |
| `duplicate_external_id`  | 400    | Один `external_id` дважды в одном запросе (оформление семьи)       | Свой номер каждому ребёнку                       |
| `external_id_taken`      | 409    | `external_id` уже у другой вашей записи (в `message` — её номер)   | Скорее всего, бронь уже оформлена — прочитать её по `external_id` |
| `bad_metadata`           | 400    | Метки не по правилам: больше 20 пар, длинный ключ или значение, вложенный объект | Ключ до 40 знаков, значение до 500, без вложенности |
| `bad_partner_manager`    | 400    | `partner_manager` длиннее 200 знаков                               | Сократить                                        |
| `bad_if_match`           | 400    | `If-Match` — не `version` записи                                   | Передать `version` как есть                      |
| `version_mismatch`       | 412    | Запись изменили после вашего чтения                                | Перечитать запись и повторить                    |
| `edit_conflict`          | 409    | Бронь изменилась прямо во время правки                             | Перечитать бронь и повторить                     |
| `contacts_hidden`        | 403    | Ребёнка ведёт другой отдел: контакты семьи не правятся             | Телефоны меняет отдел, который ведёт ребёнка      |
| `forbidden`              | 403    | Недостаточно прав для действия                                     | Действие доступно только лагерю                  |
| `blacklisted`            | 403    | Оформить ребёнка нельзя (запрет лагеря; причина не сообщается)    | Обратиться в лагерь                              |
| `duplicate_child`        | 409    | Ребёнок уже записан на эту смену (возможно, другим отделом)       | Если это ошибка — обратиться в лагерь            |
| `quota_exhausted`        | 409    | Места вашего отдела на смене закончились                           | Другая смена, или попросить лагерь добавить мест |
| `voucher_head_only`      | 403    | Путёвку выпускает и отмечает отправленной руководитель отдела или лагерь | Попросить руководителя отдела               |

### Счета Kaspi

| `code`              | Статус | Когда                                                 | Что делать                                              |
| ------------------- | ------ | ----------------------------------------------------- | ------------------------------------------------------- |
| `bad_kind`          | 400    | `kind` счёта — не `min`/`full`/`amount`               | Исправить `kind`                                        |
| `bad_amount`        | 400    | Сумма не по шаблону, или `amount` передан без `kind: amount` | Сумма строкой: `"50000"` или `"50000.00"`        |
| `min_already_paid`  | 409    | `kind: min`, а минимальный платёж уже внесён          | Выставить `full` или `amount`                           |
| `invoice_not_live`  | 409    | Счёт уже закрыт — отменять нечего                     | Перечитать счёт                                         |
| `invoice_mismatch`  | 409    | По брони разбирается расхождение суммы счёта (при отмене такого счёта — `403`) | Ждать решения лагеря                  |

### Ключ и доступ

| `code`           | Статус | Когда                                                 | Что делать                                                  |
| ---------------- | ------ | ----------------------------------------------------- | ----------------------------------------------------------- |
| `unauthorized`   | 401    | Нет ключа, опечатка, ключ отозван или истёк           | Проверить заголовок `Authorization: Bearer …`; новый ключ — у лагеря |
| `scope_missing`  | 403    | У ключа нет нужного права (какого — в `message`)      | Попросить право у администратора лагеря                     |
| `https_required` | 403    | Запрос по `http://` — **ключ отозван**                | Исправить адрес на `https://`, получить новый ключ          |
| `key_in_url`     | 400    | Ключ в адресе запроса — **ключ отозван**              | Передавать ключ только в заголовке, получить новый ключ     |
| `ip_not_allowed` | 403    | Запрос не с разрешённого адреса                       | Сообщить лагерю адрес сервера                               |

### Повтор запроса и лимиты

| `code`                     | Статус | Когда                                            | Что делать                                       |
| -------------------------- | ------ | ------------------------------------------------ | ------------------------------------------------ |
| `idempotency_key_required` | 400    | Нет заголовка `Idempotency-Key`                  | Добавить заголовок                               |
| `idempotency_key_too_long` | 400    | `Idempotency-Key` длиннее 255 знаков             | UUID (36 знаков) подходит                        |
| `idempotency_mismatch`     | 422    | Этот ключ уже использован с другим телом         | Новое действие — новый ключ                      |
| `rate_limited`             | 429    | Превышен лимит                                   | Ждать `Retry-After` секунд                       |
| `timeout`                  | 503    | Сервер не ответил за 30 секунд                   | Повторить **с тем же** `Idempotency-Key`: начатое действие доводится до конца |
| `internal`                 | 500    | Внутренняя ошибка сервера                        | Повторить позже с тем же ключом; если не проходит — в лагерь с `request_id` |

### Вебхуки

| `code`                 | Статус | Когда                                           | Что делать                                     |
| ---------------------- | ------ | ----------------------------------------------- | ---------------------------------------------- |
| `bad_url`              | 400    | Адрес вебхука не принят (не `https`, внутренний, с логином, с `#`, длиннее 2000 знаков) | Причина — в `message` |
| `bad_event_type`       | 400    | Незнакомый тип события в подписке               | Проверить по списку в [Событиях](/partners/events#типы-событий) |
| `too_many_webhooks`    | 409    | Уже 5 действующих вебхуков                      | Отключить ненужный (`DELETE /webhooks/{id}`)   |
| `secret_unavailable`   | 409    | Секрет вебхука недоступен                       | Сменить секрет (`rotate-secret`)               |
| `webhooks_unavailable` | 503    | Вебхуки временно недоступны                     | Повторить позже                                |

### Служебные коды

`main_department_only` (403), `too_many_keys` (400), `bad_job` (400) относятся к служебным
разделам лагеря. Ключу отдела продаж они не встречаются.

## Правила лагеря

Кроме кодов самого API, оформление, правка и счета возвращают **коды правил лагеря** — те же, что
видит менеджер в CRM лагеря:

| `code`                    | Статус | Когда                                                            |
| ------------------------- | ------ | ---------------------------------------------------------------- |
| `sales_closed`            | 409    | Продажи на смену закрыты                                         |
| `shift_not_open`          | 409    | Продажи на смену ещё не открыты (или смена уже не в продаже)     |
| `no_quota`                | 409    | Вашему отделу не выделена квота на смену                         |
| `birth_date_implausible`  | 400    | На дату заезда ребёнку меньше 3 или больше 19 лет — проверьте дату рождения |
| `bad_birth_date`          | 400    | Дата рождения в будущем                                          |
| `transfer_point_required` | 400    | `needs_transfer: true`, а точка посадки не указана               |
| `discount_over_max`       | 400    | Скидка больше `max_discount` смены                               |
| `bad_discount`            | 400    | Скидка отрицательная или больше стоимости                        |
| `bad_invoice`             | 400    | `invoice` при оформлении — не `min`/`full`/`none`                |
| `too_many_bookings`       | 400    | В «вместе» больше 50 броней                                      |
| `already_cancelled`       | 409    | Бронь уже отменена                                               |
| `already_checked_in`      | 409    | Ребёнок уже заехал — бронь не отменить                           |
| `not_cancelled`           | 409    | Восстановить можно только отменённую бронь                       |
| `same_shift`              | 409    | Перенос на ту же смену                                           |
| `shift_started`           | 409    | Перенос на уже начавшуюся смену                                  |
| `booking_cancelled`       | 409    | Счёт по отменённой брони не выставляется                         |
| `invoice_live`            | 409    | У брони уже есть неоплаченный счёт                               |
| `invoice_paid`            | 409    | Оплаченный счёт не отменяется                                    |
| `amount_over_debt`        | 400 / 409 | Сумма счёта больше остатка по брони                           |
| `fractional_amount`       | 400    | Сумма счёта с тиынами — только целые тенге                       |
| `nothing_to_pay`          | 400    | Остаток по брони — ноль                                          |
| `amount_too_big`          | 400    | Сумма счёта подозрительно велика (лишний ноль?)                  |
| `family_shared`           | 403    | Общая семья: её общие данные не правятся (см. [Ваша база](/partners/your-base#общая-семья-shared-true)) |
| `phone_taken`             | 409    | Второй номер уже записан за другой семьёй                        |

:::tip{title="Список пополняется"}

Новые коды могут появиться в любой момент. Незнакомый код обрабатывайте **по коду ответа HTTP**:
`4xx` — показать менеджеру `message` и не повторять тот же запрос; `5xx` — повторить позже с тем же
`Idempotency-Key`.

:::
