Errors
Every error response uses one envelope:
{ "error": { "code": "not_found", "message": "agent 00000000-... not found" } }
code is a stable, machine-readable string you can branch on. It is
deliberately not the HTTP status number: the status is already in the status line,
and it cannot distinguish two different failures that share it. 422 covers both a
malformed body and a reused idempotency key, and those need different handling.
message is for humans and logs. Do not parse it.
Codes
| HTTP | code |
Meaning | What to do |
|---|---|---|---|
| 400 | bad_request |
Rejected by us or by an upstream provider. The message carries the provider's own text. | Read the message. Provider capacity and policy errors land here. |
| 401 | unauthorized |
Missing, malformed, invalid or revoked credential. | Check the bearer. Revoked keys are indistinguishable from wrong ones, on purpose. |
| 403 | forbidden |
Authenticated, but not permitted. | Most commonly: an API key tried to mint an API key. Use a console session. |
| 404 | not_found |
No such resource in your organization. | A resource in another tenant also returns 404 — existence is not disclosed. |
| 405 | method_not_allowed |
Wrong verb. | — |
| 409 | idempotency_in_progress |
A request with this Idempotency-Key is still running. |
Retry after a short backoff. Do not change the body. |
| 422 | validation_error |
Body failed validation. | Fix the request. |
| 422 | idempotency_key_reuse |
Same key, different body. | A key is bound to one request. Use a new key. |
| 422 | idempotency_key_invalid |
Malformed key. | — |
| 429 | rate_limited |
Over 120 requests/minute for the organization. | Back off. Per-org, so more keys will not help. |
| 500 | internal_error |
Our fault. | Retry with backoff. Report if it persists. |
| 503 | unavailable |
A required subsystem is off. | x402 routes return this when the paid surface is not configured — closed rather than free. |
402 is not an error
402 on an /x402/* route is the protocol working. It carries signable payment
terms, not a failure. See x402.
Notable behaviours
404 hides existence. Requesting an agent that belongs to another organization returns the same 404 as one that never existed. This is intentional: distinguishing them would let anyone enumerate other tenants' resources.
Revoked and invalid are the same 401. For the same reason.
Upstream errors are not laundered. When an email provider refuses to create an
inbox, you get 400 with the provider's actual message. The alternative — swallowing
it and storing a dead address — fails later, further away, and harder to diagnose.
Bookkeeping never fails a request. If a payment settles but the ledger write
fails, the request still returns 200. The action happened and the money moved;
reporting failure would be the false answer.
Retrying safely
Unsafe POSTs accept an Idempotency-Key header. Send the same key on a retry and
you get the first request's response instead of a second agent, a second wallet,
or a second payment.
curl -X POST $BASE/agents \
-H "Authorization: Bearer $INCARNA_KEY" \
-H 'Idempotency-Key: 8f14e45f-ea6a-4f7e-9c1b-1a2b3c4d5e6f' \
-H 'Content-Type: application/json' \
-d '{"name":"..."}'
Rules worth knowing:
- Records expire after 24 hours.
- A key is bound to the body it first saw. Reusing it with a different body is
422 idempotency_key_reuse, not a silent replay. - Concurrency is resolved in the database, so two simultaneous retries cannot both
do the work. The loser gets
409 idempotency_in_progress.
Degraded is not an error
An agent in degraded returns 200 like any other. It means a provisioning job ran
out of retries — recoverable, and it recovers on its own. See the
lifecycle.