# Ошибки первого дня

Всё из этого списка случается почти с каждым, обычно в первые сутки. Формат
одинаковый: симптом → причина → починка. Ошибки идут от частых к коварным.

## 401 на ключе, который точно верный

**Пробел или перенос строки в скопированном ключе.** Ключ показывается один раз,
копируют его в спешке. Проверка: длина строки в вашей переменной равна длине
ключа на экране создания.

**Нет слова `Bearer`.** Заголовок — `Authorization: Bearer lms_live_…`, не голый
ключ.

**Ключ отозван.** Список ключей в настройках показывает состояние и когда ключ
использовался последний раз — если «только что», а у вас 401, вы смотрите не на
тот ключ.

## 403 при верной аутентификации

**Не хватает права.** В ответе сказано, **какого именно**, — читайте тело, а не
только код. Не добавляйте `*` «чтобы работало»: право добавляется одно, названное.

**Ключ сужен до курса.** Ключ, ограниченный одним курсом, получает 403 на чужих —
это не поломка, это его назначение.

## 404 на объекте, который есть

**Он вне видимости ключа.** «Нет» и «не видно вам» снаружи неразличимы намеренно —
иначе перебором можно узнать, какие объекты существуют.

**Идентификатор не того типа.** Смотрите на префикс: `ord_` в адресе платежей —
это 404, и по префиксу причина видна за секунду. Ради этого префиксы и существуют.

## Фильтр «не работает» — а на самом деле работает не тот

Самая коварная ошибка списка, потому что она **тихая**.

Квадратные скобки и плюс в строке запроса должны быть закодированы: `%5B`, `%5D`,
`%2B`. Незакодированный `+` в дате читается как **пробел** — дата молча
становится другой, запрос успешно возвращает не тот набор данных, и выгрузка
выглядит правильной.

```bash
# Неверно: paid_at[gte]=2026-07-01T00:00:00+03:00
# Верно:
curl -X GET 'https://school.example.com/api/v1/orders?paid_at%5Bgte%5D=2026-07-01T00:00:00%2B03:00' \
  -H 'Authorization: Bearer lms_live_a8f3c1e9d2b74a5f8e0c1d3b5a7f9e2c' \
  -H 'Accept: application/json'
```

Любая HTTP-библиотека кодирует это сама, если передавать параметры параметрами,
а не склеенной строкой.

## Второй заказ после таймаута

Отправили создание заказа, получили таймаут, повторили — заказов два.

Сеть теряет **ответы**, а не запросы: первый запрос мог выполниться. Повтор
операции записи всегда идёт с тем же `Idempotency-Key` — тогда повторять можно
сколько угодно. [Идемпотентность](/api/idempotency/).

## 409 на повторе, который «должен был» пройти

Тот же `Idempotency-Key` пришёл с **другим телом**. Это ошибка клиента, и почти
всегда — баг генерации ключей: например, ключ считается от времени с точностью
до секунды, и две разные операции в одну секунду получают один ключ. Ключ должен
быть уникален **для операции**, одинаков **для её повторов**.

## 429 не проходит никогда

**`Retry-After` игнорируется, повтор в цикле.** Ждите столько, сколько сказано
в заголовке, — он в секундах.

**После 429 обход списка начат заново.** Повторяйте **тот же** запрос с **тем же**
курсором — иначе обход возвращается на первую страницу и не заканчивается никогда.

## Значения «не сохраняются» или сохраняются странно

- **Булевы строками**: `"true"` — это не `true`. Булевы — булевы.
- **Деньги дробью**: `1490.00` — неверно. Целое в копейках, парой с валютой:
  `{ "amount": 149000, "currency": "RUB" }`.
- **Дата без пояса**: `2026-08-09 14:30` требует догадки о том, чей это пояс,
  и догадка будет неверной. Всегда ISO 8601 с поясом.

Полные правила — [Формат](/api/format/).

## Вебхук не проходит проверку подписи

**Подпись считается по разобранному телу.** Большинство фреймворков разбирают
JSON автоматически; собрав его обратно, вы получите другие байты и другую
подпись. Подпись проверяется по **сырому телу до разбора** — на маршруте вебхука
авторазбор надо отключить.

**Часы сервера уехали.** Метка времени в подписи проверяется с допуском 300
секунд. Расхождение больше — все подписи «неверны». Проверьте синхронизацию
времени на приёмнике.

## Вебхук приходит дважды

Так и должно быть: доставка — «как минимум один раз». Повтор, переигрывание,
таймаут после успешной обработки — событие может прийти повторно, и обработчик
обязан быть идемпотентным по `event.id`. Как — с уникальным индексом, а не
проверкой перед вставкой: [Вебхуки](/api/webhooks/).

## Вебхуки «через раз» и с повторами

Обработчик работает синхронно и не укладывается в 10 секунд. Правильный
обработчик отвечает `200` **сразу**, а работает **потом** — иначе каждое событие
приходит по шесть раз, и все шесть обрабатываются.

## Если ничего из списка не подошло

В каждом ответе есть `X-Request-Id`. Запишите его, время и что делали — по нему
в журнале находится полная трассировка запроса. Это превращает разбор из гадания
в работу.
