> For a complete index of Agora Finance documentation, fetch https://docs.agora.finance/llms.txt

# Errors

[← Switch to the Production API](/api)

> **Warning**
>
> **This is the sandbox.** It runs on testnets only. **Never send real money or real tokens to an address or bank account the sandbox returns.**
>
> The sandbox is subject to the Beta Features section of our [Terms of Use](https://static.agora.finance/termsofuse.pdf).

Sandbox errors use the same codes and shapes as production. A few behave differently:

* **Dashboard fixes.** The sandbox has no dashboard yet. Where a fix below says to use the dashboard, contact Agora instead.
* **`direction_unsupported`** is returned for any route pair outside the [four the sandbox supports](/api/sandbox/endpoints/routes/overview#supported-pairs).
* **`route_creation_unavailable`** means the sandbox ran out of deposit addresses. It refills, so retry shortly.

## Error response shape

Every error response shares a common JSON shape:

```json
{
  "code": "rate_limit_exceeded",
  "message": "Too many requests.",
  "docs_url": "https://docs.agora.finance/api/errors"
}
```

| Field      | Type              | Description                                                                                                                                                                               |
| ---------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `code`     | string            | Machine-readable error code. Stable — safe to match on in code.                                                                                                                           |
| `message`  | string            | Human-readable explanation. May change without notice — do not match on it.                                                                                                               |
| `docs_url` | string            | Link back to this page for deeper context.                                                                                                                                                |
| `context`  | object \| omitted | Per-error structured details. Present only when the error code carries them — `parameter_invalid`, `unauthorized`, and `route_already_exists` today. See [Error context](#error-context). |

Every response — success or error — also includes a `Request-Id` header. Log it; Agora support uses it to trace requests end-to-end.

## Error codes

| HTTP | `code`                         | When it happens                                                                                                                                             | Recommended handling                                                                                                                   |
| ---- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| 400  | `parameter_invalid`            | One or more request parameters fail validation. The response includes a `context.issues` array with per-field details.                                      | Inspect `context.issues` for the offending field; correct the request and retry.                                                       |
| 400  | `chain_unsupported`            | The request references a chain Agora does not support.                                                                                                      | Use a supported chain.                                                                                                                 |
| 400  | `currency_unsupported`         | The request references a currency Agora does not support for this operation.                                                                                | Use a supported currency — see the supported topologies on `POST /v0/routes`.                                                          |
| 400  | `direction_unsupported`        | The `from`/`to` pair does not resolve to a supported route direction.                                                                                       | Check the supported topologies on `POST /v0/routes` and adjust `from`/`to`.                                                            |
| 400  | `entitlement_not_requestable`  | The requested entitlement can't be requested for that account or network (unsupported, or already present). The response includes a `context.issues` array. | Inspect `context.issues`; request a supported entitlement on a supported network.                                                      |
| 401  | `unauthorized`                 | Authentication is missing or failed. The specific cause is in `context.reason` — see [Authentication failures](#authentication-failures-401).               | Resolve per the `reason`; typically exchange or refresh your session JWT. See the [Authentication guide](/api/sandbox/authentication). |
| 403  | `forbidden`                    | Authentication succeeded but the credential lacks permission for this resource.                                                                             | Check the roles on the key in the dashboard; otherwise contact your Agora account manager.                                             |
| 403  | `account_not_eligible`         | Your organization, or the destination account for the requested chain, isn't eligible for this operation.                                                   | Contact your Agora account manager to enable it.                                                                                       |
| 403  | `account_not_provisioned`      | Your organization isn't provisioned for the product this endpoint belongs to.                                                                               | Contact your Agora account manager to have the product enabled.                                                                        |
| 403  | `account_verification_pending` | Your organization is provisioned for the product, but verification for it hasn't completed.                                                                 | Wait for verification to complete, then retry; the check runs per request. Contact your Agora account manager if it stays pending.     |
| 404  | `not_found`                    | The requested resource does not exist (generic fallback).                                                                                                   | Verify the URL and method. The current surface is documented under [Endpoints](/api/sandbox/endpoints).                                |
| 404  | `account_not_found`            | The referenced account does not exist for your organization.                                                                                                | Verify the account `id` against `GET /v0/accounts`.                                                                                    |
| 404  | `route_not_found`              | The referenced route does not exist for your organization.                                                                                                  | Verify the route `id` against `GET /v0/routes`.                                                                                        |
| 404  | `transaction_not_found`        | The referenced transaction does not exist for your organization.                                                                                            | Verify the transaction `id` against `GET /v0/transactions`.                                                                            |
| 409  | `route_already_exists`         | A route with these parameters already exists. The response includes `context.routeId` with the existing route's id.                                         | Use the existing route — route ids are deterministic; fetch it via `GET /v0/routes/{routeId}` using `context.routeId`.                 |
| 409  | `account_already_exists`       | An account with these details is already registered for your organization (including registering a wallet on a network it already holds).                   | Fetch it via `GET /v0/accounts` instead of re-creating it.                                                                             |
| 429  | `rate_limit_exceeded`          | The per-IP rate limit at the Cloudflare edge was exceeded. The response includes a `Retry-After` header (seconds).                                          | Wait the `Retry-After` duration, then retry. Use exponential backoff for repeat triggers.                                              |
| 500  | `internal_error`               | An unexpected server-side failure. The response intentionally omits internal details.                                                                       | Retry with exponential backoff. If it persists, capture the `Request-Id` and contact support.                                          |
| 503  | `route_creation_unavailable`   | Route creation is temporarily unavailable.                                                                                                                  | Retry after a short delay with exponential backoff. If it persists, capture the `Request-Id` and contact support.                      |
| 503  | `service_unavailable`          | The service is temporarily unavailable.                                                                                                                     | Retry after a short delay with exponential backoff.                                                                                    |

## Authentication failures (401)

A `401 unauthorized` response carries a `context.reason` naming the specific cause, so clients can branch without parsing `message`:

```json
{
  "code": "unauthorized",
  "message": "Authentication is required.",
  "docs_url": "https://docs.agora.finance/api/errors",
  "context": { "reason": "token_expired" }
}
```

| `reason`              | When it happens                                                                      | Recommended handling                                                                             |
| --------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| `missing_header`      | No `Authorization: Bearer` header on the request.                                    | Attach the session JWT and retry.                                                                |
| `invalid_token`       | The bearer token is malformed or failed validation (signature, audience, or issuer). | Re-exchange your API key for a fresh JWT.                                                        |
| `token_expired`       | The session JWT is past its 15-minute lifetime.                                      | Exchange your API key again and retry.                                                           |
| `token_paused`        | The API key behind this session is paused.                                           | Resume the key from the dashboard, then retry.                                                   |
| `token_revoked`       | The API key behind this session has been revoked.                                    | Create a new API key from the dashboard.                                                         |
| `key_not_provisioned` | The key wasn't created through the dashboard and is no longer usable.                | Create a new API key from the dashboard.                                                         |
| `unknown_tenant`      | The token does not resolve to a known organization.                                  | Re-exchange; if it persists, contact support.                                                    |
| `missing_claim`       | The token verified but is missing a required claim.                                  | Should not occur with tokens from `/v0/auth/token`; treat as a bug and capture the `Request-Id`. |

See the [Authentication guide](/api/sandbox/authentication) for the full exchange and refresh flow.

## Error context

Three codes attach a `context` object; all others omit it.

**`parameter_invalid`** returns a `context.issues` array describing each validation failure:

```json
{
  "code": "parameter_invalid",
  "message": "One or more request parameters are invalid.",
  "docs_url": "https://docs.agora.finance/api/errors",
  "context": {
    "issues": [
      { "field": "amount", "message": "Must be a positive decimal string." },
      { "field": "destination.chain", "message": "Unsupported chain. Supported: avalanche-fuji, ethereum-sepolia." }
    ]
  }
}
```

> **Info**
>
> **Illustrative example.** The fields shown above are illustrative. The `context.issues` shape is stable across every endpoint that emits `parameter_invalid`.

**`unauthorized`** returns a `context.reason` — see [Authentication failures](#authentication-failures-401) above.

**`route_already_exists`** returns a `context.routeId` — the id of the pre-existing route. Route ids are deterministic, so you can use it directly (e.g. fetch the route via `GET /v0/routes/{routeId}`).

## Handling errors programmatically

* **Match on `code`, never on `message`.** Messages are human-readable and may change without notice. For `401`, branch on `context.reason`.
* **Use the HTTP status as a secondary filter.** Status plus `code` together fully disambiguate every error.
* **Persist the `Request-Id` header.** It is the single best lever for diagnosing issues with Agora support.
* **Retry with backoff for `429`, `500`, and `503`.** Honor `Retry-After` when present. The `503` codes (`route_creation_unavailable`, `service_unavailable`) are transient and safe to retry.