Errors
Every error is one envelope — parse it once, handle it everywhere:
{
"error": {
"message": "Human-readable description.",
"type": "invalid_request_error",
"param": "the_offending_field",
"code": "invalid_param"
}
}
param is present only when a specific field caused the error. code is the stable machine handle; type maps onto the OpenAI SDK's error taxonomy. Every response — success or error — carries an x-request-id header: quote it in any support conversation.
The examples below run verbatim against a live stack (export IRONSTRATUM_API_KEY, IRONSTRATUM_REVOKED_KEY, and IRONSTRATUM_BASE_URL first). Codes that are not live yet are documented without an example on purpose — we do not publish curls we cannot stand behind today.
400 invalid_request_error — malformed input
Five codes: invalid_json (unparseable body), invalid_param (a known field with a bad value or an unsupported field), unknown_param (a field we do not accept), context_length_exceeded and sessions_unavailable (session-mode requests — below). Fix the request; no state was created.
An unknown parameter is rejected loudly, never silently ignored:
curl -sS -w '\n%{http_code}\n' $IRONSTRATUM_BASE_URL/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $IRONSTRATUM_API_KEY" \
-d '{
"model": "qwen3.8-27b",
"messages": [{"role": "user", "content": "Hi"}],
"verbosity": "high",
"max_tokens": 16
}'
# expect: 400
context_length_exceeded
The assembled session context — every stored turn plus the new delta — exceeds the model's context_length. Start a new session_id or trim the conversation. Documented without a curl on purpose: overflowing a real session means first filling it past the model's window, which is a slow demo of a deterministic check; the envelope is:
{
"error": {
"message": "The assembled session context exceeds this model's context_length. Start a new session_id or trim the conversation.",
"type": "invalid_request_error",
"param": "messages",
"code": "context_length_exceeded"
}
}
sessions_unavailable
The request carried a session_id, but this gateway runs without the conversation store (JSONL fallback mode). Resend without session_id — full-context requests work unchanged there. Envelope-only for the same reason: the fallback mode is an internal posture, not something a live public stack can demonstrate.
{
"error": {
"message": "Conversations are unavailable on this gateway (no conversation store); resend without session_id.",
"type": "invalid_request_error",
"param": "session_id",
"code": "sessions_unavailable"
}
}
401 authentication_error — invalid_api_key
The bearer token is missing or unknown. Check the key, or create a new one in the console.
curl -sS -w '\n%{http_code}\n' $IRONSTRATUM_BASE_URL/v1/models \
-H "Authorization: Bearer sk-ironstratum-not-a-real-key"
# expect: 401
402 insufficient_credits — credit_balance_exhausted
The wallet is empty. The wallet is a prepaid hard cap — it cannot go negative — so a 402 means top-up, never a surprise bill. This code is documented from day one; it fires once the billing wallet ships.
403 permission_error — key_revoked
The key exists but was revoked. Rotate it out of your configuration:
curl -sS -w '\n%{http_code}\n' $IRONSTRATUM_BASE_URL/v1/models \
-H "Authorization: Bearer $IRONSTRATUM_REVOKED_KEY"
# expect: 403
404 invalid_request_error — model_not_found
The model alias does not exist. List live aliases via GET /v1/models:
curl -sS -w '\n%{http_code}\n' $IRONSTRATUM_BASE_URL/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $IRONSTRATUM_API_KEY" \
-d '{
"model": "does-not-exist",
"messages": [{"role": "user", "content": "Hi"}],
"max_tokens": 16
}'
# expect: 404
404 invalid_request_error — conversation_not_found
A /v1/conversations id that is missing, deleted, foreign to this key, or malformed — deliberately indistinguishable (never leak which ids exist). List yours via GET /v1/conversations:
curl -sS -w '\n%{http_code}\n' $IRONSTRATUM_BASE_URL/v1/conversations/00000000-0000-4000-8000-000000000000 \
-H "Authorization: Bearer $IRONSTRATUM_API_KEY"
# expect: 404
Deleting your own conversation — live or already deleted — is an idempotent 204, never this error.
409 invalid_request_error — idempotency_key_reused
An Idempotency-Key was reused with a different request body — a client bug, not a server state. Generate a fresh key per logical operation. First establish a keyed request, then reuse the key with a different body:
curl -sS -w '\n%{http_code}\n' $IRONSTRATUM_BASE_URL/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $IRONSTRATUM_API_KEY" \
-H "Idempotency-Key: docs-errors-conflict-demo-0001" \
-d '{
"model": "qwen3.8-27b",
"messages": [{"role": "user", "content": "Reply with the word: first"}],
"max_tokens": 16
}'
curl -sS -w '\n%{http_code}\n' $IRONSTRATUM_BASE_URL/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $IRONSTRATUM_API_KEY" \
-H "Idempotency-Key: docs-errors-conflict-demo-0001" \
-d '{
"model": "qwen3.8-27b",
"messages": [{"role": "user", "content": "Reply with the word: second"}],
"max_tokens": 16
}'
# expect: 409
429 rate_limit_error — rate_limited
Emitted today in one case: a concurrent request is already holding the same session_id, and the bounded lock wait (30 s by default) expired — back off briefly and retry the same request; nothing was stored. Per-key request/token throttling is still future work; see rate posture and sessions.
500 api_error — internal_error
Our fault. Retry with an Idempotency-Key if you have one; quote x-request-id if it persists.
502 api_error — upstream_rejected
A provider rejected the request and failover could not rescue it. Retrying with the same body is usually correct — transient provider errors are the common case.
503 api_error — no_provider_available
Every slot in the model's chain is unavailable. Back off and retry, or try another model from GET /v1/models.
SDK mapping
type follows the OpenAI taxonomy: invalid_request_error, authentication_error, insufficient_credits, permission_error, rate_limit_error, api_error — OpenAI SDKs route these to their standard exception classes (AuthenticationError, PermissionDeniedError, …). Handle on code (stable) and display message (human).