Skip to main content
Failures share one envelope:
code is stable and safe to branch on. message is for humans and may change. details is present when we can name the offending field.

Codes

400 is for a request we cannot parse or understand. 422 is reserved for a request that is perfectly well-formed but that we cannot serve for the requested region. Branching on error.code is more durable than branching on the status.

Region rejections

Both region codes are decided before the task is queued, so you learn immediately instead of watching a task burn its whole retry budget and fail with a symptom-shaped error. REGION_UNSUPPORTED is permanent — that engine cannot serve the requested country:
See the table of unserveable combinations. REGION_UNAVAILABLE is transient — the route exists but nothing can serve it at the moment. Query GET /capacity to see what each region can currently serve.

Batch failures are per-item

A well-formed batch always returns 200, even if every task inside it failed. The HTTP status describes the request; results[].success describes each task.
Only a malformed request as a whole — non-JSON, not an array, empty, over 500 items, over 8 MB, or unauthorized — produces a 4xx for the batch itself.
A batch item can fail with REGION_UNAVAILABLE or ENQUEUE_ERROR, neither of which appears in the per-item error enum of the async task contract this API implements. Both are additions, not replacements — VALIDATION_ERROR and RESOURCE_ALREADY_EXISTS are unchanged.

Task-level failures

A task that fails after being queued does not produce an HTTP error at all. You learn about it from the terminal status:
Note the shape difference: this top-level error is a string, not the {code, message, timestamp} object used by request-level errors. It is the last engine error recorded before the task gave up. The same task delivers a webhook with task.status === "FAILED".