Errors
Errors return a non-2xx status and a JSON body with a stable machine-readable type. Branch on type, not on the human-readable message — messages change, types do not.
{
"error": {
"type": "invalid_request",
"message": "The prompt field is required.",
"details": { "prompt": ["The prompt field is required."] }
},
"request_id": "req_01JCX8..."
}
Always log request_id. It is the fastest way for us to trace a failing call.
Status codes
| Code | type | Meaning | Retry? |
|---|---|---|---|
| 400 | invalid_request | Malformed body or failed validation | No — fix the request |
| 401 | unauthenticated | Missing, malformed or revoked key | No |
| 403 | forbidden | Key lacks the required ability, or plan excludes this model | No |
| 402 | insufficient_credits | Balance or key cap would be exceeded | After topping up |
| 404 | not_found | Unknown generation, model or provider | No |
| 422 | unprocessable | Valid shape, impossible values (e.g. unsupported size) | No |
| 429 | rate_limited | Key or account rate limit exceeded | Yes — honour Retry-After |
| 500 | server_error | Fault on our side | Yes, with backoff |
| 502 | provider_error | Upstream provider failed or timed out | Yes, with backoff |
| 503 | provider_unavailable | Provider disabled or over capacity | Yes, or switch provider |
Retry policy
Retry only 429, 500, 502 and 503. Retrying a 4xx other than 429 will fail identically every time.
import time, random, requests RETRYABLE = {429, 500, 502, 503} def call(url, headers, payload, attempts=4): for i in range(attempts): r = requests.post(url, headers=headers, json=payload, timeout=120) if r.status_code < 400: return r.json() if r.status_code not in RETRYABLE or i == attempts - 1: r.raise_for_status() wait = float(r.headers.get("Retry-After", 2 ** i)) + random.random() time.sleep(wait)
Failed generations
A 200 response can still carry status: "failed" — the request was
accepted and billed nothing, but the provider could not complete it. This is distinct from
a transport error. Check status on every generation, not just the HTTP code.
{
"id": "550e8400-...",
"status": "failed",
"error": "Provider rejected the prompt: content policy.",
"credits_used": 0
}
Provider content policies
Upstream providers apply their own safety filters. A prompt they decline surfaces as a
failed generation with the provider's reason, not as a GenzoAI error. If your workload
is being refused, trying a different provider is often the fastest fix —
policies differ between vendors.