Для AI-агентов: markdown этой страницы — /docs-content/ai/chat/rate-limits.md индекс документации — /llms.txt

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

Обращения к моделям ограничены двухуровневой корзиной, а при пиковой нагрузке платформа может ненадолго отказывать в обслуживании. Оба сигнала — 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` и `db_transient`

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

В обычном (не потоковом) режиме обрабатывайте оба кода одинаково — по статусу 503 вместе с Retry-After, а не списком известных вам строк: так клиент переживёт появление третьего транзитного кода.

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-квота компании.

Ответ `429 ai_provider_cooldown`

Пятый ответ со статусом 429 приходит, когда кластер моделей отвечает ошибками подряд и платформа перестаёт обращаться к нему, чтобы повторы не мешали ему восстановиться. Пауза снимается сама; заголовок Retry-After несёт её остаток в секундах. Запрос не выполнялся, списания нет.

Длительность паузы не фиксирована: первая — около минуты, но если по её истечении кластер всё ещё отвечает ошибками, следующая пауза становится длиннее (до четырёх минут). Поэтому берите время ожидания из заголовка Retry-After, а не из фиксированной константы в своём коде.

Отличить его от остальных 429 можно по коду и по отсутствию заголовков: у него нет ни X-RateLimit-Scope (он есть у rate_limit_exceeded), ни X-AI-Admission (он есть у ai_congested). В потоковом режиме статус 200 уже отправлен, поэтому сигнал приходит последним событием потока — тем же способом, что и pool_exhausted, с полем retryAfter внутри кадра ошибки.

Ответ `429 ai_deadline_exceeded`

Шестой ответ со статусом 429 приходит, когда запрос не уложился в срок обслуживания — предельное время, которое платформа готова держать вызов, прежде чем честно отказать. Раньше такой вызов просто обрывался по таймауту без тела; теперь у него есть объявленная граница и понятный отказ.

Тело несёт код ai_deadline_exceeded, тип rate_limit_exceeded и поле retryAfter. Вместе с ним приходят два заголовка: Retry-After — пауза перед повтором в секундах, и X-AI-Deadline-Ms — бюджет, который реально был у этого запроса, в миллисекундах.

Свой, более короткий бюджет можно назвать заголовком запроса X-AI-Deadline-Ms (миллисекунды). Он только сокращает срок: значение больше платформенного не выдаётся, а там, где срок отключён администратором, заголовок его не включает. Нечисловое или неположительное значение считается неуказанным и вызов не отклоняет.

Срок отсчитывается от прихода запроса и покрывает всю его обработку целиком, включая автоматический повтор на резервной модели. В потоковом режиме статус 200 уже отправлен, поэтому отказ приходит последним событием потока — как у pool_exhausted и stream_idle_timeout, с полем retryAfter внутри кадра ошибки.

Отличить его от stream_idle_timeout просто: тот означает «кластер замолчал посреди ответа», а этот — «времени на весь запрос больше нет», и он срабатывает даже когда фрагменты продолжают идти.

Ошибки

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

Полный список общих ошибок API — Ошибки.

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

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 поверх тысяч записей. Без пауз пакетная обработка упрётся в один из уровней корзины.

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