# Errors

Every error response uses one envelope:

```json
{ "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](x402.md).

## 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 `POST`s 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.

```sh
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](concepts.md#status-lifecycle).
