Python
retryable = {408, 429, 500, 502, 503}
if response.status_code == 429 and "Retry-After" in response.headers:
wait_seconds = min(float(response.headers["Retry-After"]), 30)
# Add jitter, cap attempts, and retry only idempotent work.Do not turn every 403 into a payment prompt or every 429 into an endless retry loop.
| HTTP | Code | Meaning | Recovery |
|---|---|---|---|
| 400 | invalid_request | Malformed or unsupported request | Fix the request |
| 401 | varies by legacy envelope | Missing, malformed, revoked, or invalid token | Fix Authorization; do not retry unchanged |
| 402 | payment_required, if emitted | Payment action required | Check Console balance; do not retry unchanged |
| 403 | insufficient_user_quota | Observed balance branch | Check balance or reduce request cost |
| 403 | other code | Model, group, or endpoint permission | Check access; do not show a funding prompt |
| 404 | model_not_found | Unknown or unavailable model ID | Read GET /v1/models |
| 408 | timeout | Request did not finish in time | Retry only safe work, with a cap |
| 429 | rate_limit_exceeded | Throughput limit | Honor Retry-After when present; add jitter |
| 500 / 502 / 503 | upstream or gateway failure | Transient service failure | Bounded backoff; save request ID; use a tested fallback |
retryable = {408, 429, 500, 502, 503}
if response.status_code == 429 and "Retry-After" in response.headers:
wait_seconds = min(float(response.headers["Retry-After"]), 30)
# Add jitter, cap attempts, and retry only idempotent work.The verified negative-balance response is 403 with insufficient_user_quota. Keep support for 402 as a payment branch only if the gateway emits it; never relabel an unrelated 403.
Send the request timestamp, model ID, HTTP status, error code, and request ID when available. Remove prompts and secrets. Never include an API key.