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 process right now — today that means REGION_UNAVAILABLE alone. Branching on error.code is more durable than branching on the status.

Region unavailability

CHATGPT and CLAUDE draw on pools of real accounts that are bound to a region. Before queuing, the API checks whether it can actually serve payload.country (defaulting to US) and rejects up front if it cannot:
This is a deliberate fast failure. Without it the task would be accepted and then spin, failing to acquire an account until it exhausted its retries — a slow, expensive way to learn the same thing. Query GET /capacity to see what each region can currently serve. Every other engine is stateless from an account perspective and is never rejected this way.

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 Cloro’s documented per-item error enum. Both are additions, not replacements — VALIDATION_ERROR and RESOURCE_ALREADY_EXISTS behave as Cloro specifies.

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".