Skip to content

Error handling and debugging

Every Gate error carries a machine-readable code and source so you can branch on stable values, see who failed, and know exactly what to do next.

How to read any Gate error

Every error response is JSON with this envelope:

{
"error": {
"code": "insufficient_balance",
"message": "Insufficient PAYG balance for this request....",
"source": "gateway",
"balanceCents": 12,
"availableCents": 0,
"estimatedCostCents": 47,
"billingUrl": "/billing"
}
}

Two fields carry the essential triage information:

  • code: the machine-readable error identifier. Branch on this, not the prose message.
  • source: which side was responsible. This is usually more useful than the HTTP status, because a 503 could be Gate’s or the provider’s, and a 401 could be a bad Gate key or a bad upstream credential.

The same source value also rides on the x-gate-error-source response header, so you can read it without parsing the body.

Error source reference

sourceWho failedRetry the same request?
gatewayGate stopped the request early (balance, rate limit, security, internal error).Only after fixing the cause.
pricingGate could not price the request and refused to serve it unpaid.Not until pricing is available.
customer_configA provider account Gate manages is misconfigured (missing credentials, wrong model, etc.).Not until the configuration is fixed.
byok_credentialsThe provider key you supplied was rejected by the upstream.Not until you fix the key.
providerThe upstream provider’s infrastructure failed.Usually yes, with backoff.
vendorThe model API returned an error (rate limit, bad input, overloaded).Depends on the vendor error.
validationYour request shape was invalid (bad JSON, wrong method, unsupported header).Not until you fix the request.

The request ID, your fastest debugging path

Every response, including error responses, carries X-Gate-Request-Id. Paste that value into the Messages page on the dashboard to see the full record: prompt, response, tokens, cost, cost status, security verdict, and upstream provider. It is the single fastest path from “something went wrong” to “here is exactly what happened.”


Complete error code reference

The table below lists every code Gate emits, its HTTP status, and which source it carries.

CodeHTTPSourceShort description
invalid_key401validationThe API key is missing, invalid, or revoked.
no_route400validationNo provider account can serve the requested model.
invalid_upstream400validationThe X-Gate-Upstream-Url header is missing or malformed.
missing_upstream_header400validationPassthrough tokens require an explicit upstream URL header.
rate_limit_exceeded429gatewayThe API key or org has exceeded its rate limit.
usage_limit_exceeded429gatewayA configured cost/token/request spending cap was hit.
insufficient_balance402gatewayYour prepaid balance can’t cover this request.
payg_disabled402gatewayPaying through Gate isn’t enabled for this workspace.
org_not_provisioned402gatewayThe workspace hasn’t completed paying through Gate setup.
pricing_unavailable503pricingGate cannot price the model for paying through Gate.
security_blocked403gatewayThe request was blocked by Gate’s security policy.
ssrf_blocked403gatewayThe upstream URL was blocked by Gate’s network policy.
circuit_open503providerThe upstream is temporarily unavailable.
upstream_error502providerThe upstream request failed.
upstream_timeout504providerThe upstream request timed out.
upstream_error_in_2xx502vendorThe upstream answered 2xx but the payload was an error.
missing_credentials503customer_configThe provider account has no credentials configured.
bedrock_sdk_unavailable503customer_configThe Bedrock SDK is not available in this gateway build.
invalid_byok_credentials401byok_credentialsThe upstream rejected the caller-supplied your own keys credential.
request_too_large413validationThe request body exceeds the maximum allowed size.
invalid_request400validationThe request shape is invalid (bad JSON, wrong method, etc.).
not_found404validationThe requested resource does not exist.
forbidden403gatewayAccess denied.
method_not_allowed405validationThe HTTP method is not allowed on this endpoint.
duplicate_entry409validationA record with this value already exists.
internal_error500gatewayAn unexpected internal error occurred.

Error details by category

Routing errors

no_route, model not available on any configured provider (HTTP 400)

{
"error": {
"code": "no_route",
"message": "Model \"claude-sonnet-4-5\" is not available on any provider …",
"source": "validation",
"param": "model",
"requested_model": "claude-sonnet-4-5",
"suggested_models": ["claude-3-5-sonnet-20241022"],
"available_provider_types": ["bedrock", "openrouter"],
"available_account_names": ["my-bedrock", "my-openrouter"]
}
}

Gate could not find a provider that can serve the model you requested.

The error body includes useful extras:

  • requested_model: the model ID you sent.
  • suggested_models: similar models Gate can serve (if any).
  • available_provider_types: the provider types available to your organization.
  • available_account_names: the provider accounts available to serve your request.

What to do. Use the X-Gate-Provider header or a "provider" body field to target a specific provider, pick one of the suggested_models, or use your own provider key for the model you want. Model IDs must match what the provider exposes, so the suggested_models list is a good starting point if you have a typo.

invalid_upstream / missing_upstream_header, your own keys upstream URL issue (HTTP 400)

These arise when you use the passthrough path:

  • invalid_upstream: the X-Gate-Upstream-Url header is present but the value is missing or malformed.
  • missing_upstream_header: a passthrough token requires an explicit upstream URL, but no X-Gate-Upstream-Url was provided.

What to do. Supply a valid X-Gate-Upstream-Url value pointing at the provider’s API endpoint (for example, https://api.anthropic.com). See Billing mode.


Rate and usage limit errors

rate_limit_exceeded, API key or org rate limit (HTTP 429)

{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Please retry after the reset window.",
"source": "gateway",
"retryAfter": 1749200460000
}
}

Gate sets three headers alongside a 429 so you can implement backoff precisely:

HeaderMeaning
X-RateLimit-LimitThe configured limit for this window.
X-RateLimit-RemainingHow many requests remain in the current window.
X-RateLimit-ResetUnix timestamp (seconds) when the window resets.
X-RateLimit-ScopeWhich limit fired: org or passthrough-ip.

retryAfter in the error body echoes X-RateLimit-Reset as a convenience for clients that parse the body but not headers.

What to do. Back off until X-RateLimit-Reset and retry. If you hit org-level limits regularly, contact support to discuss a higher plan. See Limits and retention.

usage_limit_exceeded, configured spending cap hit (HTTP 429)

{
"error": {
"code": "usage_limit_exceeded",
"message": "Usage limit exceeded: \"Monthly budget\" (cost limit of 100 p…",
"source": "gateway",
"limitType": "cost",
"threshold": 100,
"currentUsage": 100.4,
"timeWindow": "month"
}
}

Your organization (or the specific API key) has a configured spending cap, a rolling cost, token, or request budget, and this request would exceed it.

The extras tell you exactly which limit fired:

  • limitType: cost, tokens, or requests.
  • threshold: the cap value.
  • currentUsage: what you’ve used so far in the window.
  • timeWindow: the rolling window (day, week, month).

What to do. Wait for the window to reset, raise the cap in Dashboard → Usage limits, or switch to your own provider key that is not subject to the same cap. See Limits and retention.


Security errors

security_blocked, request blocked by security policy (HTTP 403)

For Anthropic-compatible endpoints:

{
"type": "error",
"error": { "type": "permission_error", "message": "Request blocked by security policy." }
}

For all other endpoints:

{
"error": {
"code": "security_blocked",
"message": "Request blocked by security policy.",
"source": "gateway",
"type": "permission_error"
}
}

Gate’s prompt-injection detection decided this request should be blocked. The x-gate-error-source: gateway header is always present on blocked responses.

No tokens were generated and nothing was billed.

What to do.

  • If this is a false positive, open the request in the dashboard (Messages page, paste X-Gate-Request-Id) to see the security verdict and the flagged content.
  • Review the prompt for patterns that resemble injection attempts.
  • Adjust the sensitivity setting in Dashboard → Security if your use case consistently triggers false positives. Operators can configure how detections are handled.

Note: Gate scans every request. There are no bypass paths and no trusted callers. See Prompt-injection defense.

ssrf_blocked, upstream URL blocked (HTTP 403)

{
"error": {
"code": "ssrf_blocked",
"message": "Upstream URL blocked by network policy.",
"source": "gateway"
}
}

When you use the passthrough path with X-Gate-Upstream-Url, Gate checks the target URL against its network policy before making the outbound call. This error means the URL resolved to an address that is not permitted.

What to do. Make sure X-Gate-Upstream-Url points at a legitimate, publicly routable provider API endpoint (for example, https://api.openai.com). Internal IP ranges, localhost, and link-local addresses are not permitted.


Provider and infrastructure errors

circuit_open, upstream temporarily unavailable (HTTP 503)

{
"error": {
"code": "circuit_open",
"message": "Upstream \"my-bedrock\" is temporarily unavailable.",
"source": "provider"
}
}

When Gate sees sustained failures from a provider account, it stops sending traffic to that account for a short time. This protects your traffic from hammering an unresponsive upstream, and it clears automatically after a short delay.

What to do. Retry with exponential backoff. If you have multiple provider accounts, add a provider hint (X-Gate-Provider) to route to a healthy alternative while the failing account recovers. Check the upstream provider’s status page for outage information.

upstream_error / upstream_timeout, provider infrastructure failure (HTTP 502 / 504)

{
"error": {
"code": "upstream_error",
"message": "Upstream request failed. Please try again later.",
"source": "provider"
}
}

The upstream provider returned a 5xx error or the connection timed out. The x-gate-upstream-status response header pins the provider’s original HTTP status code when Gate relays the failure.

What to do. Retry with backoff. These errors come from the provider side, so a retry usually resolves them. Check the upstream provider’s status page if they persist.

upstream_error_in_2xx, error payload inside a 2xx response (HTTP 502)

{
"error": {
"code": "upstream_error_in_2xx",
"message": "Upstream returned an error payload despite a 2xx HTTP status.",
"source": "vendor"
}
}

The upstream answered with HTTP 200 but the response body contained an error envelope (for example, an Anthropic {"type": "error",...} or a stream that closed without a terminal event). Gate reclassifies these so the dashboard surfaces the failure rather than a misleading green badge.


Provider account configuration errors

missing_credentials, provider account has no credentials (HTTP 503)

{
"error": {
"code": "missing_credentials",
"message": "Provider account is missing credentials.",
"source": "customer_config"
}
}

The provider account Gate would use to serve this request has no credentials configured. This is a platform-side configuration issue, not something you can change from your dashboard.

What to do. Use a different model or provider, or use your own provider key for this request. If it keeps happening, contact support.

invalid_byok_credentials, upstream rejected your provider key (HTTP 401)

{
"error": {
"code": "invalid_byok_credentials",
"message": "BYOK credential rejected by the upstream provider.",
"source": "byok_credentials"
}
}

The key you passed (for example via Authorization: Bearer <your-key>) was rejected by the upstream provider. The key may be expired, revoked, or scoped to a different resource.

What to do. Verify the key is valid with a direct call to the provider, then update it in your client.


Paying through Gate errors

Everything in this section applies to traffic paid through Gate only. Requests on your own keys are billed by the provider on your own credential, so the balance and pricing errors below do not apply to them. See Billing mode.

How a request paid through Gate is billed (so the errors make sense)

A request paid through Gate is billed in two steps:

  1. Before the call (a hold, not a charge). Before contacting any provider, Gate checks two things: can it price this model at all, and can your balance cover a worst-case estimate? If either check fails, the request is refused before any provider call, so you are never charged for a refusal. If both pass, Gate places a temporary hold on your balance.

  2. After the response (the real charge). Once the response completes, Gate releases the hold and debits the exact amount the provider charged. You are billed what the provider charged, one for one. See Billing mode.

A hold is never a charge. It is released as soon as the charge is final, and the only money that ever moves is the provider-exact debit.

pricing_unavailable, Gate can’t price the model (HTTP 503)

{
"error": {
"code": "pricing_unavailable",
"message": "Model \"some/model\" is not in our pricing catalog for provider \"my-bedrock\"....",
"source": "pricing"
}
}

Gate refuses to serve a request paid through Gate it cannot bill, because serving one would mean giving you the request for free and silently absorbing the provider cost. There are two sub-reasons:

  • Catalog miss. Discovery has never seen this (provider account, model) pair, or the model ID doesn’t match what discovery recorded. Gate has no row to price against.
  • Pricing missing. Discovery did record the model, but neither the upstream nor the rate index produced a usable number for it.

Both surface as the same 503 pricing_unavailable code, so your client only needs to handle one case. The difference matters for fixing the problem, not catching it.

What to do.

  • Wait for the next scheduled catalog refresh, or switch that model to your own provider key (the provider bills you directly, so Gate skips pricing).
  • Double-check the model ID you sent matches what the provider exposes.
  • Switch that model to your own provider key. Requests on your own keys skip pricing entirely because the provider bills you directly.

insufficient_balance, balance can’t cover the request (HTTP 402)

{
"error": {
"code": "insufficient_balance",
"message": "Insufficient PAYG balance for this request. Worst-case cost…",
"source": "gateway",
"balanceCents": 12,
"availableCents": 0,
"estimatedCostCents": 47,
"billingUrl": "/billing"
}
}

The hold couldn’t be placed because your available balance can’t cover this request’s worst-case estimate. Read the three numbers together:

  • balanceCents: your total balance.
  • availableCents: your balance minus the holds placed by other in-flight requests. This is what’s spendable right now.
  • estimatedCostCents: the worst-case estimate this request needs to reserve.

The request is refused when estimatedCostCents > availableCents.

Why availableCents can be far below balanceCents. When you fire many requests at once, each one places its own hold. A later request sees only what’s left after earlier holds, so you can have a healthy total balance and still be refused because most of it is reserved by requests that have not finished yet. Those holds are released as each request finishes, freeing the balance back up.

How the worst-case estimate is computed. Gate reserves a conservative worst-case estimate of the request’s cost with a safety margin. The hold is released once the charge is final, and you are billed the exact provider cost.

What to do. Top up at /billing. Under heavy concurrency with a near-empty balance, either raise your balance (or auto-topup threshold) so it stays well above your per-second burn, or reduce in-flight concurrency. See Plans.

insufficient_balance with reason: "cost_unbounded" (HTTP 402)

{
"error": {
"code": "insufficient_balance",
"message": "This request's maximum cost could not be bounded, so it cannot be safely billed against your PAYG balance. Set an explicit max output (max_tokens / max_output_tokens) or use a BYOK key.",
"source": "gateway",
"reason": "cost_unbounded",
"billingUrl": "/billing"
}
}

Note the "reason": "cost_unbounded". This is a different failure from a plain balance refusal. Gate could not compute any worst-case ceiling for the request (no catalog estimate and no observed cost to scale from), so it refused rather than serve a request whose cost it can’t bound.

What to do. Set an explicit output cap (max_tokens / max_output_tokens) so the estimate has an upper bound, or use your own provider key for that request.

payg_disabled / org_not_provisioned (HTTP 402)

{
"error": {
"code": "payg_disabled",
"message": "PAYG is not enabled for this workspace. Enable it in /billing or use a BYOK key.",
"source": "gateway"
}
}
  • payg_disabled: Paying through Gate isn’t switched on for this workspace. Enable it under Billing, or send the request as your own keys.
  • org_not_provisioned: the workspace hasn’t completed paying through Gate setup.

An overdraft after a successful response

A request can pass the up-front check and still fail to charge cleanly, and this case is easy to miss because you do not get an HTTP error. The request already succeeded and the response was already delivered to your client. Instead, Gate records the gap in your audit log as a payg.debit.insufficient_balance event:

{
"event_type": "payg.debit.insufficient_balance",
"message": "PAYG balance insufficient at charge time, overdraft after the up-front check",
"data": { "balanceCents": 0, "requiredCents": 5, "costUsd": "0.000512" }
}

Why the up-front check passes but the charge fails. The hold reserves a conservative estimate. By the time the response is complete, the real cost is known, and your spendable balance may have changed in between:

  • Concurrent burn-down. Several requests pass the check at nearly the same time on a near-empty balance. As each one’s real cost is charged, the balance drains, and a later one finds nothing left to charge.
  • A balance edit in between. Something changed the balance (a manual adjustment, a refund) after the hold was placed but before the charge landed.

What happens to the request. Gate already served it, so your response is real and complete. Gate does not fail the request mid-stream. It records the uncharged amount and moves on. The balance is not pushed negative.

How the money is recovered. Gate automatically re-applies any debit for a request that was served (2xx), produced a real positive cost, and has no matching ledger entry. It uses an idempotency key so it can never double-charge, it debits the exact provider cost rather than an estimate, and it skips anything the balance still can’t cover. An overdraft like this is a timing gap, not lost money. Your next top-up squares the books.

Telling the two “insufficient balance” cases apart in your audit log.

Audit reason / event_typeWhenWas the request served?
insufficient_balance_reservationUp-front, hold couldn’t be placed.No. Refused with 402.
payg.debit.insufficient_balanceCharge time, real charge couldn’t be applied.Yes, then recovered later.

The up-front refusal is the normal case: Gate compares your worst-case estimate against your available balance (total minus other in-flight holds) and refuses early. The charge-time event is the only one where the request actually ran.


Understanding cost: estimate vs. catalog vs. what you’re billed

Three different numbers can appear around a single request:

NumberWhere you see itWhat it is
Worst-case estimateThe 402 refusal message / holdA conservative upper bound used only to size the hold.
Catalog estimateCost-divergence reporting (informational)Gate’s own computed cost from the pricing catalog.
Billed costX-Gate-Cost-Usd, cost_usd, ledgerThe exact provider cost, the only number that moves money.

The rule that resolves all confusion: you are billed exactly what the provider charged, one for one. The catalog is used for estimating the hold. It never decides what you pay. Per-request cost is calculated against the provider catalog and we verify accuracy.

A note on _cents. Your balance and ledger are tracked at sub-cent precision. The *_cents figures in headers, the API, and the dashboard are a rounded display mirror of those amounts. They are correct for display but not for reconciliation. For exact reconciliation, sum the 6-decimal cost_usd values, not the rounded cents. See Understanding cost_usd.

Why a request’s cost couldn’t be calculated

If X-Gate-Cost-Usd is absent or cost_usd is null, read X-Gate-Cost-Calculation-Status (the cost_status field on the request record) to learn why. A 0.000000 is never ambiguous. The status tells you whether it’s a genuine zero or an uncalculable one.

StatusMeaning
calculatedPriced from token usage. Normal case; may be a legitimately tiny or zero amount.
upstream_reported_costPriced from the provider’s own per-request cost (the authoritative, provider-exact figure).
cache_hitServed from Gate’s cache, never reached a provider, so cost is zero by definition.
byokYour own keys: the provider bills you directly, so Gate has no cost to report.
unknown_modelGate had no pricing for the resolved model; cost is null.
pricing_gapModel was found but a billing dimension has no usable rate; cost is null.
missing_usageThe upstream returned no usage data (e.g. no include_usage on a streamed response).
streaming_no_usageA streamed response closed without a usage event.
free_requestA non-token-billed modality (embeddings, images, etc.), so no cost applies.
non_billableA non-completion endpoint (e.g. model listing), so no tokens exchanged.

The full set of values is also documented in Dashboard tour.

Catalog vs. provider divergence is informational

When Gate’s catalog estimate differs from the provider’s actual reported cost, it records the difference so you can see it, but it never changes your bill. A background verification sweep independently confirms what Gate billed against what the provider actually charged and flags any mismatch beyond a small tolerance. If the catalog and provider figures disagree, that is expected, it is surfaced on purpose, and the amount you paid already matches the provider. See Billing mode.


A debugging checklist

When a request behaves unexpectedly, work through this in order:

  1. Grab the request ID. Read X-Gate-Request-Id from the response (or find the request in the dashboard) and open its full record. Nearly every question is answered there.
  2. Check source (or x-gate-error-source) first. It tells you whether you, Gate, the provider, or the vendor is responsible, and whether a plain retry can help.
  3. Refused or charged? A 402/503/403 means the request was refused before the provider call, so nothing was billed. A successful response that shows a charge-time gap (payg.debit.insufficient_balance in the audit log) means it was served and will be recovered later.
  4. For a balance refusal, compare availableCents to balanceCents. A gap means other in-flight requests are holding funds. The difference frees up as they finish.
  5. For a cost question, read the cost status. X-Gate-Cost-Calculation-Status distinguishes a genuine $0 from an uncalculable one, and explains an absent X-Gate-Cost-Usd.
  6. For a no_route error, check suggested_models and available_provider_types. These tell you what Gate can route to, and whether the issue is a typo or a missing provider account.
  7. For a security block, open the request in the dashboard. The security verdict and flagged content are visible there, and sensitivity can be adjusted per-organization.