Skip to content

Gateway

Errors & retries

6 classes, chosen so each one implies a different response. Every failure carries the request id, including the ones that fail before authentication finishes.

Every error has this shapejson
{
  "error": {
    "message": "monthly allowance exhausted ($10.00 of $10.00 of model spend). Add a top-up or upgrade the plan, or wait for the period to reset.",
    "type": "quota",
    "code": 402,
    "request_id": "8f2b1e0c-4d5a-4b91-9c3e-6a7f0d2b5c41"
  }
}

type is the class. code repeats the HTTP status. request_id is also returned as x-lobstack-request-id and is the id of the trace row, so quoting it in a support thread turns a description into a lookup. The taxonomy is deliberately small: 6 buckets that each imply a different action beats a long list nobody can group by.


The 6 classes

ClassStatusWhose problemRetry?
auth401, 403The credentialNo
quota402, 429Allowance or rate limit402 no, 429 yes
validation400, 409, 422The request bodyNo
provider502, 503, other 5xxThe upstream model providerYes, with backoff
timeout408, 504We gave up waitingYes
internal500OursOnce, then report it

The mapping runs from status to class, so a status the Gateway raises and a status a provider returns land in the same bucket. The statuses above are the ones each class claims by name; any other 5xx is attributed to the provider, which is why provider reads “other 5xx” and internal does not — 500 is claimed by name and never reaches that rule.

An unknown throw is classified internal rather than provider: blaming upstream for our own crashes would make the error rate look better than it is. A sub-500 status no class claims goes the same way, for the same reason.

auth

The credential was rejected. Causes: no bearer token; a key that does not match the lsk_<env>_<8 hex><48 hex> shape; a key that is unknown, revoked or expired; a key without the inference scope; and a legacy agent credential whose row is not active, which returns 403 with that status in the message.

Do not retry. Revoked and expired keys are reported distinctly from unknown ones in the message, because "this key was revoked" beats "unauthorized" when somebody is debugging at two in the morning, but every one of them is a rejection.

quota

Two different things share this class. A 402 means the allowance for the period is gone. A 429 means a rate limit was hit — in practice the provider's, passed through with its status.

Which allowance depends on the meter, and the message says so rather than making the caller guess. x-lobstack-quota-meter on the same response carries spend, requests or legacy; the three are described on Pricing & plans.

Enforcement is asymmetric on purpose. API keys are enforced from the start; legacy agent credentials get the quota headers but are not blocked. Turning enforcement on for them would start returning 402 to callers that have been over their allowance for weeks and working fine, which is a billing-policy decision rather than something to switch on inside a refactor. Both kinds of caller get the same headers, so the data to make that decision exists.

402 on a spend meterhttp
HTTP/1.1 402 Payment Required
x-lobstack-request-id: 8f2b1e0c-4d5a-4b91-9c3e-6a7f0d2b5c41
x-lobstack-quota-meter: spend
x-lobstack-quota-allowance-usd: 10.000000
x-lobstack-quota-spent-usd: 10.004120
x-lobstack-quota-remaining-usd: 0.000000
x-lobstack-quota-resets: 2026-10-01T00:00:00.000Z
retry-after: 1976400

{"error":{"message":"monthly allowance exhausted ($10.00 of $10.00 of model spend). Add a top-up or upgrade the plan, or wait for the period to reset.","type":"quota","code":402,"request_id":"8f2b1e0c-4d5a-4b91-9c3e-6a7f0d2b5c41"}}
402 on a request or legacy meterhttp
HTTP/1.1 402 Payment Required
x-lobstack-request-id: 8f2b1e0c-4d5a-4b91-9c3e-6a7f0d2b5c41
x-lobstack-quota-meter: requests
x-lobstack-quota-limit: 100000
x-lobstack-quota-used: 100000
x-lobstack-quota-remaining: 0
x-lobstack-quota-resets: 2026-10-01T00:00:00.000Z
retry-after: 1976400

{"error":{"message":"monthly allowance exhausted (100,000 of 100,000 requests). Upgrade the plan or wait for the period to reset.","type":"quota","code":402,"request_id":"8f2b1e0c-4d5a-4b91-9c3e-6a7f0d2b5c41"}}

retry-after is in seconds and is computed from the period reset, so on a 402 it is usually days rather than seconds. Treat it as "this will not clear on its own before then", not as a backoff hint: top up or upgrade the plan. Purchased balance is consumed after the included allowance — as dollars on a spend meter, and as x-lobstack-quota-credits on the counted meters.

A request allowance counts this calendar month's successful traced requests. Requests that failed at the provider are excluded, so an outage does not consume the allowance, and the same rule holds on a spend meter: an error row accrues no spend. A quota lookup that itself errors fails open — an unavailable quota table must never stop inference, so an allowance that cannot be read is treated as empty rather than as exhausted and the failure is logged rather than turned into a 402.

validation

The request was malformed, and it will fail identically until you change it. Causes: messages missing or empty; a body that is not JSON; a model key the registry does not know, including after alias resolution; a model that exists but is not available in managed mode; and BYOK mode with no key on the agent.

Provider 400s also land here, because the status passes through with the provider's own message inside. That is the right bucket — the caller can fix it — but it means a validation error can be about something you never wrote yourself. The worked example below is exactly that case.

provider

The upstream failed and it is not your fault. Causes: the network call to the provider threw, which becomes a 502; the provider returned a 5xx, which passes through with its status; and 503 when a managed provider key is not configured on the deployment.

A provider 401 is deliberately remapped to 502. Lobstack's own credential being wrong is our failure, and reporting it as 401 would tell the caller to go and check a key that is fine.

Retry with exponential backoff and jitter. The exception is the 503 whose message says no managed provider key is set — that one is a configuration problem and will fail forever until a key is added or the agent is switched to BYOK.

timeout

The provider call was aborted. Adapters abort at 120 seconds by default, and the route itself allows up to 300 seconds. Anything whose message looks like an abort or a timeout is classified here even when it arrives as an unrecognised throw.

Retryable, but read the warning about duplicate billing below before you do it automatically.

internal

Our bug. Any throw that is not a recognised Gateway error and does not look like a timeout lands here as a 500. Retry once. If it repeats, the request id is the whole conversation: the trace row holds the status, the latency, the model, the credential and the first 1,000 characters of the error message.


Worked example: the temperature 400

Anthropic deprecated the sampling parameters on Opus 4.7 and everything after it, including Opus 5, Sonnet 5 and the Fable line. The API does not ignore a temperature on those models; it returns a hard 400.

What the provider returnsjson
400 {"type":"invalid_request_error","message":"`temperature` is deprecated for this model."}

Passed through the Gateway, that arrived as a 400 of class validation with the provider's message wrapped inside. It was the most common failure in this account's history, and not because customers were setting temperatures: the Gateway sent temperature: 0.7 on every Anthropic request whether or not anyone had asked for one, and every one of them failed.

Two changes removed it. A temperature is forwarded only when the caller actually set one — inventing a sampling default was never the Gateway's decision to make, since it silently changed the character of the output. And it is forwarded only to models that still accept it, matched on the provider-native model id that goes on the wire. When one is dropped the response says so in x-lobstack-dropped-params: temperature. A check in the repo holds the expected answer for every Anthropic model in the registry, so a new one cannot default in by accident.

If you see this 400 today, you sent a temperature to a model that rejects it through a path that is not this Gateway. The behaviour of each family is tabulated on Chat Completions.


Retrying

There is no idempotency keyThe Gateway accepts no idempotency header. A request that timed out may still have completed at the provider, so retrying it can produce a second answer and a second charge — both of which are metered, because the tokens were really generated. For anything expensive, read the trace by request id before you retry rather than retrying blind.
ClassWhat to do
authFix the credential. Never retry.
quota402: top up, upgrade, or wait for the reset. 429: back off exponentially with jitter.
validationFix the body. Retrying reproduces the failure exactly.
providerRetry with backoff, except a 503 saying no managed key is configured.
timeoutRetry, aware that the first attempt may have completed.
internalRetry once, then report the request id.

Failures inside a stream

A stream that fails part-way through cannot change its status code, which is already 200 and whose headers are long gone. The Gateway sends an error object into the stream and then [DONE]. The trace row records the real status, so a stream that appeared to succeed to a naive client still shows as a failure in the usage API.

Mid-stream failuretext
data: {"error":{"message":"provider error 529: overloaded","type":"gateway_error","code":529,
       "request_id":"8f2b1e0c-4d5a-4b91-9c3e-6a7f0d2b5c41"}}

data: [DONE]
The type field differs hereInside the stream the error object reports "type": "gateway_error" rather than one of the6 class names. The class is still recorded on the trace row; group by it through the usage API rather than by parsing the stream.

Counting failures

GET /api/v1/usage returns summary.errors_by_class over any range, which is the fastest way to see whether a bad hour was upstream or yours. A failed round also writes a ledger row with zero tokens, so error rate stays computable from the ledger alone. See Metering & cost.

Lobstack

One key over every frontier model, a receipt on every call, and an agent that waits before it changes anything.

© 2026 LobstackAll rights reserved  Status