Errors & Rate Limits
Error envelope, HTTP status codes, error codes by category, rate-limit headers, and retry guidance.
Errors
Error Envelope
The OpenAI-compatible API (/v1/chat/completions and /v1/models) answers every error with one object. The same object is the last frame of a stream whose turn failed after the stream started.
| Field | Type | Description |
|---|---|---|
| error.type | string | Error family: invalid_request_error, authentication_error, permission_error, not_found_error, billing_error, rate_limit_error, api_error, overloaded_error. Branch on it |
| error.code | string | Stable machine-readable code, a row of the table below. Branch on it for detail, not on the text |
| error.message | string | English text |
| error.param | string | null | The request field the error is about (body.messages, model, session_id) |
| request_id | string | null | Same value as the x-request-id response header |
The family follows the status: 400 and 422 are invalid_request_error, 401 is authentication_error, 402 is billing_error, 403 is permission_error, 404 is not_found_error, 409 is invalid_request_error, 429 is rate_limit_error, 503 is overloaded_error, other 5xx is api_error. The body carries no retry flag: retry on the status and the Retry-After header (see Retry Logic). Proxies may return non-JSON; check the shape before reading fields.
The detail envelope. Two places answer in another shape, {"detail": {"code", "message", "message_en", "hint", "is_retryable", …}}: a 401 of the MCP server, sent before any MCP message is read, and the native /api/v1 (key management, see Authentication). There message is Russian, message_en English, and the field path of a schema error is in field.
MCP Tool Errors
Tools called over MCP (for example connector_execute) don't carry HTTP status codes. A failed call is a result with isError: true whose text carries a JSON object {code, message, retryable, hint?, details?}; hint and details appear when present. The MCP SDK may prefix the text with Error executing tool …:, so parse the JSON that follows it:
When retryable is true, the call may be retried after a short backoff; when false, retrying with the same arguments will fail again.
HTTP Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created: the resource was created |
| 202 | Accepted: turn admitted; execution continues in the background |
| 204 | No Content: successful deletion |
| 400 | Bad Request: invalid parameters or body |
| 401 | Unauthorized: missing or invalid token |
| 402 | Payment Required: the weekly limit is spent or a plan is required. Retrying before reset_at will not help |
| 403 | Forbidden: no access to the resource, or the key lacks the required scope |
| 404 | Not Found: the resource does not exist or is not visible to you (the two are not distinguished) |
| 409 | Conflict: the resource's state does not allow the operation (for example, a turn is already running in the chat, or session_id belongs to another agent) or the idempotency key is taken by another request |
| 413 | Content Too Large: the file or body exceeds the allowed size |
| 422 | Unprocessable Entity: the body failed schema validation; the field path is in error.param on /v1, in field/fields on the native API |
| 429 | Too Many Requests: request rate or concurrent-chat limit exceeded |
| 500 | Internal Server Error |
| 502, 503 | Upstream failure or temporary unavailability; retry with backoff |
| 504 | Gateway Timeout: the request took too long |
Error Codes
The codes a client of /v1 and MCP can receive, with the status, text and retry flag the server answers with. The set of codes grows: handle an unknown code by its HTTP status and error.type instead of failing the client.
| HTTP | Code | Meaning | Retry |
|---|---|---|---|
| 400 | DATA_INVALID_PARAMETER | An invalid request parameter was provided. | no |
| 400 | TOOL_VALIDATION_FAILED | The request is missing data or the data is in the wrong format. | yes |
| 400 | UNKNOWN_INSTANCE | No cabinet chosen, or the named one is unknown. Pick one of the available instances. | no |
| 401 | AUTH_HEADER_MISSING | Missing authorization header. | no |
| 401 | AUTH_INVALID_CREDENTIALS | We couldn't verify the sign-in. Try again. | no |
| 401 | AUTH_TOKEN_EXPIRED | Token has expired. | no |
| 401 | AUTH_TOKEN_INVALID | Invalid authorization token. | no |
| 402 | DATA_USAGE_QUOTA_EXCEEDED | The weekly limit is reached. It resets automatically. | no |
| 402 | SUBSCRIPTION_REQUIRED | This requires a plan that includes it. | no |
| 403 | APPROVAL_NOT_ASKABLE | This write needs approval, which cannot be asked for from here: it was not performed. | no |
| 403 | AUTH_PERMISSION_DENIED | Insufficient permissions for this operation. | no |
| 403 | AUTH_SCOPE_MISSING | Integration lacks the required scope for this action. Reconnect with extended permissions. | no |
| 403 | AUTH_SCOPE_NOT_GRANTABLE | The service refused this action, and the connection form will not open access to it. | no |
| 403 | TOOL_AUTO_REVIEW_BLOCKED | Auto-review stopped this action. | no |
| 404 | DATA_NOT_FOUND | Requested resource not found. | no |
| 409 | AGENT_ON_A_CALL | The agent is already in another conversation. Wait for it to end and start again. | yes |
| 409 | DATA_CONFLICT | This action conflicts with the current state of the data or work. | no |
| 409 | SESSION_BUSY | A turn is still running in this chat. Wait for the response, or stop it and try again. | yes |
| 409 | WRITE_OUTCOME_UNKNOWN | The write's outcome is unknown: the service may have accepted it. It was not sent again. | no |
| 429 | LLM_RATE_LIMITED | Rate limit exceeded. Please wait a moment. | yes |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests. Please wait a moment. | yes |
| 429 | SERVICE_RATE_LIMITED | Requests to the external service are temporarily limited. Please try later. | yes |
| 429 | SESSION_CONCURRENCY_LIMIT | Too many active chats. Close unused ones and try again. | yes |
| 500 | DATABASE_QUERY_CANCELED | The database server stopped the request. If it keeps happening, contact support. | no |
| 500 | SERVICE_SERVER_ERROR | Something went wrong. Please try again a little later. | yes |
| 502 | LLM_PROVIDER_ERROR | The model did not respond. Please try again. | yes |
| 503 | DATABASE_UNAVAILABLE | Service temporarily unavailable. Waiting for the database connection to recover. | yes |
| 503 | LLM_PROVIDER_UNAVAILABLE | The model is temporarily unavailable. Please try again in a few minutes. | yes |
| 503 | SERVICE_UNAVAILABLE | Service temporarily unavailable. Please try later. | yes |
| 504 | LLM_REQUEST_TIMEOUT | The model did not respond in time. Please try again. | yes |
Error Handling Example
Recommended approach to handling API errors in Python.
Request ID
Every HTTP response, successful or not, carries an X-Request-ID header (and X-Trace-ID). You may send your own X-Request-ID, and the response echoes it back. Include this ID when contacting support: it locates the request in our logs.
Rate Limits
Rate Limit Headers
When a refusal knows the delay, it carries a Retry-After header in whole seconds (rounded up). On /v1 the header is the only place the delay is named.
The API does not send X-RateLimit-* headers: the remaining budget is not exposed, so rely on 429.
Retry-After also appears on other retryable errors (409 SESSION_BUSY, 503); a client should accept either seconds or an HTTP date.
Plan-Based Limits
Each organization API key has its own per-minute limit from the organization plan; /v1/chat/completions, /v1/models and the MCP tool calls spend the same budget.
| Plan | Requests per minute, per key |
|---|---|
| Pro | 180 |
| Max | 360 |
Limits may change; the pricing page has the current values.
Rate Limit Exceeded Response
When the rate limit is exceeded, the API returns a 429 status and names the wait in the Retry-After header.
Weekly Limit
Model spend is counted against the plan's weekly window (there is no monthly budget anymore). When the window is spent, /v1/chat/completions and task runs answer 402 DATA_USAGE_QUOTA_EXCEEDED (a plan without tasks answers SUBSCRIPTION_REQUIRED with feature, required_plans and min_plan). On /v1 it is the error object with error.type: "billing_error"; the native API's detail body also carries usage, limit, percent and reset_at. There is no Retry-After: the window resets on its own, and retrying before then gets 402 again.
Retry Logic
Retry on 429, 502, 503 and 504 (on /v1: error.type of rate_limit_error, overloaded_error or api_error): wait for Retry-After, falling back to exponential backoff. Other 4xx are answers to the request itself; on the native API is_retryable says the same. Retry GET/HEAD automatically; do not blindly retry mutations. Do not retry POST /v1/chat/completions blindly: a retried request starts another turn. After a dropped stream, continue the conversation with the same session_id (see Streaming).
Best Practices
- Cache responses that don't change often (e.g., model list)
- Use exponential backoff for retries
- Do not retry
402or another4xxexcept429andSESSION_BUSY: unchanged, the request is refused again - Follow each endpoint's pagination contract: nothing pages through results for you