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

# Errors

> One envelope everywhere, a small set of codes to branch on, and the one place where the codes are the banking partner's own.

Every error the API produces has the same shape, and every response, error or
not, starts with the `environment` that answered. This page is the whole error
story: the envelope, the codes, what to branch on, and the one deliberate
exception.

## The envelope

```json theme={null}
{
  "environment": "sandbox",
  "error": {
    "code": "chain_not_allowed",
    "message": "Human-readable explanation",
    "fields": { "applicantInfo.nationality": "iso3166_1_alpha2" }
  }
}
```

* `code` is what you branch on. It is stable.
* `message` is for your logs, not your users. Its wording may change.
* `fields` appears only when a bank endpoint refused a specific input, and it
  names the field, never the value.
* `environment` says which upstreams answered: `sandbox` or `production`. Read
  it off any response you paste into a ticket.

Every endpoint page in the [API reference](/api-reference/introduction) lists
the codes that endpoint can answer, per HTTP status.

## The codes

| HTTP | `code`                 | What happened                                                                                                   | What to do                                                          |
| ---- | ---------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| 400  | `invalid_request`      | A field is missing or malformed, or the body is not valid JSON. The message names the field.                    | Fix the request. Retrying the same body will not help.              |
| 400  | `no_route`             | No provider can route that pair or currency right now.                                                          | A normal, temporary answer. Re-quote later; do not retry in a loop. |
| 400  | `quote_expired`        | The quote is past its `validUntil`.                                                                             | Request a fresh quote and build from that one.                      |
| 401  | `unauthorized`         | Missing, malformed or unknown API key. We do not say which.                                                     | Check the header and the key.                                       |
| 403  | `permission_denied`    | Valid key, but it does not carry the product this endpoint needs.                                               | Ask us to add the product to your key.                              |
| 403  | `chain_not_allowed`    | Valid key, but not enabled for that chain on that side.                                                         | Ask us to enable the corridor. Retrying will not help.              |
| 403  | `token_not_allowed`    | Valid key, but not enabled for that token on that chain.                                                        | As above.                                                           |
| 403  | `execution_capped`     | The transfer exceeds the key's execution ceiling in USD. The key's policy, not liquidity.                       | Quote a smaller amount, or ask us to raise the ceiling.             |
| 403  | `quote_not_verified`   | The quote does not verify, or was issued to a different key.                                                    | Build only from a quote this key received.                          |
| 404  | `not_found`            | No such endpoint, or no such thing behind the id.                                                               | Check the path and the id.                                          |
| 404  | `order_not_found`      | Unknown or malformed order id, or an order another key built.                                                   | Ask with the id the create call returned.                           |
| 409  | `transaction_conflict` | This order already follows another transaction hash.                                                            | Report each order once.                                             |
| 429  | `rate_limited`         | Over your key's requests per second.                                                                            | Back off, then retry.                                               |
| 500  | `internal`             | Unexpected server error. Details are never leaked.                                                              | Retry with backoff; tell us if it persists.                         |
| 501  | `not_implemented`      | This deployment cannot answer that call, for instance reading a virtual account back where `readBack` is false. | Keep the details the create call returned.                          |
| 502  | `upstream_error`       | A provider behind the rail is unavailable or answered badly.                                                    | Retry with backoff.                                                 |
| 503  | `not_configured`       | This deployment has no provider configured for that product.                                                    | Contact us.                                                         |

The bank onboarding flow adds a few of its own on the consent calls:
`consent_session_invalid`, `consent_declined`, `consent_link_failed` and, on the
sandbox identity call, `kyc_provider_refused`. Each is listed on the endpoint
page that answers it.

## What to branch on

* **Branch on `code` and the HTTP status.** Never on `message`: its wording is
  not part of the contract.
* **Three are worth handling on their own.** `no_route` is temporary: re-quote
  later. `rate_limited` means back off, not retry at once. `chain_not_allowed`
  means the corridor was never yours, so no retry will fix it.
* **A 4xx on a create call created nothing.** There is no order or payout to
  clean up; fix the request and call again.
* **Do not enumerate the codes for the user.** Show your own copy per branch
  and keep `code` and `message` in your logs for the support ticket.

## The exception: bank transfers and virtual accounts

On the bank transfer and virtual account endpoints the envelope is the same, but
`code` and `fields` inside it are the banking partner's own, passed through
unchanged rather than mapped to the table above. The set is theirs and it can
grow, so there:

* branch on the HTTP status first,
* log `code` as a string rather than matching it against a fixed set,
* read `fields` to know which input to fix; it names the field, not the value.

```json theme={null}
{
  "environment": "sandbox",
  "error": {
    "code": "<the banking partner's code>",
    "message": "Human-readable detail from the banking partner",
    "fields": { "applicantInfo.nationality": "iso3166_1_alpha2" }
  }
}
```

The flows this applies to are on [On-ramp](/products/ramps/on-ramp) and
[Virtual accounts](/products/virtual-accounts).

## Related

* Which products, chains and tokens your key carries, and why a request is
  refused rather than defaulted: [Authentication](/api-reference/authentication).
* Rate limits and what your key's ceiling means for your own users:
  [Rheon API](/integration/api#rate-limits).
