Errors & limits
Every failure has the same shape, a machine-readable type and a message written for a person. Branch on the type; show the message.
#The error shape
{
"error": {
"type": "invalid_request",
"message": "Invalid request body.",
"details": {
"properties": {
"message": {
"errors": [
"Required"
]
}
}
}
},
"request_id": "9b2f0c1e-5d47-4a8e-b3a6-2c1d0e9f8a77"
}| Field | What it is |
|---|---|
error.type | One of the types below. Stable — branch on this. |
error.message | A sentence for a person. Safe to show; may change wording. |
error.details | Present when the body or query failed validation: which field, and why. |
request_id | Also sent as the X-Request-Id header on every response. |
#Error types
| type | Status | Means | Do |
|---|---|---|---|
invalid_request | 400 · 405 · 422 | The request doesn't match the schema, or a value was refused (a record field, an agent plan that isn't ready). | Fix the field named in details; don't retry unchanged. |
authentication | 401 | The key is missing, malformed or revoked. | Check the Authorization header. |
insufficient_credits | 402 | The workspace is out of credits. | Stop; someone tops up in Settings → Billing. |
permission | 403 | The key may not do this. | Don't retry. |
not_found | 404 | No such id in this key's workspace. | Re-list to find the right one. |
conflict | 409 | The state changed, or an Idempotency-Key was reused with a different body. | Re-read, then retry with a new key. |
rate_limited | 429 | Too many requests. | Wait (Retry-After seconds when present), then retry with the same key. |
server_error | 5xx | Something failed on Attria's side. | Retry with backoff and the same Idempotency-Key. |
#Failed turns
Refusals before any work starts — a bad body, an unknown id, no credits — are ordinary HTTP errors. Once a turn has been claimed, a failure comes back as the turn itself, with status: "failed" and an error. Handle both: HTTP errors for work that never started, failed for work that did.
In a stream, a failure arrives as an error event with message and request_id, and the stream ends.
#Rate limits
Past the rate limit you'll get 429 with type rate_limited. Wait — at least Retry-After seconds when the header is present — then retry with the same Idempotency-Key.
Long work doesn't need many requests. One stream: true call replaces a polling loop; Jobs & streaming shows both.
#Credits
API calls spend the workspace's credits exactly as the same work in the app does — no separate plan. When they run out, calls that make something return 402 insufficient_credits until the workspace is topped up.
#Request ids
Every response carries an X-Request-Id header, and errors repeat it as request_id. Log it, and quote it when something looks wrong. You may send your own X-Request-Id to tie a call to your logs.
HTTP/2 404
content-type: application/json
x-request-id: 9b2f0c1e-5d47-4a8e-b3a6-2c1d0e9f8a77