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

# Error codes

> The stable vocabulary to branch on, and what each code actually means.

Errors carry a consistent envelope:

```json theme={"system"}
{
  "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
  "code": "INVALID_PARAMS",
  "error": "orderId is required",
  "data": null
}
```

Branch on **`code`**. It is a stable wire contract in `SCREAMING_SNAKE_CASE`.
`error` is prose for a developer reading a log — its wording can change, and it
is not written to be shown to a payer.

`traceId` identifies this exact request in our logs. Include it when you
[contact support](/resources/support).

## Client errors

| Code                        | HTTP | Means                                                                                      |
| --------------------------- | ---- | ------------------------------------------------------------------------------------------ |
| `BAD_REQUEST`               | 400  | Malformed request — bad JSON, wrong type, missing body.                                    |
| `INVALID_PARAMS`            | 400  | Well-formed but a value is unacceptable. `error` names the field.                          |
| `UNSUPPORTED_CHAIN`         | 400  | Chain is not supported, or not in this key's environment.                                  |
| `UNAUTHORIZED`              | 401  | Missing, malformed or unknown key.                                                         |
| `FORBIDDEN`                 | 403  | Valid key without rights — wrong key type, or product not enabled.                         |
| `RECIPIENT_NOT_ALLOWLISTED` | 403  | Checkout `recipient` is not on your payout allowlist. Add it in the portal.                |
| `NOT_FOUND`                 | 404  | No such resource, or not yours.                                                            |
| `PAYMENT_NOT_FOUND`         | 404  | No session with that id.                                                                   |
| `NOT_CONFIGURED`            | 404  | A feature your account has not been set up for.                                            |
| `CONFLICT`                  | 409  | Conflicts with current state.                                                              |
| `PAYMENT_EXPIRED`           | 409  | Session passed `expiresAt`. Create a new one.                                              |
| `PAYLOAD_TOO_LARGE`         | 413  | Request body too big — usually oversized `metadata`.                                       |
| `AMOUNT_OUT_OF_BOUNDS`      | 422  | Below the minimum or above the maximum.                                                    |
| `PAYOUT_TOKEN_NOT_ALLOWED`  | 422  | That token is not permitted as a destination.                                              |
| `UNSUPPORTED_ROUTE`         | 422  | We cannot serve this asset pair **at all**. Permanent — do not retry; offer another asset. |
| `INSUFFICIENT_LIQUIDITY`    | 422  | No route can fill this size right now.                                                     |
| `OVER_DELIVERY_CAP`         | 422  | Amount exceeds the per-transaction cap on your account.                                    |
| `RATE_LIMITED`              | 429  | Slow down and retry with backoff.                                                          |

<Note>
  **403 vs 404 is deliberate.** Reading a session belonging to another partner
  returns `NOT_FOUND`, not `FORBIDDEN` — telling you a session exists but is not
  yours would leak that it exists. Do not read a 404 as proof the id is invalid.
</Note>

## Server and upstream errors

| Code                            | HTTP | Means                                                                 |
| ------------------------------- | ---- | --------------------------------------------------------------------- |
| `INTERNAL_ERROR`                | 500  | Our bug. Send the `traceId`.                                          |
| `UPSTREAM_ERROR`                | 502  | A provider we depend on failed.                                       |
| `NO_QUOTE_AVAILABLE`            | 502  | No provider returned a usable route.                                  |
| `QUOTE_STALE`                   | 502  | The quote expired before it could be used. Re-quote.                  |
| `SIMULATION_REVERTED`           | 502  | The route simulated as failing, so it was not offered.                |
| `SERVICE_UNAVAILABLE`           | 503  | Temporarily unable to serve.                                          |
| `ROUTE_TEMPORARILY_UNAVAILABLE` | 503  | No route for this asset **just now**. Transient — retry with backoff. |
| `KILL_SWITCH_ACTIVE`            | 503  | A feature is deliberately disabled.                                   |
| `CIRCUIT_BREAKER_OPEN`          | 503  | Repeated upstream failures tripped a breaker.                         |
| `UPSTREAM_TIMEOUT`              | 504  | A provider did not answer in time.                                    |

## Routing: `422` vs `503`

The split is deliberate and worth branching on. `UNSUPPORTED_ROUTE` (`422`) means
the asset pair is one we **cannot** serve — retrying reproduces it, so surface a
different asset to the payer. `ROUTE_TEMPORARILY_UNAVAILABLE` (`503`) means the
pair is supported but has **no route right now** — retry with backoff and it may
clear on its own. Same failure surface, opposite response.

## Retrying

<AccordionGroup>
  <Accordion title="Safe to retry — 429, 502, 503, 504">
    Transient. Use exponential backoff with jitter. `QUOTE_STALE` and
    `NO_QUOTE_AVAILABLE` are worth re-quoting rather than replaying the same
    request.
  </Accordion>

  <Accordion title="Do not retry unchanged — 400, 401, 403, 404, 409, 413, 422">
    The request is wrong, or the state is. Retrying reproduces the error.
  </Accordion>

  <Accordion title="Session creation is already idempotent">
    Retrying a create with the same `orderId` returns the EXISTING session with
    `200` and `created: false` rather than opening a second one — so a retry
    after a timeout cannot double-charge.
  </Accordion>
</AccordionGroup>

## `OK`

`OK` is the success sentinel, returned alongside `error: null`. It is in the
same enum so that one field can be checked uniformly; it is not an error.
