Status codes¶
The gateway uses standard HTTP status codes. This page is the single source of truth for "what does this code mean here".
| Status | When |
|---|---|
200 OK |
Successful completion (non-streaming) or successful stream start (streaming). |
400 Bad Request |
Malformed JSON body; multimodal content rejected by the policy layer. |
401 Unauthorized |
Missing Authorization header, header malformed, or bearer token ≤ 10 characters. |
403 Forbidden |
Privacy gate (A force-free request that violates privacy policy must fail closed.), Scout on sensitive (Scout cannot be called on sensitive data.), DENY action, or STOP_FOR_HUMAN for non-ROUTE/SCOUT decisions. |
404 Not Found |
Unknown route (anything other than /healthz, /metrics, /v1/chat/completions, /v1/route/decision). |
422 Unprocessable Entity |
Pydantic validation error. The detail is a list of field-level violations. |
500 Internal Server Error |
OpenRouter returned a non-retryable 4xx, or all retries exhausted. May include a fallback chain that already failed. |
502 Bad Gateway |
OpenRouter transport error after retries. |
503 Service Unavailable |
Same as 502. |
504 Gateway Timeout |
Same as 502. |
How to distinguish 403 sub-cases¶
The detail field is the key:
| Detail | Cause |
|---|---|
A force-free request that violates privacy policy must fail closed. |
Privacy gate; client used smart-router-free or another free override on sensitive content. |
Scout cannot be called on sensitive data. |
X-Scout: true on sensitive. |
Request denied by policy: <cause> |
A DENY action. The cause is in the detail. |
| Anything else | STOP_FOR_HUMAN. Common causes: all models unhealthy, hard budget cap exceeded, privacy hard gate on free-tier task, SAFE_DEGRADED + Pro request. |
How to distinguish 500 sub-cases¶
| Detail | Cause |
|---|---|
OpenRouter API error: <code> |
Non-retryable upstream 4xx. Common: 400 (bad request shape), 401 (server-side OpenRouter key bad), 403 (provider refusal). |
OpenRouter API failed after retries with status <code> |
Retryable 5xx/429 after 3 attempts. |
OpenRouter connection failed: <reason> |
Transport error after retries. |
For Pro requests, the gateway attempts one fallback to GLM-5.2 before returning 500. So if you see 500 on a Pro call, both Pro and GLM-5.2 failed.
What is never returned¶
- 200 with a synthetic success body when the upstream actually failed.
- 200 with an empty
choicesarray on a hard-cap denial. - 200 with an error detail inside the response body.
If you see any of these, file a bug.