Skip to content

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

{"detail": "human-readable explanation"}

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_requirement outside 0..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 choices array 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"}