## Request limits and retries

Calls to models are bounded by a two-tier bucket, and under peak load the platform may briefly refuse service. Both signals — `429` and `503` — mean "retry later", not "the request is invalid". Neither one consumes quota.

## Two bucket tiers

- **Per key** — `600` requests per minute by default. The bucket is tied to the key, not to the IP address: a client behind NAT does not share it with other keys, and the same key from different addresses has a shared counter.
- **Per user** — `1500` requests per minute in total across all keys of one user by default. This protects the platform when a single account hands out a dozen keys to agents and each one draws down its own limit.

A request is checked first against the key, then against the user. The first exhausted counter returns `429`.

For individual keys — partner integrations, batch processors — the platform administrator can raise the per-key limit. The per-user limit continues to apply.

## The `429` response

```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
  }
}
```

The `scope` field takes one of two values:

- `per-key` — the limit of a specific key is exhausted. Reduce the number of concurrent requests for this key.
- `per-user` — the shared account limit is exhausted. Reduce the total load across all keys or ask the platform administrator to raise the limit.

The `429` comes with headers:

- `Retry-After: <seconds>` — how long to wait before the next attempt.
- `X-RateLimit-Scope: per-key | per-user` — the tier that was hit. Useful for client logs and metrics.

The limit is checked after the key is validated, so requests with an invalid key (`401`) do not consume quota.

## Transient `503 pool_exhausted`

Besides `429`, which means the client quota was exceeded, the platform may return `503` with the `pool_exhausted` code when the internal connection pool is temporarily exhausted — for example, during a simultaneous peak across several clients. This is a "try again in a few seconds" signal, not "the infrastructure is broken". The response always contains a `Retry-After` header — an integer number of seconds from 3 to 7, with a random spread on the server side so that mass recovery does not hit the pool again.

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

In streaming mode (`stream: true`) status `200` is already sent before the platform learns about the overload, so the signal arrives as the last stream event: `data: { "error": { "code": "pool_exhausted", "retryAfter": <number> } }`, followed by the usual `data: [DONE]`. Recognize `code === "pool_exhausted"` and retry the request after `retryAfter` seconds.

There is also a third overload signal — `429 ai_congested`. It arrives when the AI cluster pool is overloaded. The request was not executed and there is no charge. You can tell it apart from a quota overrun by the `X-AI-Admission: shed` header, which is absent from a `429 rate_limit_exceeded` response.

## The `429 ai_pacing_limited` response

A fourth `429` response has nothing to do with platform load. It arrives when the Bitrix24 account administrator has enabled AI-quota pacing and the call trips the daily or weekly window. Its body comes in the `{ "success": false, "error": { ... } }` envelope rather than the raw shape of the other errors on this page, and it carries the `reason`, `overageDenied`, and `resetAt` fields. The fields and the modes are covered on the [Company AI quota](/docs/ai/consumption/quota) page.

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 429 | `rate_limit_exceeded` | Request limit exceeded. The tier is in the `scope` field and the `X-RateLimit-Scope` header |
| 429 | `ai_congested` | The AI cluster pool is overloaded. The request was not executed, no charge. The response carries the `X-AI-Admission: shed` header |
| 429 | `ai_pacing_limited` | A daily or weekly quota pacing window is exceeded. Retry the request after the time in the `Retry-After` header |
| 503 | `pool_exhausted` | The platform is temporarily overloaded. Retry the request after the number of seconds from `Retry-After` |

The full list of common API errors — [Errors](/docs/errors).

## Ready-made retry recipe

```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
      // Honor Retry-After and add a small random spread
      // so that parallel workers do not return in sync.
      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('Retries exhausted')
}
```

What matters:

- **Always honor `Retry-After`.** It matches the expected recovery time. A shorter interval speeds nothing up and only adds load to the platform.
- **Add your own random spread** on top of the server one — a delay of 100-1000 ms breaks synchronous worker loops.
- **Cap the number of attempts.** Five is enough for most scenarios. Beyond that, propagate the error to the caller.
- **On `5xx` without `Retry-After`**, for example on a provider-side failure, grow the pause exponentially: `min(2^attempt × 1s, 30s)` with a random spread.
- **Do not run `Promise.all` over thousands of records.** Without pauses batch processing will hit one of the bucket tiers.

## See also

- [Create a chat completion](./completions.md)
- [Streaming](./streaming.md)
- [Limits and optimization](/docs/optimization)
- [Errors](/docs/errors)
