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.

json
{ "type": "error", "error": { "type": "invalid_request_error", "code": "DATA_INVALID_PARAMETER", "message": "Invalid request parameter body.messages: Field required", "param": "body.messages" }, "request_id": "76a9e461-7848-4159-b90f-5c0d704ac97f" }
FieldTypeDescription
error.typestringError family: invalid_request_error, authentication_error, permission_error, not_found_error, billing_error, rate_limit_error, api_error, overloaded_error. Branch on it
error.codestringStable machine-readable code, a row of the table below. Branch on it for detail, not on the text
error.messagestringEnglish text
error.paramstring | nullThe request field the error is about (body.messages, model, session_id)
request_idstring | nullSame 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:

json
{ "code": "SERVICE_RATE_LIMITED", "message": "External service rate limit exceeded.", "retryable": true }

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

CodeDescription
200Success
201Created: the resource was created
202Accepted: turn admitted; execution continues in the background
204No Content: successful deletion
400Bad Request: invalid parameters or body
401Unauthorized: missing or invalid token
402Payment Required: the weekly limit is spent or a plan is required. Retrying before reset_at will not help
403Forbidden: no access to the resource, or the key lacks the required scope
404Not Found: the resource does not exist or is not visible to you (the two are not distinguished)
409Conflict: 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
413Content Too Large: the file or body exceeds the allowed size
422Unprocessable Entity: the body failed schema validation; the field path is in error.param on /v1, in field/fields on the native API
429Too Many Requests: request rate or concurrent-chat limit exceeded
500Internal Server Error
502, 503Upstream failure or temporary unavailability; retry with backoff
504Gateway 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.

HTTPCodeMeaningRetry
400DATA_INVALID_PARAMETERAn invalid request parameter was provided.no
400TOOL_VALIDATION_FAILEDThe request is missing data or the data is in the wrong format.yes
400UNKNOWN_INSTANCENo cabinet chosen, or the named one is unknown. Pick one of the available instances.no
401AUTH_HEADER_MISSINGMissing authorization header.no
401AUTH_INVALID_CREDENTIALSWe couldn't verify the sign-in. Try again.no
401AUTH_TOKEN_EXPIREDToken has expired.no
401AUTH_TOKEN_INVALIDInvalid authorization token.no
402DATA_USAGE_QUOTA_EXCEEDEDThe weekly limit is reached. It resets automatically.no
402SUBSCRIPTION_REQUIREDThis requires a plan that includes it.no
403APPROVAL_NOT_ASKABLEThis write needs approval, which cannot be asked for from here: it was not performed.no
403AUTH_PERMISSION_DENIEDInsufficient permissions for this operation.no
403AUTH_SCOPE_MISSINGIntegration lacks the required scope for this action. Reconnect with extended permissions.no
403AUTH_SCOPE_NOT_GRANTABLEThe service refused this action, and the connection form will not open access to it.no
403TOOL_AUTO_REVIEW_BLOCKEDAuto-review stopped this action.no
404DATA_NOT_FOUNDRequested resource not found.no
409AGENT_ON_A_CALLThe agent is already in another conversation. Wait for it to end and start again.yes
409DATA_CONFLICTThis action conflicts with the current state of the data or work.no
409SESSION_BUSYA turn is still running in this chat. Wait for the response, or stop it and try again.yes
409WRITE_OUTCOME_UNKNOWNThe write's outcome is unknown: the service may have accepted it. It was not sent again.no
429LLM_RATE_LIMITEDRate limit exceeded. Please wait a moment.yes
429RATE_LIMIT_EXCEEDEDToo many requests. Please wait a moment.yes
429SERVICE_RATE_LIMITEDRequests to the external service are temporarily limited. Please try later.yes
429SESSION_CONCURRENCY_LIMITToo many active chats. Close unused ones and try again.yes
500DATABASE_QUERY_CANCELEDThe database server stopped the request. If it keeps happening, contact support.no
500SERVICE_SERVER_ERRORSomething went wrong. Please try again a little later.yes
502LLM_PROVIDER_ERRORThe model did not respond. Please try again.yes
503DATABASE_UNAVAILABLEService temporarily unavailable. Waiting for the database connection to recover.yes
503LLM_PROVIDER_UNAVAILABLEThe model is temporarily unavailable. Please try again in a few minutes.yes
503SERVICE_UNAVAILABLEService temporarily unavailable. Please try later.yes
504LLM_REQUEST_TIMEOUTThe model did not respond in time. Please try again.yes

Error Handling Example

Recommended approach to handling API errors in Python.

python
import requests response = requests.post(url, headers=headers, json=body) if response.status_code >= 400: try: payload = response.json() except ValueError: payload = {} error = (payload.get("error") or payload.get("detail") or {}) if isinstance(payload, dict) else {} if not isinstance(error, dict): error = {"message": str(error)} family = error.get("type") code = error.get("code", f"HTTP_{response.status_code}") message = error.get("message") or response.text request_id = response.headers.get("X-Request-ID") if response.status_code == 429: retry_after = response.headers.get("Retry-After", "60") print(f"Rate limited. Retry after {retry_after}s") elif response.status_code == 401: print(f"Auth error: {message}") else: print(f"Error [{code}] {family}: {message} (param {error.get('param')}, request {request_id})")

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.

PlanRequests per minute, per key
Pro180
Max360

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.

http
HTTP/1.1 429 Too Many Requests Retry-After: 12 Content-Type: application/json { "type": "error", "error": { "type": "rate_limit_error", "code": "RATE_LIMIT_EXCEEDED", "message": "Too many requests. Please wait a moment.", "param": null }, "request_id": "0b7d4e2a-6c1f-4a8e-9d3b-2f5a7c1e8b40" }

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).

python
import time import requests def request_with_retry(url, headers, max_retries=3): for attempt in range(max_retries): response = requests.get(url, headers=headers) if response.status_code in (429, 503): retry_after = int(response.headers.get("Retry-After", str(2 ** attempt))) print(f"Rate limited. Retrying in {retry_after}s...") time.sleep(retry_after) continue return response raise Exception("Max retries exceeded")

Best Practices

  • Cache responses that don't change often (e.g., model list)
  • Use exponential backoff for retries
  • Do not retry 402 or another 4xx except 429 and SESSION_BUSY: unchanged, the request is refused again
  • Follow each endpoint's pagination contract: nothing pages through results for you
Was this page helpful?