Core API

Errors & rate limits

Parse gateway and upstream errors, decide when to retry, and collect a trace identifier.

On this page

This page is for developers diagnosing failed production requests or designing retry behavior.

Most gateway-generated 401, 402, and 429 responses use a string in error. Upstream errors commonly use an OpenAI-compatible object, and so do two gateway errors: a revoked key (401) and a blocked tool (403). Clients must handle both. Read error.code when error is an object; a spending-limit 402 carries a top-level code instead.

JSONSyntax highlighted
{"error":"Unauthorized: Missing API key"}
JSONSyntax highlighted
{"error":{"message":"The selected model rejected the request.","type":"invalid_request_error"}}

Status codes#

StatusMeaningRetry?Action
400Invalid request, including unsupported masked image edits.noFix the body or form fields.
401Missing or unknown API key, or a rotated or revoked key (key_revoked).noFix server-side authentication or switch to the new key.
402Expired, disabled, parent-disabled, or depleted key, or a spending limit reached (key_daily_limit, key_monthly_limit).noInspect key state, balance, and limits.
403A tool blocked for the account (tool_not_allowed), or an upstream authorization or policy rejection.usually noRemove or allow the tool, or inspect the upstream body.
404Unrecognized path prefix, a model that cannot serve the requested endpoint, or a missing account record (not_found).noCorrect the endpoint or the id.
408Upstream timeout response.before output onlyRetry with bounded backoff if the operation is safe.
413Upload too large, including image edits over the combined 8 MB limit.noShrink or split the upload.
429CLSSAI free daily pool exhaustion, an account-endpoint budget (rate_limited), or an upstream limit.laterRead the body and Retry-After, then wait before retrying.
502All gateway connection attempts failed.yesRetry with bounded backoff.
503Gateway configuration or upstream availability failure.yesRetry later and report the trace if persistent.
504Upstream gateway timeout.yesRetry before output with bounded backoff.

CLSSAI does not impose a per-key RPM limit on inference traffic. Free users share a global daily pool; exhaustion returns the gateway 429 string below. Account metadata routes have their own small budgets (see Usage & billing). Other 429 responses come from the upstream API and may use a different body or headers.

JSONSyntax highlighted
{"error":"Too Many Requests: Daily Free Quota Exhausted","message":"The global free tier limit for today has been reached. Please add funds."}

Not found and account rate limits#

Newer gateway-generated errors use the OpenAI-compatible object shape with type, param, and code.

JSONSyntax highlighted
{"error":{"message":"This API endpoint is not supported.","type":"invalid_request_error","param":null,"code":"not_found"}}

not_found (404): the path is not a supported gateway endpoint, the model record does not exist, or a generation id does not resolve to a request owned by the calling key. Unsupported platform paths (for example file or batch management) intentionally return this code instead of forwarding. A generation lookup also returns not_found when the log row is not yet written (about one minute after the request) or when the id belongs to another account. Do not retry not_found unchanged.

JSONSyntax highlighted
{"error":{"message":"Too many account requests. Retry after 1 seconds.","type":"invalid_request_error","param":null,"code":"rate_limited"}}

rate_limited (429): the calling key exceeded a metadata-route budget — GET /v1/key and GET /v1/credits share 30 requests per 10 seconds, and GET /v1/generation allows 5 per 10 seconds with 1 lookup in flight. Read the integer Retry-After response header (seconds until the next slot) and wait. This code is distinct from the free daily pool string above and from upstream 429 responses.

Key and account errors#

These gateway errors come from the key or from account settings. Retrying the same request does not help.

JSONSyntax highlighted
{"error":{"code":"key_revoked","message":"This API key was rotated or revoked. Use the new key from your CLSSAI console."}}

key_revoked (401): the key was rotated and its grace period has ended, or it was revoked. Switch to the replacement key.

JSONSyntax highlighted
{"error":{"type":"permission_error","code":"tool_not_allowed","tool":"web_search","message":"The web_search tool is turned off for this account. Remove it from the request or enable it in your CLSSAI console."}}

tool_not_allowed (403): the account blocks this tool in Workspace tools. tool is one of web_search, web_fetch, code_execution, image_generation, or file_search. Remove the tool from the request, or allow it there.

JSONSyntax highlighted
{"error":"Payment Required: Key spending limit reached","code":"key_daily_limit","message":"This API key reached its daily spending limit of $10.00. The limit resets at 2026-09-23 00:00 UTC."}

key_daily_limit and key_monthly_limit (402): a spending limit set in Workspace keys was reached. A limit on a console key counts that key's spend. A limit on the default Account Key counts the whole account's spend and stops requests from every key. Daily limits reset at 00:00 UTC and monthly limits on the 1st of the month (UTC); message names the limit and the reset time. Limits are soft: requests already in flight can take spend slightly past the limit. Limits are soft: the limit counter can run up to about a minute behind and can undercount under heavy concurrent traffic, so spend can go past a limit before requests stop. Billing itself is always exact; set limits below the amount you must not exceed.

Parse both shapes and retry safely#

JavaScriptSyntax highlighted
const retryable = new Set([408, 429, 502, 503, 504]);

async function requestWithBackoff(url, init, attempts = 3) {
  for (let attempt = 0; attempt < attempts; attempt += 1) {
    const response = await fetch(url, init);
    if (response.ok) return response;

    const body = await response.json().catch(() => ({}));
    const message = typeof body.error === "string"
      ? body.error
      : body.error?.message ?? body.message ?? `HTTP ${response.status}`;

    if (!retryable.has(response.status) || attempt === attempts - 1) throw new Error(message);
    await new Promise((resolve) => setTimeout(resolve, 500 * (2 ** attempt)));
  }
  throw new Error("Request failed");
}

Do not retry after an SSE stream has emitted its first event. The gateway itself makes at most two attempts for connection-level 502, 503, and 504 failures, except for request bodies too large to replay.

Trace a request#

Inference responses do not add an application-specific request ID. Record the cf-ray response header on every response, plus x-generation-id when an OpenAI-compatible response includes it. Share those identifiers, the timestamp, endpoint, and status with support; never share the API key.

Common mistakes#

  • Do not assume error is always an object, and do not retry key_revoked, tool_not_allowed, or spending-limit errors unchanged.
  • Do not infer that a path exists from a 401; authentication happens before route selection.
  • Do not invent a per-key RPM threshold.
  • Include /v1 in the OpenAI SDK base URL and omit it from the Anthropic SDK base URL.
  • Keep server calls out of the browser, preserve complete variant IDs, and check empty streaming choices arrays.
  • Record cf-ray and, for OpenAI-compatible routes, x-generation-id when present.
Need a hand?

Find answers to common questions or diagnose a failed request.

Frequently asked questions →Troubleshoot errors →