API error codes
System One API error shapes, status codes, and how to handle failures in the TypeScript SDK.
Use this reference when handling errors from the HTTP API and console endpoints, or integrating the System One SDK. Playground shows a user-facing message; an application should classify errors from the documented response headers.
Answer-ready
Q: How does System One API error handling work?
A: The System One API returns a consistent error envelope with HTTP status codes. The TypeScript SDK surfaces these as typed errors you should handle before retrying with an idempotency key.
What to save
Save the HTTP status, X-System-One-Error-Code, X-Request-Id, and the time of the request. Use the header error code to decide what to do, rather than matching message wording or assuming every error body has the same structure. Do not log API keys, authorization headers, or sensitive request content.
Identify the error source
Read X-System-One-Error-Source alongside X-System-One-Error-Code. upstream means a valid model-service JSON error retains its original 400–599 HTTP status and body. Its body may be an object, array, string, other JSON scalar, or null; it need not use the System One envelope. For example:
{
"detail": [
{ "loc": ["body", "questions"], "msg": "Invalid question definition", "type": "value_error" }
]
}This is an illustrative format, not a recorded error. The stable header classifies HTTP 429/529 as provider_busy, 400/422 as provider_rejected_request, and other upstream HTTP failures as provider_unavailable. Upstream 401/403 remain 401/403; they do not mean the caller's System One login expired. Even platform plus a provider_* code can indicate a safely replaced model-service error, not an account authentication problem.
Retry-After can contain seconds or an HTTP date and is preserved when safely available. Render a localized message from the stable classification rather than displaying arbitrary message or detail text; error bodies may echo sensitive input. X-Upstream-Request-Id and X-TypeSafe-Request-Id, when present, are separate tracking identifiers from System One's X-Request-Id.
System One error envelope
X-System-One-Error-Source: platform identifies System One errors and safety replacements. Their JSON envelope includes error.code; prefer the header classification when handling both sources. Payment endpoints have their own payment contract.
A typical System One error has this shape; the values are illustrative:
{
"error": {
"code": "insufficient_credits",
"message": "This request requires 3 credits. Top up your account to continue.",
"request_id": "example-request-id"
},
"request_id": "example-request-id"
}Other error bodies can differ. For support and accounting, read the request ID from X-Request-Id, not an arbitrary body field. A provider_* error describes a problem completing the model request; even with HTTP 401 or 403, it does not mean your System One key or browser login has expired.
Key and request problems
| HTTP | Code | What to do |
|---|---|---|
| 400 | invalid_json | Send valid UTF-8 JSON without duplicate object keys. |
| 401 | invalid_api_key | Check the Bearer key. Create a replacement in your console if the key was revoked or lost. |
| 401 | unauthorized | This endpoint requires an authenticated account/session. Sign in; a Bearer key does not replace a console session. |
| 403 | account_disabled | Contact support about your account. |
| 403 | invalid_origin | A session mutation is missing the exact application Origin or came from another origin. Use the signed-in application origin. |
| 404 | not_found | Check the main application origin and documented path; console paths start with /api/system-one, not /v1. |
| 405 | method_not_allowed | Use the endpoint's documented method: decisions use POST, lists use GET, and key revocation uses DELETE. |
| 404 | key_not_found | The key ID is unknown or belongs to another account. Check your own key list. |
| 413 | request_too_large | The raw body exceeds the selected model or endpoint limit: Jev/HTTP modes retain 65,536 bytes, image-capable Clef allows up to 13,631,488 bytes (catalog limits may be tighter); key creation allows 4,096 bytes. |
| 415 | unsupported_media_type | Send Content-Type: application/json. |
| 422 | invalid_request | Check the indicated field and the selected model's limits. |
| 422 | unsupported_model | Copy a current model ID from the models list. |
| 422 | invalid_idempotency_key | Follow the operation-key format. |
Authentication and initial request-validation failures occur before a new decision reservation and do not charge decision credits. This includes unsupported models and model-specific input/extension restrictions; check the selected catalog entry's limits. Duplicate decision-object keys are invalid even when escaped names decode to the same key. Numeric values outside the supported finite range, invalid names, and unsupported null-versus-omitted forms can fail validation. The service does not substitute a fallback answer or another model. A failure does not undo a different, earlier operation.
Balance, limits, and repeated requests
| HTTP | Code | What to do |
|---|---|---|
| 402 | insufficient_credits | Check your balance. Add credits or reduce the request's question count or size. |
| 409 | key_limit_exceeded | There are already 20 active keys on this account. Revoke an unused key before creating another. |
| 409 | idempotency_conflict | The key was used with another request. Restore the original body to recover it, or use a new key for a genuinely new operation. |
| 409 | request_in_progress | Wait for Retry-After, then retry with the same key and body. |
| 409 | idempotency_failed | The original operation failed and its credits were returned. Use a new key only for a deliberate new attempt. |
| 409 | request_expired | The operation ended before completion and its credits were returned. Check its record before starting again. |
| 410 | idempotency_expired | The original answer is no longer available for replay. Check the original operation before starting another. |
| 429 | rate_limit_exceeded | Wait for Retry-After. API keys and Playground share the account's decision limit. |
Replay protection lasts 24 hours from request creation, not indefinitely. An expired key can start a new billable operation without returning 410. See Idempotency and retries.
Decision attempts share the account window across keys and Playground (120 per minute by default), including replays. Model listing has a separate 60-per-account-per-minute limit; key creation has a 10-per-account-per-minute limit. API authentication also has a separate 600-attempt-per-IP-per-minute guard. On a local 429, X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset describe the exhausted window, while Retry-After gives the wait in seconds. Reset is a Unix timestamp in seconds, not a duration. Upstream limits use the separate X-Upstream-RateLimit-* namespace.
Service problems
| HTTP | Code | What to do |
|---|---|---|
| 429 / 529 | provider_busy | The model service is busy. Observe Retry-After and check the recorded outcome before another attempt. |
| 400 / 422 | provider_rejected_request | Review the request and selected model's requirements before trying again. |
| 502 or other 4xx / 5xx | provider_unavailable | The model request could not be completed. Check the outcome and contact support if it continues. |
| 502 | invalid_provider_response | The model returned an invalid, unsafe, non-JSON, invalid-UTF-8, over-1-MB response, or a redirect. No default decision is generated. |
| 504 | provider_timeout | The 20-second model-response deadline, covering headers and body, expired. Check the recorded outcome. |
| 499 | request_cancelled | The request was cancelled. Check its record before submitting a new operation. |
| 503 | provider_not_configured, provider_configuration_error, app_not_configured | The service needs attention. Contact support; you do not need to change service settings. |
| 503 | model_catalog_unavailable | The current model catalog could not be loaded. No new evaluation was started; retry later or contact support. Do not substitute a remembered model price. |
| 501 | model_listing_not_supported | Model listing is unavailable through this connection. Use the Playground selector and contact support. |
| 503 | request_reconciliation_pending | The request's charge is not yet settled. Keep its ID and check console usage; do not assume a new attempt is free. |
| 500 | internal_error | Keep the request ID and check the original operation before trying again. |
Check charges before retrying
Ordinary failed evaluations return the credits charged for that attempt. X-System-One-Credits: 0 confirms no retained charge for the attempt. If immediate reconciliation cannot finish, the service returns request_reconciliation_pending and omits this header. A missing charge header does not mean zero. Check Credits and billing and your console's usage records.
A timeout or lost connection in your own tool does not prove the operation failed. Recover it with the same operation key and request body. After a confirmed failed-and-refunded outcome, a new attempt needs a new key. Avoid automatic new-key retries.
Model-list and key-management requests do not reserve decision credits. A confirmed ordinary evaluation failure is refunded before its error response is returned. If immediate reconciliation fails, 503 request_reconciliation_pending takes precedence over that error and the charge header is omitted. Do not interpret a missing header, client timeout, or cancelled connection as a confirmed refund.
Safety exceptions and response headers
Unsafe credential-bearing error content is replaced with a safe System One body while retaining the HTTP status and stable provider classification; its source becomes platform. Unsafe successful responses are rejected rather than returned. Malformed JSON, invalid UTF-8, oversized responses, and redirects are rejected; redirects are not followed. These safety rules mean clients must not assume every model-service response is forwarded byte for byte.
Only documented safe tracking/rate headers, Content-Type, and Retry-After are forwarded. Secret-bearing header values are omitted; cookies, authorization, and Location are not forwarded. System One's no-store, security, and CORS policy remains authoritative. See browser access for exposed headers and the difference between Bearer API access and same-origin session endpoints.
SDK error handling
The System One @system-one-ai/core@0.8.0 SDK exports APIError with statusCode, requestId, and retryAfterMs, but not the full server error body or classification headers. Use Fetch when an integration must distinguish the different 409 codes. The independently maintained official @typesafe-ai/sdk@0.6.0 has its own error classes; do not assume both clients expose the same shape, or log either error object wholesale. See SDK requests and clients.
Published core 0.8.0 retains that same APIError surface; image and extract support do not add access to hosted error classification headers. Invalid image formats, representations, counts, decoded sizes or selected-model capabilities are rejected before reservation. A valid image request that later fails upstream follows the existing refund/reconciliation rules. Hosted decision image clients use Fetch and must inspect response.ok and the response headers explicitly. Clairic extract errors use the same APIError fields.
Client retry policies are separate from the service, which does not automatically retry model inference. After an uncertain response, recover with the retained key and body. After a recorded failed-and-refunded outcome, the same key returns idempotency_failed; use a new key only for a deliberate new attempt. See Idempotency and retries.
For unresolved errors or charges, email support@system-one.dev with the request ID, time, status, and error code. Do not send the API key or sensitive submitted content.