> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mnemom.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Responses

> The canonical error-response contract: status codes, named codes, retry semantics, and how Safe House interventions show up on the wire.

This page is the **canonical reference** for everything a client integration needs to know about Mnemom API errors: which status codes the API emits, what each one means, the named error codes ([Stripe-style](https://stripe.com/docs/error-codes)) clients should branch on, retry semantics, and how Safe House content interventions show up on the wire.

## Response shape

Every error response carries the same body shape:

```jsonc theme={null}
{
  "error": {
    "code": "bad_request",              // stable snake_case identifier
    "message": "Invalid JSON body",     // human-readable summary
    "details": { /* optional */ }       // structured per-code data when present
  }
}
```

`code` defaults to a status-class value (`bad_request`, `unauthorized`, `forbidden`, `not_found`, `conflict`, `payload_too_large`, `unprocessable_entity`, `rate_limited`, `internal_error`, `service_unavailable`, …) derived from the HTTP status. A number of endpoints enrich it with a more specific code for a particular failure (e.g. `auth_required`, `invalid_hash_proof`) — see the per-status sections below for the ones worth branching on. 5xx responses additionally carry `request_id` inside `error` for support correlation.

REST calls to `api.mnemom.ai` also always carry an **`X-Request-Id`** response header (success or error) — paste it into a support ticket to pull the server-side logs for that request. This is distinct from `X-Mnemom-Request-Id`, which only appears on **gateway-routed** (LLM-proxy) responses — see [Headers](/api-reference/headers#x-mnemom-request-id--support-correlation) for the split. On gateway-routed responses, `X-Mnemom-Verdict` is also always present (see [headers](/api-reference/headers#x-mnemom-verdict--structured-per-checkpoint-state)) — even on a 4xx, it reflects what Safe House observed before the error fired. `Retry-After` is present on `429` and maintenance/some `503` responses.

Branch on `error.code`, not on `error.message`. Messages are human-facing and may evolve; codes are stable contract.

## Status codes at a glance

| Status | Meaning | Retryable? |
| - | - | - |
| **`400 Bad Request`** | Request shape / validation problem. | No — fix the request and resubmit. |
| **`401 Unauthorized`** | Missing or invalid credentials. | No — re-authenticate. |
| **`402 Payment Required`** | Billing is unconfigured for the org, or the µ balance is depleted (gateway-routed calls). | No — resolve billing, then retry. |
| **`403 Forbidden`** | Authenticated, not authorized for the requested resource or action — or the agent is contained (`paused`/`killed`, gateway-routed calls). | No. |
| **`404 Not Found`** | Resource doesn't exist. | No. |
| **`409 Conflict`** | Concurrent modification / already-exists conflict. | Yes — re-read, re-modify, re-submit. |
| **`413 Payload Too Large`** | Request body exceeded the size budget. | No — split or shrink the payload. |
| **`422 Unprocessable Entity`** | Request structurally valid but semantically rejected. | No — see the response body for why. |
| **`429 Too Many Requests`** | Rate limit hit. `Retry-After` header tells you when to retry. | Yes — back off per `Retry-After`. |
| **`500 Internal Server Error`** | Unexpected server-side condition. | Limited — see retry guidance below. |
| **`503 Service Unavailable`** | A downstream LLM provider is unhealthy (gateway-routed calls only), or a declared maintenance window has writes paused. `Retry-After` is set. | Yes — back off per `Retry-After`, default to exponential backoff if absent. |
| **`529 Site is Overloaded`** | Gateway saturation (gateway-routed calls only — the Anthropic-shaped `overloaded_error` passed through). | Yes — back off per `Retry-After`. |

## 400 Bad Request

Most 400s carry the default `bad_request` code with a message describing the specific validation failure (missing required field, malformed value, body that isn't parseable JSON). A few endpoints use a more specific code:

| Code | When |
| - | - |
| `invalid_request` | Malformed request body — used broadly, including on OAuth 2.1 token/registration endpoints (RFC 6749/7591 error vocabulary). |

If you see an unfamiliar 400, capture the `X-Request-Id` and open a support ticket.

## 401 / 403 / 404 — auth and ownership

| Code | Status | When |
| - | - | - |
| `unauthorized` | 401 | Default — missing or invalid credentials. |
| `auth_required` | 401 | Explicit variant used on session-dependent endpoints when no credentials were presented at all. |
| `forbidden` | 403 | Default — authenticated but not authorized for this resource or action. |
| `aal2_required` | 403 | The action (e.g. removing a passkey, rotating an API key) requires a fresh **AAL2 step-up**; re-verify and retry. See [Authentication — AAL2 step-up](/guides/authentication#aal2-step-up). |
| `invalid_hash_proof` | 403 | The `hash_proof` supplied to prove agent-key possession (claim, link, rekey) doesn't match. |
| `not_found` | 404 | Default — the resource doesn't exist, or doesn't belong to the caller's org (Mnemom does not distinguish the two, to avoid leaking existence across orgs). |

**Agent containment (403).** A `paused` or `killed` agent gets every gateway-routed request rejected with a distinct code:

```json theme={null}
{
  "error": {
    "code": "containment_error",
    "message": "Agent contained",
    "details": { "reason": "agent_paused" }
  }
}
```

`details.reason` is `agent_paused` or `agent_killed`. Checked before any billing evaluation, so a contained agent always sees `403`, never `402`. See [Agent containment](/gateway/enforcement#agent-containment) for the containment API and lifecycle.

## 402 Payment Required — billing and depletion

Gateway-routed requests (`gateway.mnemom.ai`) can be rejected for two distinct billing reasons, both under the `billing_error` code, distinguished by `details.reason`:

| `details.reason` | When |
| - | - |
| `billing_unconfigured` | The org's billing account could not be resolved (e.g. no payment method configured). Checked first — always wins over a depleted balance for the same org. |
| `balance_depleted` | The org's µ balance is determined to be at or below zero. The gateway fails closed: the request is rejected rather than proxied to the model. |

```json theme={null}
{
  "error": {
    "code": "billing_error",
    "message": "Billing is not configured for this organization. Your organization admin has been notified — no action is needed here.",
    "details": { "reason": "billing_unconfigured" }
  }
}
```

An unresolved balance (a cache miss, not a confirmed depletion) fails **open** — it is never mistaken for `balance_depleted`. Add a payment method / top up µ, then retry; this is not a transient condition, so retrying immediately without resolving billing will keep failing. See [Pricing](https://www.mnemom.ai/pricing) for how µ balances and auto top-up work.

## 422 — semantically rejected

`422` covers a request that was well-formed and authorized but rejected on its content — e.g. an out-of-bounds index on a Merkle-proof lookup, or a value that fails a downstream validation rule specific to that endpoint. The response body's `details` (when present) carries the specifics; there is no single named code shared across every 422 — check the endpoint's own reference page.

Safe House content interventions do **not** go through this status — see [Safe House interventions](#safe-house-interventions-are-not-error-responses) below.

## 429 — rate limits

`429` responses always carry a `Retry-After` header. The value is **seconds** (integer per [RFC 9110 §10.2.3](https://datatracker.ietf.org/doc/html/rfc9110#section-10.2.3)):

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 4
X-Request-Id: 8f446ed6-ca87-4c1d-aa90-e2bc6e9ef580

{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded"
  }
}
```

See [Rate limits](/api-reference/overview#rate-limits) for the actual ceilings and which counter (per-IP, auth-surface, or per-user/org LLM budget) a given endpoint is subject to.

**Idempotent retries.** When retrying after a 429, include an `Idempotency-Key` header — if the request actually went through and the API is reporting back the 429 (rare but possible mid-failure), the second attempt is idempotent on key match.

## 5xx / 529 — server-side, maintenance, and saturation

| Status | When | Retry pattern |
| - | - | - |
| **`500`** | Unexpected server condition. | Limited retry — at most once, immediately. If 500 persists, do not retry-loop; capture `X-Request-Id` and file a ticket. |
| **`503`** (`maintenance`) | A declared, scheduled write-freeze window. Reads keep working; writes are paused. Body `details` carries `reads`/`writes` status and, when known, `window_ends_at` and `status_page`. The response also carries `X-Mnemom-Maintenance: 1` so you can distinguish a declared window from an incidental outage without parsing the body. | Honor `Retry-After` (minimum 30s). |
| **`503`** (gateway-routed calls) | A downstream LLM provider returned 5xx or timed out. | Exponential backoff, starting at 1s. Honor `Retry-After` when present. |
| **`529`** (gateway-routed calls) | Gateway-saturation overload — an Anthropic-shaped `overloaded_error` passed through. `Retry-After` is set. | Honor `Retry-After`. Treat 529 as "back off and re-route through the same endpoint when the window expires," not "permanent failure." |

The 503/529 distinction on gateway-routed calls matters for your fallback strategy: 503 means the gateway is fine but the LLM provider isn't; 529 means the gateway itself is shedding load. A client that has a backup provider should switch on 503 but not on 529.

## Safe House interventions are not error responses

The Safe House front door screens every inbound surface a gateway-routed request carries — the inbound message, and each tool result being handed back to the model — and emits a verdict (`pass | observed | nudged | enforced`) per [`X-Mnemom-Verdict`](/api-reference/headers#x-mnemom-verdict--structured-per-checkpoint-state). In `enforce` mode, an offending inbound message or tool result is **replaced in place** with a quarantine notice (or redacted, on the back door) and the — possibly modified — request still proceeds to the upstream LLM provider:

| Verdict | HTTP status | Notes |
| - | - | - |
| `pass` | 200 | Clean. |
| `observed` | 200 | Detector matched, but mode is `observe`. Verdict logged, nothing changed. |
| `nudged` | 200 | Advisory injected into the agent's prompt context; `X-Mnemom-Advisory` carries the entries. |
| `enforced` | 200 | The offending content (inbound message, tool result, or outbound response) was replaced/redacted same-turn. The request still completes. |

**This holds for the inbound message itself, not only for tool results** — front-door enforcement never turns your chat/completion call into a 4xx. Do not read a 200 as "no front-door action this turn": read `X-Mnemom-Verdict`, not the status code, to know whether an intervention happened.

The `X-Mnemom-Verdict` response header reflects the full per-checkpoint state even on error responses that arise for unrelated reasons (e.g. a 401) — useful for distinguishing "front-door intervention" (`front=enforced`) from "agent's own integrity intervention" (`integrity=enforced`).

A quarantined message is also logged for asynchronous review: fetch it via `GET /v1/safe-house/quarantine/{quarantine_id}` or, with operator role, release it via `POST /v1/safe-house/quarantine/{quarantine_id}/release`. This is a separate, async surface from the synchronous same-turn replacement above.

<Note>
  **`integrity=unverified` does not get its own status code.** Unlike the Safe House front/back door, the `integrity` checkpoint has a fifth value, `unverified` — the analyzer timed out, errored, or the circuit breaker was open, so no trustworthy verdict was produced. This is never reported as `pass`. In `integrity_mode: enforce`, the response is withheld and replaced same-turn (fail-closed), the same way an `enforced` intervention is — **still a 2xx**, not a dedicated 4xx/5xx, since the intervention is delivered as the response body rather than as an error. In `observe`/`nudge`, the response is forwarded unmodified and the `unverified` state is recorded for post-hoc review. See [`X-Mnemom-Verdict`](/api-reference/headers#x-mnemom-verdict--structured-per-checkpoint-state) for the full semantics.
</Note>

## Retry semantics summary

| Class | Retry? | Honor `Retry-After`? | Use `Idempotency-Key`? |
| - | - | - | - |
| 4xx (except 409 / 429) | No | n/a | Idempotency-Key is still safe — guarantees the same response on retry instead of a different validation failure. |
| 409 Conflict | Yes (re-read first) | n/a | Yes. |
| 429 | Yes | **Yes** | Yes. |
| 500 | Once, immediately | n/a | Yes. |
| 503 | Yes, exponential backoff | Yes (when present) | Yes. |
| 529 | Yes | **Yes** | Yes. |

`Idempotency-Key` semantics + the `Idempotent-Replay: true` response echo are documented in the [Headers reference](/api-reference/headers).

## See also

* [Headers](/api-reference/headers) — request-id, verdict structure, retry headers.
* [Safe House](/concepts/safe-house) — the four-mode protection contract and three-layer detection model.
* [API Versioning Policy](/policy/versioning) — what we will (and won't) change about the error surface across versions.
* [API Overview](/api-reference/overview) — base URL, auth, rate-limit headers.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.