> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chattler.ai/llms.txt
> Use this file to discover all available pages before exploring further.

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

> Техническое окно на ключ, о котором сообщают заголовки в успешных JSON-ответах и в 429.

У каждого ключа своё окно: по умолчанию **120 запросов за 60 секунд**. Это операционный потолок от злоупотреблений, а не квота тарифа. Само использование учитывается балансом владельца, а не этим счётчиком, и ключи одного владельца окно не делят.

Аутентификация выполняется до ограничителя, поэтому неаутентифицированный запрос никогда не тратит бюджет ключа.

## Заголовки

Успешные JSON-ответы (`200`) и ответ `429` несут состояние окна:

| Заголовок               | Значение                                         |
| ----------------------- | ------------------------------------------------ |
| `X-RateLimit-Limit`     | Сколько запросов разрешено в окне                |
| `X-RateLimit-Remaining` | Сколько запросов осталось в текущем окне         |
| `X-RateLimit-Reset`     | Unix-время (в секундах), когда окно сбрасывается |

Ответ `text/event-stream` потокового эндпоинта и остальные конверты ошибок (`401`, `403`, `404`, `409`, `422`, `503`, `500`) этих заголовков не несут; в каждом ответе есть только `X-Request-ID`. Если вы регулируете клиент по `X-RateLimit-Remaining`, считайте отсутствующий заголовок «неизвестно», а не нулём.

Когда окно исчерпано, API отвечает `429 rate_limit_exceeded` и добавляет `Retry-After` (секунды до сброса окна).

```http theme={null}
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1789470060
Retry-After: 17
X-Request-ID: 7f20a5f0-5dde-48cf-94aa-38acbbd218e0

{"error": {"code": "rate_limit_exceeded", "message": "Rate limit exceeded for this API key; try again later", "request_id": "7f20a5f0-5dde-48cf-94aa-38acbbd218e0"}}
```

## Когда недоступен сам ограничитель

Ограничитель отказывает **закрыто**. Если его хранилище недоступно, API отвечает `503 api_rate_limit_unavailable` с `Retry-After: 5` вместо того, чтобы пропускать запросы без учёта, и вместо вводящего в заблуждение 429. Относитесь к нему как к любой другой временной ошибке: подождите и повторите с тем же `Idempotency-Key`.

<Warning>
  Не распределяйте трафик по нескольким ключам одного агента, чтобы поднять потолок. У каждого ключа свои диалоги, поэтому один и тот же конечный пользователь получит раздробленную историю. Если вам нужен лимит выше, свяжитесь с нами.
</Warning>

## Регулирование на стороне клиента

<CodeGroup>
  ```bash curl theme={null}
  # Retries on 429/503 after Retry-After, then prints the window state of the final answer.
  while :; do
    status=$(curl -s -o /dev/null -D headers.txt -w '%{http_code}' \
      "$CHATTLER_BASE_URL/v1/agents/$CHATTLER_AGENT_ID/conversations?limit=20" \
      -H "Authorization: Bearer $CHATTLER_API_KEY")
    if [ "$status" = "429" ] || [ "$status" = "503" ]; then
      sleep "$(grep -i '^Retry-After:' headers.txt | tr -d '\r' | awk '{print $2}')"
      continue
    fi
    grep -i '^X-RateLimit-' headers.txt
    break
  done
  ```

  ```python Python theme={null}
  import time

  import httpx


  def request_with_pacing(method: str, url: str, **kwargs) -> httpx.Response:
      while True:
          response = httpx.request(method, url, headers=HEADERS, timeout=120, **kwargs)
          if response.status_code in (429, 503):
              time.sleep(float(response.headers.get("Retry-After", "5")))
              continue
          if int(response.headers.get("X-RateLimit-Remaining", "1")) == 0:
              reset_at = int(response.headers.get("X-RateLimit-Reset", "0"))
              time.sleep(max(0.0, reset_at - time.time()))
          return response
  ```

  ```javascript Node theme={null}
  async function requestWithPacing(url, init = {}) {
    for (;;) {
      const response = await fetch(url, {
        ...init,
        headers: { Authorization: `Bearer ${apiKey}`, ...(init.headers ?? {}) },
      });
      if (response.status === 429 || response.status === 503) {
        const wait = Number(response.headers.get("Retry-After") ?? 5);
        await new Promise((r) => setTimeout(r, wait * 1000));
        continue;
      }
      if (response.headers.get("X-RateLimit-Remaining") === "0") {
        const resetAt = Number(response.headers.get("X-RateLimit-Reset") ?? 0) * 1000;
        await new Promise((r) => setTimeout(r, Math.max(0, resetAt - Date.now())));
      }
      return response;
    }
  }
  ```
</CodeGroup>
