Error Codes and Error Handling
Error response JSON structure, common error codes by HTTP status (authentication, parameters, model authorization, balance, rate limits, image-specific errors), and retry recommendations.
Every endpoint signals an error with a non-2xx status code plus a JSON error body. Use the HTTP status to decide between "fix the request" and "retry", then branch precisely on the machine code in the body.
Error Body Structure
Error details always live in the top-level error object:
{
"error": {
"message": "Human-readable error description",
"type": "bad_request",
"code": "optional_machine_code",
"param": "image"
}
}| Field | Description |
|---|---|
message | Human-readable description; fine for developers, not recommended to show verbatim to end users |
code | Stable machine code — branch on it first when present |
type | Error category; on some endpoints the machine code appears only here |
param | The offending request field (only on some parameter errors) |
Differences between endpoint families:
- Chat endpoints (
/v1/chat/completions,/v1/responses): gateway-side errors are identified byerror.codeand usually carry notype; rate-limit errors also includeretry_after_seconds. /v1/messagesuses the Anthropic format:{"type": "error", "error": {"type": "invalid_request_error", "message": "..."}}, whereerror.typeis an Anthropic value such asinvalid_request_error/authentication_error/permission_error/rate_limit_error/api_error. There is no gateway machine code in this protocol, so handle errors by HTTP status (for example, insufficient balance is also429).- Image, speech, OCR, and vision segmentation use
error.typeprimarily; some errors also carrycode.
Recommended parsing:
def error_code(body: dict) -> str | None:
err = body.get("error")
if isinstance(err, dict):
return err.get("code") or err.get("type")
return err if isinstance(err, str) else NoneQuick Reference by Status
| HTTP | Typical machine codes | Meaning | What to do |
|---|---|---|---|
400 | bad_request, invalid_request, invalid_request_body, model_required | Missing / malformed parameter, or the body is not valid JSON | Fix the request; do not retry |
400 | model_not_available_for_routing | The model name does not exist or is currently unavailable | Check the name against the model catalog |
400 | model_retired | The model has been retired | Switch to another model |
400 | wrong_endpoint_for_model | An image / video model was sent to a chat endpoint | Use the matching endpoint |
401 | missing_api_key, invalid_api_key, authentication_required | Key missing, invalid, or (on chat endpoints) lacking the scope | See Authentication and API Keys |
402 | MISSING_MULTI_CURRENCY_PRICE, MODEL_PRICE_NOT_CONFIGURED (type is billing_error) | The model has no price configured for your organization; the gateway rejects instead of serving it for free | Contact support to complete pricing, then retry |
403 | insufficient_scope, ip_not_allowed | Key lacks the scope / source IP not on the allowlist | See Authentication and API Keys |
403 | model_not_allowed_for_api_key, image_model_not_allowed, image_size_not_allowed | Outside the key's model scope / image size tiers | Adjust the key's restrictions or use another key |
403 | model_not_authorized_for_org | The model is not enabled for your organization | Ask your administrator to enable it |
429 | rate_limit_exceeded (speech / OCR use rate_limit) | Organization or key rate / quota exceeded | Back off per retry-after (image endpoints omit the header; use exponential backoff); see Rate Limits and Quotas |
429 | BILLING_BLOCKED | Insufficient account balance or a budget cap reached | Top up or adjust the budget; plain retries will not succeed |
429 | all_providers_rate_limited | The model is at capacity right now | Back off per retry-after (30 seconds) |
451 | content_policy_rejected | Image content failed the safety review | Change the prompt / reference images |
502 | routing_dispatch_failed, upstream_error | Server-side processing failed | Retry a limited number of times with backoff |
503 | all_candidates_exhausted, provider_rpm_unavailable, image_routing_unavailable, image_buffer_overload | Service temporarily busy or unavailable | Retry a limited number of times with backoff; honor retry-after when present |
503 | handler_unhandled_exception | Internal gateway exception (still returns JSON rather than dropping the connection) | Retry a limited number of times |
504 | upstream_timeout | Processing timed out | Retry later; for images, try a lower size tier |
Insufficient Balance and Pricing Errors
Insufficient balance returns 429, not 402. Distinguish it from ordinary rate limiting by the machine code:
{
"error": {
"code": "BILLING_BLOCKED",
"message": "账户余额不足,请及时充值后重试",
"type": "rate_limit_exceeded"
}
}- Prepaid accounts are blocked from new requests once the balance (including vouchers) is ≤ 0; service resumes automatically once a top-up lands.
- Reaching an organization or key daily / monthly budget, or a key's total spending cap, also returns
BILLING_BLOCKED. - On chat endpoints the
messagefor this error may just say "too many requests" — branch oncode.
402 pricing not configured means the model has no valid price for your organization yet, so the gateway rejects the request instead of serving it at zero cost. Contact support; retrying does not help.
{
"error": {
"code": "MISSING_MULTI_CURRENCY_PRICE",
"message": "模型计价信息未确认,请联系客服处理",
"type": "billing_error"
}
}Common Image Errors
| HTTP | Body highlights | Scenario |
|---|---|---|
400 | type: bad_request, param: image, message: 参考图片数量超出上限(最多 N 张) ("too many reference images, max N") | Too many reference images: Gemini-family image models (such as Nano Banana) accept at most 14, other models (such as gpt-image-2) at most 16 |
400 | type: bad_request, param: image | An image edit supplied no reference image |
400 | type: bad_request, message: prompt is required | Missing prompt |
400 | code: invalid_image, param: image | A reference image cannot be decoded (not an image, truncated, or not the declared format; PNG / JPEG / WebP are supported) |
400 | code: image_prompt_rejected, param: prompt | The prompt's length or format does not meet the model's requirements |
403 | code: model_not_authorized_for_org | The image model is not enabled for your organization |
403 | type: image_routing_required | No capacity currently matches this request's parameters (size tier, quality, etc.), or routing does not match |
429 | type: rate_limit_exceeded | Organization image RPM exceeded (default 500 requests/minute unless configured otherwise); or code: BILLING_BLOCKED for insufficient balance |
451 | type: content_policy_rejected | Generated content failed the safety review |
503 | type: image_buffer_overload, header retry-after: 5 | Momentary overload |
504 | type: upstream_timeout | Image generation timed out |
{
"error": {
"message": "参考图片数量超出上限(最多 16 张)",
"type": "bad_request",
"param": "image"
}
}A reduced image count is not an error: if you request n images (max 4) and fewer can be delivered, the endpoint still returns 200 with the response header x-image-count-degraded: delivered/requested (e.g. 1/4). Always count images by data.length.
Retry Recommendations
Long requests such as image generation / editing and long-context chat can take a while. Set your client timeout to ≥ 600 seconds, or the client may disconnect while the server is still working.
400/401/402/403/451: request-side problems; retrying is pointless — fix and resend.429: checkcodefirst —BILLING_BLOCKEDneeds a top-up; otherwise read theretry-afterheader and back off before retrying (use exponential backoff when it is absent). Never retry in a tight loop.502/503/504: retry a limited number of times (≤ 3 recommended, exponential backoff), waiting forretry-afterwhen present.- When reporting an issue, keep the request ID from the response (such as the
x-gateway-request-idheader, if present) and the time it happened so support can locate it.