Error codes

The gateway's error truth table — HTTP status × the three protocol error shapes, streaming error behaviour, per-endpoint timeouts and how to debug

Error responses are protocol-adaptive: whichever protocol endpoint received the request, the error comes back in that protocol's official format, so your SDK can parse it natively.

Error body shapes (three protocols side by side)#

OpenAI shape (/v1/chat/completions, /v1/responses, /v1/embeddings, and the image / video endpoints):

{"error": {"message": "...", "type": "...", "code": "..."}}

Anthropic shape (/v1/messages):

{"type": "error", "error": {"type": "...", "message": "..."}}

Gemini shape (/v1beta/models/{model}:generateContent):

{"error": {"code": 402, "message": "...", "status": "RESOURCE_EXHAUSTED"}}

Note: the string code field (such as quota_exceeded) only exists in the OpenAI shape. Anthropic error bodies have just type and message; Gemini's code is the numeric HTTP status, with the enum semantics in status. When using the Anthropic or Gemini SDK, match on the corresponding column in the table below.

Error truth table#

HTTPOpenAI codeOpenAI typeAnthropic typeGemini statusCause
400model_not_foundinvalid_request_errornot_found_errorNOT_FOUNDmodel id isn't in the registry
400model_not_allowedinvalid_request_errorinvalid_request_errorINVALID_ARGUMENTthe key's allowed_models allowlist doesn't include that model
400invalid_requestinvalid_request_errorinvalid_request_errorINVALID_ARGUMENTmalformed request body
400tool_not_supportedinvalid_request_errorinvalid_request_errorINVALID_ARGUMENTthe tool doesn't match the upstream channel's capabilities
400invalid_idempotency_keyinvalid_request_errorinvalid_request_errorINVALID_ARGUMENTmalformed Idempotency-Key header (empty, >200 chars, or containing whitespace / non-printable characters)
409idempotency_key_reuseinvalid_request_errorinvalid_request_errorALREADY_EXISTSthe same Idempotency-Key was used for a different request body — use a fresh key
409idempotency_in_flightinvalid_request_errorinvalid_request_errorALREADY_EXISTSthe previous call with this key is still running — wait a few seconds and retry with the same key; a new key really would submit again
409idempotency_response_not_cachedinvalid_request_errorinvalid_request_errorALREADY_EXISTSthe original request succeeded and was billed, but its response was too large to retain and cannot be replayed — use the task list endpoints instead
401invalid_api_keyauthentication_errorauthentication_errorUNAUTHENTICATEDkey missing / invalid / revoked / expired / account disabled
402quota_exceededinsufficient_quotabilling_errorRESOURCE_EXHAUSTEDaccount balance exhausted
403permission_deniedpermission_errorpermission_errorPERMISSION_DENIEDthe upstream provider refused (passed through)
429rate_limit_exceededrate_limit_errorrate_limit_errorRESOURCE_EXHAUSTEDover your account's per-minute or concurrency limit, or an upstream rate limit (passed through)
500internal_errorserver_errorapi_errorINTERNALa gateway-side fault
502upstream_unreachableapi_errorapi_errorUNAVAILABLEcouldn't connect to the upstream
503no_channel_availableserver_erroroverloaded_errorUNAVAILABLEthe model exists but has no channel on this protocol
504upstream_timeoutapi_errorapi_errorDEADLINE_EXCEEDEDupstream timed out (180s on chat endpoints)
upstream's own statusmapped by status — see Upstream error pass-throughas leftapi_errorUNAVAILABLEa non-2xx upstream status passed through unchanged

On Gemini endpoints, 402 and 429 share status: "RESOURCE_EXHAUSTED" — use the numeric code (402 vs 429) to tell them apart.

400 Bad Request#

Most gateway-generated errors are 400s, and these four are the ones people actually hit.

model_not_found#

The requested model id isn't in the pricing registry.

OpenAI shape:

{"error": {"message": "model `claude-3.5-opus` is not available", "type": "invalid_request_error", "code": "model_not_found"}}

How to debug:

  1. Check the spelling — claude-haiku-4-5-20251001 includes a date suffix that must be complete
  2. Compare against the full model list. Placeholder SKUs returned by GET /api/v1/models/public with callable=false also produce this error
  3. Note: when the model id exists but the protocol you used has no channel, you get 503 no_channel_available instead — see the compatibility matrix

model_not_allowed#

The key's allowed_models allowlist doesn't include the requested model.

How to debug: edit the key at dianqi.zsopc.com/keys and add the model to the allowlist, or create a key with no model restriction. Note this is a 400 — don't branch on 403.

invalid_request#

Malformed request body (missing required field, JSON parse failure, wrong field type). Fix the body per the message.

tool_not_supported#

The request carries a tool (such as web_search or image_generation) but was routed to an upstream channel that doesn't support it.

How to debug: confirm the model supports that tool (compatibility matrix), or drop the tools field and retry.

401 Unauthorized#

invalid_api_key#

401 has only this one code — no key, wrong key, revoked key, expired key and disabled account all return it. Use the message to tell them apart:

messageMeaning
authentication failed: missing or malformed api key (...)no key on the request, or the wrong format
authentication failed: invalid api keythe key doesn't exist (truncated copy, or deleted)
authentication failed: api key revokedthe key has been disabled
authentication failed: api key expiredthe key is past its expires_at
authentication failed: account is not activethe account has been disabled

OpenAI shape:

{"error": {"message": "authentication failed: invalid api key", "type": "authentication_error", "code": "invalid_api_key"}}

How to debug:

  1. Check you copied the whole key — the format is sk-gpushare- plus 64 hex characters, 76 characters in total
  2. Check the key still exists at dianqi.zsopc.com/keys. You can re-reveal the full value on its detail page (stored encrypted server-side), so just copy it again if unsure
  3. Check your auth method — any of the four fallbacks works: x-api-key, x-goog-api-key, ?key= query, Authorization: Bearer. See Authentication
  4. Exception: GET /v1/models accepts only the Authorization: Bearer and x-api-key headers — not ?key= — so query auth returns 401 there

402 Payment Required#

quota_exceeded#

The account balance is exhausted. Billing uses one wallet: every API key shares the same account balance and no key has its own budget pool, so when the balance runs out every key fails at once and creating a new key brings no new credit.

OpenAI shape:

{"error": {"message": "Insufficient balance. Please top up and try again.", "type": "insufficient_quota", "code": "quota_exceeded"}}

Anthropic shape: "type": "billing_error". Gemini shape: "status": "RESOURCE_EXHAUSTED" with numeric code 402.

How to debug:

  1. Sign-up includes 121.32 of trial credit; once that's gone, top up at dflop.top/dashboard/billing (Stripe, 404.4 minimum, same SSO account and shared balance as dianqi.zsopc.com)
  2. Check your balance and usage in the console to see what's consuming it — chat and embeddings bill per token, images per image, video per second. See Image / video / music APIs
  3. Don't wait for the 402: monitor with GET /v1/key/balance — it still returns 200 at a zero balance, and costs neither credit nor rate limit. For balances inside third-party clients and relay platforms, see New API and relay platforms

403 Forbidden#

permission_denied#

Pass-through only — the gateway never generates a 403 itself. A 403 means the upstream provider refused the call (content policy, regional restriction and so on) and the message is the upstream's own wording.

How to debug: switch model so the request routes to a different upstream. If it persists, report it to support@dflop.top.

429 Too Many Requests#

rate_limit_exceeded#

Two possible sources — tell them apart by the response headers:

SourceSignatureWhat to do
Platform account limithas Retry-After plus x-ratelimit-* headerswait per Retry-After; for concurrency limits, reduce in-flight requests
Upstream channel limitno x-ratelimit-* headersexponential backoff (1s → 2s → 4s), or switch model to a different upstream

Platform limits are counted per account (all your keys share the quota) across two dimensions: requests per minute (60-second sliding window) and maximum concurrency (in-flight requests). Every /v1/* request counts, including status polls for async tasks. The message states which dimension you hit, for example:

{"error":{"message":"requests per minute limit reached (120/min); retry after 3s","type":"rate_limit_error","code":"rate_limit_exceeded"}}

When a limit is configured, successful responses carry x-ratelimit-* headers too, so you can throttle adaptively. Your account's current limits are on the console API Keys page. See also API reference · rate limits.

500 Internal Server Error#

internal_error#

A gateway-side fault. Rare.

How to debug: retry once or twice. If it persists, report it to support@dflop.top with the timestamp, model id and the complete error body (plus the Cloudflare cf-ray response header if present). There is no request_id field in error responses — don't go looking for one.

502 Bad Gateway#

upstream_unreachable#

The upstream couldn't be connected to (DNS, TCP or TLS level). Only this case returns a fixed 502 — a 5xx returned by the upstream is passed through with its own status rather than rewritten to 502.

How to debug: retry (upstreams occasionally wobble), or switch model.

503 Service Unavailable#

no_channel_available#

The requested model has no usable upstream channel on the protocol endpoint you called — this covers all channels being disabled or unhealthy, and also the model simply not supporting that protocol (for example calling /v1/messages for a model that only has an OpenAI-protocol channel).

How to debug:

  1. Check the compatibility matrix to confirm the model × protocol combination is supported
  2. Switch model
  3. If it stays unavailable, report it to support@dflop.top. Health probing runs every 6 hours (plus one warm-up after a deploy), so "wait a few minutes and retry" rarely helps an unhealthy channel — switching model is faster

504 Gateway Timeout#

upstream_timeout#

The upstream took too long. The ceiling differs per endpoint:

EndpointUpstream timeout
the four chat protocol endpoints (/v1/chat/completions etc.)180s (total, streaming included)
POST /v1/images/generations240s per attempt / 280s across the ladder (set client ≥300s)
POST /v1/videos/generations (submit)60s (the task runs async; generation doesn't count against the request)
POST /v1/embeddings30s

How to debug:

  1. Lower max_tokens, or split the work across several calls
  2. stream: true gets you the first token sooner, but the whole stream is still bound by the same 180s total — streaming is not unbounded
  3. Set your SDK timeout to ≥ 200s to leave headroom
  4. Long jobs (video generation) use async submit plus polling and aren't affected by the per-request timeout — see Image / video / music APIs

Upstream error pass-through#

When an upstream returns a non-2xx, the gateway passes the status through unchanged (upstream 500 → response 500, upstream 429 → response 429) and unwraps the message into the upstream's own wording. In the OpenAI shape, type and code are mapped from the status:

Upstream statusOpenAI codeOpenAI type
400 / 422invalid_requestinvalid_request_error
401invalid_api_keyauthentication_error
403permission_deniedpermission_error
404not_foundinvalid_request_error
429rate_limit_exceededrate_limit_error
5xx / otherupstream_errorapi_error

How to debug: retry once or twice (upstreams occasionally wobble), or switch model to route to a different channel.

Errors during streaming#

Once a streaming request has opened with HTTP 200, the status code can no longer change — errors can only show up in the stream itself:

  • The stream ends early: when the upstream fails mid-way the gateway does not inject an error frame of its own; the stream simply ends. An OpenAI SSE stream won't get its data: [DONE] terminator and the last chunk has no finish_reason (an Anthropic stream is missing message_stop).
  • Upstream error frames pass through: if the upstream emits an in-protocol error event before cutting the stream (such as Anthropic's event: error), the gateway forwards it verbatim.
  • Exception — the built-in tool path: /v1/chat/completions requests carrying the web_search or image_generation built-in tools run over a dedicated channel, and there the gateway does synthesise an error chunk ({"error":{"message":…,"type":"api_error","code":"upstream_error"}}) followed by data: [DONE]. See the streaming guide.

What the client should do:

  1. Don't rely on the connection closing — verify you received data: [DONE] / finish_reason / message_stop, and treat their absence as an incomplete stream
  2. Treat an incomplete stream as a failure and retry with backoff

How it's billed: whatever was transmitted before the interruption is settled on actual usage (when the upstream didn't get to send a usage chunk, output tokens are estimated from the characters already emitted). An interrupted turn is neither double-charged nor free.

Debugging decision tree#

request failed
├─ 400 → model spelling / key allowlist / request body / tool unsupported on this channel
├─ 401 → key problem → re-reveal and copy the key in the console (76 chars), check the auth header
├─ 402 → account balance exhausted → top up at dflop.top/dashboard/billing (a new key won't help)
├─ 403 → upstream refused (pass-through) → switch model
├─ 429 → has x-ratelimit-* headers = platform limit (wait per Retry-After / lower concurrency)
│         no such headers = upstream limit → back off and retry
├─ 503 → no channel on this protocol → check the compatibility matrix / switch model
├─ other 5xx → upstream or gateway issue → retry once or twice, switch model
├─ stream cut off (after a 200) → verify [DONE] / finish_reason → retry as a failure
└─ network level (no HTTP response at all) → check firewall / DNS / TLS reachability to dianqi.zsopc.com

Debugging tips#

Turn on your SDK's debug mode to see the full request and response:

# OpenAI Python SDK
import logging
logging.basicConfig(level=logging.DEBUG)

# Anthropic Python SDK
import os
os.environ["ANTHROPIC_LOG"] = "debug"

# full curl
curl -v -i https://dianqi.zsopc.com/v1/chat/completions ...

The X-Protocol-Translation response header records which cross-protocol translation path the request took — worth including when debugging protocol-related issues. There is no request_id in the error body or headers; when reporting an issue, include the timestamp, model id and complete error body (plus the Cloudflare cf-ray header).