Handle errors and retries
Check the HTTP status before reading output. A failed request does not contain an answer to use.
Classify the failure
| Response | Action |
|---|---|
401 | Fix the API key; do not retry unchanged. |
403 POLICY_DENIED | The policy denied every candidate; do not retry unchanged. |
403 BUDGET_EXHAUSTED, PREVIEW_UNAVAILABLE, SCHEMA_LIMIT_REACHED | Account or key limit; contact your administrator. |
404 SCHEMA_NOT_FOUND, POLICY_NOT_FOUND | Check the reference and the key that owns it. |
409 VERSION_CONFLICT | Publish a new immutable version, or reconcile an alias change. |
409 OUTCOME_INDETERMINATE | The request may have run. Do not retry automatically; read a registration or alias back first. |
413 REQUEST_TOO_LARGE | Send a smaller request. |
422 POLICY_SEARCH_EXHAUSTED | No allowed result within budget; review the task and policy before another bounded attempt. |
422 CONSTRAINT_SEARCH_EXHAUSTED | No schema-compliant result within budget; simplify the schema or the task before another attempt. |
Other 422 | Fix the request, schema or policy. A detail list names the invalid fields; error.code names other problems. |
429 RATE_LIMITED, CONCURRENCY_LIMITED | Wait for Retry-After, then retry. See rate limits. |
429 POLICY_CAPACITY_BUSY | Pause briefly, then retry. |
503 | The service is unavailable. Wait for Retry-After when it is sent, then retry within a bounded deadline. |
529 CAPACITY_BUSY | Wait for Retry-After, then retry with backoff. |
504 CAPACITY_BUSY | Retry with exponential backoff and jitter. |
See the error reference for every code.
Read the error body
Errors have three body shapes: {"detail": "..."} for 401, a detail list for request validation 422, and an error object for everything else. Every response has an X-Blatent-Request-ID header; log it and include it in support requests. These functions return the same summary for all three:
Python
import httpx
def read_error(response: httpx.Response) -> dict:
"""Summarize a failed response for logs, retries and support requests."""
try:
body = response.json()
except ValueError: # a firewall 403 has no JSON body
body = {}
error = body.get("error", {})
detail = body.get("detail")
if isinstance(detail, list): # 422: the request format is invalid
message = "; ".join(".".join(map(str, item["loc"])) + ": " + item["msg"] for item in detail)
else: # 401 sends a detail string; other errors send an error object
message = detail or error.get("message") or response.reason_phrase
return {
"status": response.status_code,
"code": error.get("code"),
"message": message,
"path": error.get("path", ""),
"request_id": response.headers.get("X-Blatent-Request-ID"),
"retry_after": response.headers.get("Retry-After"),
}
TypeScript
async function readError(response: Response) {
// A firewall 403 has no JSON body.
const body = await response.json().catch(() => ({}));
const error = body.error ?? {};
const message = Array.isArray(body.detail) // 422: the request format is invalid
? body.detail.map((item: {loc: (string | number)[]; msg: string}) => `${item.loc.join(".")}: ${item.msg}`).join("; ")
: body.detail ?? error.message ?? response.statusText; // 401 sends a detail string
return {
status: response.status,
code: error.code ?? null,
message,
path: error.path ?? "",
request_id: response.headers.get("X-Blatent-Request-ID"),
retry_after: response.headers.get("Retry-After"),
};
}
Call it when response.is_error (Python) or !response.ok (TypeScript) is true. code is null for 401 and request validation 422.
Bound retries
Use a total deadline and a small attempt limit. The service sends Retry-After, in seconds, with 429 RATE_LIMITED, 429 CONCURRENCY_LIMITED, 503 RUNTIME_UNAVAILABLE and 529 CAPACITY_BUSY; wait at least that long. For other retryable failures, use exponential backoff with random jitter so clients do not retry together. The service has no idempotency key.
A network timeout can leave the outcome unknown. Retrying generation may repeat work and is not a guarantee of the same answer. Keep external actions in your application and make them idempotent where required.
Reconcile registration and aliases
Registering identical content under the same version is idempotent. To update an existing alias safely, send its observed expected_digest; a concurrent change returns 409 VERSION_CONFLICT. After 409 OUTCOME_INDETERMINATE, the write may have been applied: read it back, or register the identical content again, before deciding what to do.
HTTP client behavior in the examples
Python calls HTTPX’s raise_for_status() before reading an answer; it raises httpx.HTTPStatusError, whose response you can pass to read_error. TypeScript checks response.ok and throws with the status and body. cURL uses --fail-with-body, which prints the body and exits with an error. The examples do not add automatic retries.
Python’s timeout=195 limits individual network operations, not the total wall-clock request. TypeScript uses AbortSignal.timeout(195_000). Use a client timeout of at least 195 seconds: some requests take more than a minute, and a shorter timeout can abandon a request that would succeed. Handle output-validation failures separately from HTTP errors.