Skip to main content

Handle errors and retries

Check the HTTP status before reading output. A failed request does not contain an answer to use.

Classify the failure​

ResponseAction
401Fix the API key; do not retry unchanged.
403 POLICY_DENIEDThe policy denied every candidate; do not retry unchanged.
403 BUDGET_EXHAUSTED, PREVIEW_UNAVAILABLE, SCHEMA_LIMIT_REACHEDAccount or key limit; contact your administrator.
404 SCHEMA_NOT_FOUND, POLICY_NOT_FOUNDCheck the reference and the key that owns it.
409 VERSION_CONFLICTPublish a new immutable version, or reconcile an alias change.
409 OUTCOME_INDETERMINATEThe request may have run. Do not retry automatically; read a registration or alias back first.
413 REQUEST_TOO_LARGESend a smaller request.
422 POLICY_SEARCH_EXHAUSTEDNo allowed result within budget; review the task and policy before another bounded attempt.
422 CONSTRAINT_SEARCH_EXHAUSTEDNo schema-compliant result within budget; simplify the schema or the task before another attempt.
Other 422Fix the request, schema or policy. A detail list names the invalid fields; error.code names other problems.
429 RATE_LIMITED, CONCURRENCY_LIMITEDWait for Retry-After, then retry. See rate limits.
429 POLICY_CAPACITY_BUSYPause briefly, then retry.
503The service is unavailable. Wait for Retry-After when it is sent, then retry within a bounded deadline.
529 CAPACITY_BUSYWait for Retry-After, then retry with backoff.
504 CAPACITY_BUSYRetry 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.