Errors
Every error response uses the same envelope with a code from a small, closed set — so you can branch on the code reliably.
The error envelope
All errors return a JSON body of this shape:
{
"error": {
"code": "validation_failed",
"message": "effectiveDate: must match ^\\d{4}-\\d{2}-\\d{2}$",
"retryable": false
}
}
code— one of the closed set below. Branch on this, not on the message.message— a human-readable explanation. Wording may change; don't parse it.retryable—trueif retrying the exact same request may succeed later (a transient condition);falseif it never will without a change on your side.
Error codes
| Code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid access token. Get a fresh token and retry. |
forbidden | 403 | The token is valid but lacks a required permission, or an
x-principal-id was invalid. See
Authentication. |
not_found | 404 | The resource doesn't exist, or isn't visible to your account.
Returned in place of 403 where confirming existence would
itself leak information. |
validation_failed | 400 | The request failed schema validation (a bad field, wrong type,
missing required value). message names the offending field.
Not retryable without fixing the request. |
conflict | 409 | The request conflicts with existing state — for example an
Idempotency-Key reused with a different body, or a duplicate
submission reference. See Async mutations. |
internal_error | 500 | An unexpected server-side error. retryable is
true; retry with backoff. |
rate_limited | — | Reserved for future use. The current API does not emit this code; it's included so clients can handle it defensively without a breaking change if rate limiting is introduced later. |
The set of codes is closed and stable: the API will not
introduce a new code value within v1 without a version change.
You can safely write an exhaustive switch over these values. Handling an
unknown code gracefully (treating it like internal_error) is
still good practice.
Errors on async mutations
Creating a job (for example POST /v1/submissions) can fail at
two different times, and the same envelope is used for both:
- At request time — validation, auth, or conflict problems are returned immediately with the appropriate code above, and no job is created.
- At processing time — a job that was accepted but then
fails carries the same
errorobject (code,message,retryable) inside its settled result when you pollGET /v1/jobs/{id}. See Async mutations.