Every status the STELQ gateway actually returns — 400, 401, 402, 403, 404, 405, 409, 502, 503 — all in one uniform {"error": string} shape, with which are safe to retry and which are fatal until you fix something. This is the cross-service error contract; each service's own Errors section is the same table, filtered to that route.
Every error the gateway returns is the same JSON object — a single error string — served as application/json. There is no code field, no nested details, no per-service envelope. If a call fails, you parse one shape and read one human-readable message. Branch on the HTTP status; read error for the why.
{ "error": "Insufficient balance. This request costs $0.0010 but your balance is $0.0000. Please top up at https://stelq.com/dashboard/billing." }This is the gateway-wide, cross-service error contract. It complements the Errors section on each service doc: a service's table is this same set, narrowed to the statuses that route can produce. Search, Content, and Answers share one synchronous proxy path; Research adds a couple of statuses of its own (409, and a 503 distinct from the auth one). The shape never changes between them.
Successful responses carry an X-Request-Id (and, on the sync services, X-Latency-Ms). Quote X-Request-Id when you report a problem — it ties your call to a row in the platform's request log.
Every status the gateway can originate, end to end — auth, balance, routing, and the upstream-down fallback. An upstream engine 5xx is passed through with its own status, but the values below are what the gateway itself produces.
| Status | Where | Meaning |
|---|---|---|
| 400 | Any service | Invalid request — a field is missing or malformed. The error string names the field, e.g. "limit must be a positive integer." or "query is required." |
| 401 | Any service | Missing, malformed, invalid, revoked, or expired API key. Includes a missing or non-Bearer Authorization header. |
| 402 | Any service | Insufficient balance to cover the request, or the key has hit its credit limit. The message says which, and includes the dashboard top-up link. |
| 403 | Any service | This API key is paused. Resume it in the console; the key itself is still valid. |
| 404 | Any service | Unknown route. The body lists the routes that do exist in an available array. |
| 405 | Any service | Method not allowed — you sent a non-POST to a known POST route, or a GET to /v1/mcp. The generic case returns a plain "Method not allowed." body; only the GET /v1/mcp probe also sets an Allow: POST, OPTIONS header. |
| 409 | Research only | A research job for this exact request is already in flight (idempotency dedupe). Comes with Retry-After: 2. |
| 502 | Any service | Upstream engine unreachable — transient. Safe to retry. Not charged. |
| 503 | Auth / Research | The authentication service is down, or (Research submit) the idempotency claim store is unreachable. Transient; the Research case sends Retry-After: 2. |
Split the table into two halves so an agent can self-heal. Transient statuses mean the gateway or an upstream is momentarily unhealthy — retry with backoff and the same call will likely succeed. Fatal statuses mean the request itself is wrong — retrying the identical call changes nothing; something (the body, the key, the balance) has to change first.
| Status | Retry? | What to do |
|---|---|---|
| 502 | Yes | Upstream is down. Retry with backoff. Not charged, so a retry costs nothing extra. |
| 503 | Yes | Auth or the Research claim store is down. Retry with backoff; honor Retry-After on the Research case. |
| 409 | Yes, briefly | Research duplicate in flight. Wait Retry-After seconds, then poll the original job instead of resubmitting. |
| 400 | No | Fix the request body — the error string names the offending field. |
| 401 | No | Fix the credential. Rotate or replace the key. |
| 402 | No | Top up credits, or raise the key's credit limit. Then retry. |
| 403 | No | Resume the paused key in the console, then retry. |
| 404 / 405 | No | Fix the route or method — check the available array on a 404; for a 405, POST to the route. |
On Retry-After: the gateway sets it in exactly two places, both Research-only, both hardcoded to 2 seconds — the 409 (duplicate in flight) and the 503 (claim store unreachable) on submit. The synchronous services — Search, Content, Answers — never send it. If you see Retry-After, you are talking to Research submit.
No 4xx or 5xx is ever billed. The charge is computed as cost when status < 400, else 0 on both the sync proxy and Research submit — so a 502, a 402, a 400, every failure records zero cost. A retry after a transient failure does not double-charge you.
The two Research-specific statuses here — the 409 dedupe and the claim-store 503 — are part of the idempotency design on research submit, not generic gateway behavior. See the cross-links below for how credits and balance produce the 402, and how Research idempotency produces the 409/503.