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

Обращения к моделям ограничены двухуровневой корзиной, а при пиковой нагрузке платформа может ненадолго отказывать в обслуживании. Оба сигнала — `429` и `503` — означают «повтори позже», а не «запрос неверен». Ни один из них не списывает квоту.

## Два уровня корзины

- **На ключ** — по умолчанию `600` запросов в минуту. Корзина привязана к ключу, а не к IP-адресу: клиент за NAT не делит её с другими ключами, а один и тот же ключ с разных адресов имеет общий счётчик.
- **На пользователя** — по умолчанию `1500` запросов в минуту суммарно по всем ключам одного пользователя. Это защищает платформу, когда один аккаунт раздаёт десяток ключей агентам и каждый выбирает свой лимит.

Запрос проверяется сначала по ключу, затем по пользователю. Первый исчерпанный счётчик выдаёт `429`.

Для отдельных ключей — партнёрских интеграций, пакетных обработчиков — администратор платформы может поднять лимит на ключ. Лимит на пользователя при этом продолжает действовать.

## Ответ `429`

```json
{
  "error": {
    "type": "rate_limit_exceeded",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded (per-key). Limit: 600 per minute. Retry after 12 seconds.",
    "scope": "per-key",
    "limit": 600,
    "retryAfter": 12
  }
}
```

Поле `scope` принимает одно из двух значений:

- `per-key` — исчерпан лимит конкретного ключа. Снизьте число одновременных запросов этого ключа.
- `per-user` — исчерпан общий лимит аккаунта. Снизьте суммарную нагрузку по всем ключам или попросите администратора платформы поднять лимит.

Вместе с `429` приходят заголовки:

- `Retry-After: <секунды>` — сколько ждать перед следующей попыткой.
- `X-RateLimit-Scope: per-key | per-user` — уровень, в который упёрлись. Полезно для логов и метрик клиента.

Лимит проверяется после проверки ключа, поэтому запросы с некорректным ключом (`401`) квоту не расходуют.

## Транзитные `503 pool_exhausted`

Помимо `429`, который означает превышение клиентской квоты, платформа может отдать `503` с кодом `pool_exhausted`, когда внутренний пул соединений временно исчерпан — например, при одновременном пике у нескольких клиентов. Это сигнал «попробуй ещё раз через несколько секунд», а не «инфраструктура сломана». Ответ всегда содержит заголовок `Retry-After` — целое число секунд от 3 до 7, со случайным разбросом на стороне сервера, чтобы массовое восстановление не било повторно в пул.

```json
{
  "error": {
    "code": "pool_exhausted",
    "type": "server_error",
    "retryAfter": 5
  }
}
```

В потоковом режиме (`stream: true`) статус `200` уже отправлен до того, как платформа узнаёт о перегрузке, поэтому сигнал приходит последним событием потока: `data: { "error": { "code": "pool_exhausted", "retryAfter": <число> } }`, после чего идёт обычный `data: [DONE]`. Распознайте `code === "pool_exhausted"` и повторите запрос через `retryAfter` секунд.

Есть и третий сигнал перегрузки — `429 ai_congested`. Он приходит, когда перегружен пул AI-кластера. Запрос при этом не выполнялся и списания нет. Отличить его от превышения квоты можно по заголовку `X-AI-Admission: shed`, которого нет у ответа `429 rate_limit_exceeded`.

## Ответ `429 ai_pacing_limited`

Четвёртый ответ со статусом `429` не связан с нагрузкой на платформу. Он приходит, когда администратор портала включил равномерное расходование AI-квоты и вызов пробил суточное или недельное окно. Тело такого ответа приходит в конверте `{ "success": false, "error": { ... } }`, а не в сыром формате остальных ошибок этой страницы, и несёт поля `reason`, `overageDenied` и `resetAt`. Разбор полей и режимов — на странице [AI-квота компании](/docs/ai/consumption/quota).

## Ошибки

| HTTP | Код | Описание |
|------|-----|---------|
| 429 | `rate_limit_exceeded` | Превышен лимит запросов. Уровень — в поле `scope` и заголовке `X-RateLimit-Scope` |
| 429 | `ai_congested` | Пул AI-кластера перегружен. Запрос не выполнялся, списания нет. Ответ несёт заголовок `X-AI-Admission: shed` |
| 429 | `ai_pacing_limited` | Пробито суточное или недельное окно равномерного расходования квоты. Повторите запрос по заголовку `Retry-After` |
| 503 | `pool_exhausted` | Платформа временно перегружена. Повторите запрос через число секунд из `Retry-After` |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Готовый рецепт повторов

```javascript
async function callWithBackoff(makeRequest, { maxAttempts = 5 } = {}) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const res = await makeRequest()
    if (res.status === 429 || res.status === 503) {
      const retryAfterSec = Number(res.headers.get('retry-after')) || 0
      // Соблюдаем Retry-After и добавляем небольшой случайный разброс,
      // чтобы параллельные обработчики не возвращались синхронно.
      const baseMs = retryAfterSec * 1000 || Math.min(1000 * 2 ** attempt, 30000)
      const jitterMs = Math.floor(Math.random() * 1000)
      await new Promise(r => setTimeout(r, baseMs + jitterMs))
      continue
    }
    return res
  }
  throw new Error('Повторы исчерпаны')
}
```

Что важно:

- **Всегда соблюдайте `Retry-After`.** Он соответствует ожидаемому времени восстановления, меньший интервал ничего не ускоряет, а только нагружает платформу.
- **Добавляйте собственный случайный разброс** поверх серверного — задержка в 100-1000 мс разрывает синхронные циклы обработчиков.
- **Ограничивайте количество попыток.** Пяти достаточно для большинства сценариев, дальше возвращайте ошибку наружу.
- **При `5xx` без `Retry-After`**, например при сбое на стороне провайдера, увеличивайте паузу по экспоненте: `min(2^attempt × 1s, 30s)` со случайным разбросом.
- **Не запускайте `Promise.all` поверх тысяч записей.** Без пауз пакетная обработка упрётся в один из уровней корзины.

## Смотрите также

- [Создать чат-комплишен](./completions.md)
- [Потоковая передача](./streaming.md)
- [Лимиты и оптимизация](/docs/optimization)
- [Ошибки](/docs/errors)
