Skip to content
On this page

Reference

Errors

Error shapes, status codes, and the provider codes you may see.

Error shapes

Most errors use a flat shape:

{ "error": "out of credit — top up via POST /deposits" }

Per-key RPM limits return { "error": "rate limited" }. Per-IP concurrency limits return a structured 429 with too many concurrent requests.

Normalize once

type ApiError = {
status: number;
message: string;
code?: string;
retryAfter?: number;
};
async function apiError(res: Response): Promise<ApiError> {
let body: any = null;
try {
body = await res.json();
} catch {
// empty body
}
const error = body?.error;
const retryAfter =
Number(res.headers.get("retry-after") || 0) || undefined;
if (typeof error === "string") {
return { status: res.status, message: error, retryAfter };
}
if (error && typeof error === "object") {
return {
status: res.status,
message: error.message ?? "request failed",
code: error.code,
retryAfter,
};
}
return {
status: res.status,
message: res.statusText || "request failed",
retryAfter,
};
}

Status codes

CodeMeaningTypical UI
400invalid body or over-long promptInline field error. Also used when the prompt estimate exceeds the model's context window.
401bad/expired JWT or keyRedirect to sign-in or the key manager.
402balance cannot cover the requestShow an add-credit call to action.
403allowlist, quota, cap, disabled model/providerExplain and link to keys, limits, or usage. Also returned for per-model daily input/output caps.
404unknown resourceEmpty state.
408request timeoutOffer a retry.
409conflict (email exists, local mode)Form error.
413body over 10 MBAsk for a smaller upload.
429IP or key rate limitBack off using Retry-After.
500internal errorGeneric error and retry.
503upstream/provider failureRetry and show the sanitized code.

Signup, sign-in and claim

These carry specific messages you can surface directly. Signup is gated hard; see the signup gate.

400 invalid input
400 invalid email
400 invalid email domain
400 <provider> addresses can sign up (provider not on the server allowlist)
400 disposable email addresses are not accepted
400 that email domain cannot receive mail
400 that email domain is too new — use a longer-standing address
400 captcha token missing
400 captcha failed — try again
409 an account already exists for this address
429 an account was already created from this network today
429 an account was already created from this device today

Limit errors

These come from the account and model limits rather than the upstream:

{ "error": "prompt is too long: ~1,200,000 tokens exceeds the 1,000,000 token context window of <id>" }
{ "error": "daily input limit reached for this model: 690,000/700,000 tokens used today (resets at 00:00 UTC)" }
{ "error": "daily output limit reached for this model: 300,000/300,000 tokens used today (resets at 00:00 UTC)" }

Check GET /account/limits to render remaining budget before a request fails.

Provider error codes

You may see no_api_key, provider_auth_error, model_unavailable, invalid_request, rate_limited, upstream_timeout, upstream_unreachable, empty_response, and upstream_error.

  • no_api_key, provider_auth_error, and model_unavailable fail fast with no failover.
  • Transport errors, timeouts, 429, 5xx, and empty_response are retryable and may fail over.
  • Failed upstream calls are never billed.
Do not expose raw upstream error text to end users. Show the sanitized code and a retry action.