Error reference¶
The gateway uses standard HTTP status codes. Error bodies are JSON
with a detail field (FastAPI / Starlette default).
Status code summary¶
| Status | Typical meaning |
|---|---|
| 200 | Success. |
| 400 | Malformed request (e.g. invalid JSON, multimodal content). |
| 401 | Missing or malformed Authorization. |
| 403 | Privacy gate, Scout on sensitive, or STOP_FOR_HUMAN for non-ROUTE/SCOUT. |
| 422 | Pydantic validation error. |
| 500 | Internal error after upstream exhausted. |
| 502 / 503 / 504 | Upstream connection failure after retries. |
Body shape¶
Pydantic 422 errors use a list-shaped detail:
{
"detail": [
{"loc": ["body", "max_tokens"], "msg": "ensure greater than or equal to 0", "type": "value_error"}
]
}
Detailed status matrix¶
400 Bad Request¶
Returned by FastAPI when:
- The body is not valid JSON.
- The JSON contains a multimodal content part (
image_url) which the v1.0.0 policy rejects. - A field has a wrong type that is not Pydantic-validated (use 422 for Pydantic-validated fields).
401 Unauthorized¶
detail |
Cause |
|---|---|
| (empty or generic) | Missing Authorization header. |
Invalid authentication credentials |
Header present but not in Bearer <token> form, or token ≤ 10 characters. |
403 Forbidden¶
detail |
Cause |
|---|---|
A force-free request that violates privacy policy must fail closed. |
smart-router-free or another free-model override used on a sensitive request. |
Scout cannot be called on sensitive data. |
X-Scout: true on a sensitive request. |
Request denied by policy: <cause> |
A DENY action was produced (rare). |
| Other | STOP_FOR_HUMAN. Common causes: all models unhealthy, hard budget cap exceeded. |
422 Unprocessable Entity¶
Pydantic validation. Common causes:
- Negative
max_tokens. - Out-of-range float fields in
/v1/route/decision(e.g.reasoning_requirementoutside0..1).
500 Internal Server Error¶
detail |
Cause |
|---|---|
OpenRouter API error: <code> |
Non-retryable 4xx from OpenRouter. Common: 400 from upstream, 401 if the server-side OpenRouter key is bad. |
OpenRouter API failed after retries with status <code> |
Retryable 5xx/429 after 3 attempts. |
OpenRouter connection failed: <reason> |
Network error. |
502 / 503 / 504¶
Bad gateway / service unavailable / gateway timeout. The message is
always OpenRouter connection failed: …. These are upstream errors;
the gateway has exhausted its retries.
What is never returned¶
The gateway never returns:
- 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 a normal-payload body.
If you see any of those, file a bug.
How errors are logged¶
Every 4xx and 5xx response emits a structured log line with the HTTP
status, the cause (for routing decisions), and the upstream status
(for upstream failures). The body of the chat request is never
logged.
{"level":"INFO","message":"Routing decision made",
"decision":{"action":"DENY","model":"none","cause":"free_route_blocked_by_privacy"},
"task_id":"t1"}
{"level":"ERROR","message":"OpenRouter API failed after retries with status 503",
"logger":"smart_gateway","timestamp":"2026-08-23T…Z"}
Related¶
- User guide → Errors — narrative view.
- Troubleshooting → Common issues — symptom-based debugging.