For AI agents: markdown of this page — /docs-content-en/ai/chat/rate-limits.md documentation index — /llms.txt
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 —
600requests 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 —
1500requests 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
{
"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.
{
"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 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.
Ready-made retry recipe
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
5xxwithoutRetry-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.allover thousands of records. Without pauses batch processing will hit one of the bucket tiers.