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

# Error reference

> Complete catalog of error responses returned by the OpenFX v1 API, including endpoint-specific messages and notes on what v1 does not return.

<Warning>
  **v1 sunsets December 31, 2026.** Migrate to v2 — see the [migration guide](/v1/migration-v1-to-v2).
</Warning>

**What this is.** Every non-2xx response from the v1 API wrapped in a single, flat error envelope, plus the exact `message` strings v1 servers return today.

**When it matters.** Anywhere your client branches on error responses. v1 has no machine-readable code field — clients that need to react to specific failures have to string-match against `message`, so knowing the exact strings matters.

**What you'll learn.** The v1 envelope shape, the three errors you'll see on essentially every endpoint, the handful of endpoint-specific messages on writes, why a few endpoints surface 409/422 in the spec that no v1 server ever returns, and where to go for a stable code catalog.

## Response shape

v1 wraps every error — regardless of HTTP status — in a flat two-field envelope:

```json theme={null}
{
  "status": "error",
  "message": "Human-readable description"
}
```

There is **no `error.code`**, no `error.type`, no `details`, and no nested `error` object. Those are v2 / v3 features. If your v1 client needs to branch on a specific failure mode, you have to compare `message` against the exact strings catalogued below. The other consequence is that `message` is the only signal — `status` is always the literal `"error"` on failure, so it only tells you "this isn't a 2xx body."

<Warning>
  Switching on `message` is brittle by design. Any copy edit to the underlying string will silently break your client. v3 introduces a stable `error.code` field for exactly this reason — see [Migrating off these messages](#migrating-off-these-messages).
</Warning>

## Common errors

Almost every v1 endpoint returns the same three failures. If you handle these and nothing else, you cover the majority of real-world cases.

| Status | Message                                                                         | Trigger                                                                                                                                                                                                        |
| ------ | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `Query validation failed`                                                       | Malformed query string, missing required parameter, or wrong field types in the request body. The v1 server runs schema validation before any business logic.                                                  |
| 401    | `The authorization key provided is either invalid or expired, please try again` | Bearer JWT missing, malformed, signature invalid, or `exp` in the past. Mint a fresh JWT — see [Authentication](/v1/authentication).                                                                           |
| 500    | `An internal error has occurred`                                                | Server-side failure. Generic catch-all — retry with exponential backoff. For write endpoints (`/trade`, `/withdrawal`, `/fiat_withdrawal`) check the resource by ID before retrying to avoid double-execution. |

## Endpoint-specific errors

A small set of v1 write endpoints return more specific 400 messages on top of the common three. These are the only deviations from the table above. Strings are verbatim from the v1 OpenAPI spec, including the punctuation and the `"withdraw."` typo that has shipped in production since the v1 GA.

### `POST /v1/brokerage/{orgId}/withdrawal` — Initiate stablecoin withdrawal

| Status | Message                                               | Trigger                                                                                                                                                                                |
| ------ | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `Checksum verification failed for withdrawal address` | Destination wallet address fails its on-chain checksum check (e.g. an EVM address whose case-encoded checksum doesn't match the bytes). Re-derive the address from its canonical form. |
| 400    | `Insufficient balance for this withdraw.`             | Available balance in the requested currency is less than the requested amount. Surface to the user; this is a state-driven failure, not a malformed request.                           |
| 400    | `Withdrawal address is not active or verified`        | Destination wallet exists on the org but hasn't been activated/verified in the dashboard. Have the operations team approve it before retrying.                                         |

### `POST /v1/brokerage/{orgId}/fiat_withdrawal` — Initiate fiat withdrawal

| Status | Message                                               | Trigger                                                                                                                                                                                                                                  |
| ------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `Checksum verification failed for withdrawal address` | The destination bank account record failed its server-side integrity check. Re-fetch the account ID from [`GET /v1/brokerage/{orgId}/fiat_withdrawal_addresses`](/v1/api-reference/withdrawals/list-fiat-withdrawal-accounts) and retry. |
| 400    | `Insufficient balance for this withdraw.`             | Available fiat balance is less than the requested amount.                                                                                                                                                                                |
| 400    | `Withdrawal account is not active or verified`        | The fiat account exists but hasn't been activated/verified.                                                                                                                                                                              |

### `POST /v1/brokerage/{orgId}/generate_quote` — Generate quote

| Status | Message                                                | Trigger                                                                                                                                                                                                                                                                             |
| ------ | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 500    | `Error while creating a quote, please try again later` | Quote-engine failure — typically an upstream pricing-provider problem. Distinct from the generic `An internal error has occurred` so clients can retry with shorter backoff and refresh-then-execute logic. The request never reached trade execution; safe to issue a fresh quote. |

### `POST /v1/brokerage/{orgId}/trade/reverse` — [Reverse a trade](/v1/api-reference/trade/reverse-trade)

| Status | Message                        | Trigger                                                               |
| ------ | ------------------------------ | --------------------------------------------------------------------- |
| 404    | `Trade not found`              | `tradeId` or `quoteId` doesn't resolve to a record on this org.       |
| 409    | `The trade cannot be reversed` | The trade is outside its reversal window or already reversed/settled. |

### `POST /v1/brokerage/{orgId}/trade/reverse-quote` — [Generate a reverse quote](/v1/api-reference/trade/generate-reverse-quote)

| Status | Message                                        | Trigger                                                                             |
| ------ | ---------------------------------------------- | ----------------------------------------------------------------------------------- |
| 404    | `Trade not found`                              | `tradeId` doesn't resolve to a record on this org.                                  |
| 500    | `Something went wrong, please try again later` | Quote-engine failure, distinct from the generic 500; request a fresh reverse quote. |

### `POST /v1/brokerage/{orgId}/trade/settle` — [Settle a trade](/v1/api-reference/trade/settle-trade)

| Status | Message                       | Trigger                                                                          |
| ------ | ----------------------------- | -------------------------------------------------------------------------------- |
| 404    | `Trade not found`             | `tradeId` doesn't resolve to a record on this org.                               |
| 409    | `The trade cannot be settled` | The trade isn't in a settleable state (already settled, not yet executed, etc.). |

## Why v1 lacks idempotency error codes

v1 **does not support `Idempotency-Key`**. Callers will never see `IDEMPOTENCY_IN_FLIGHT` (409), `IDEMPOTENCY_MISMATCH` (422), or `IDEMPOTENCY_KEY_REQUIRED` (400) responses from a v1 server, regardless of whether the request supplies an `Idempotency-Key` header.

The v1 OpenAPI spec lists 409 and 422 responses — and, on the trade-reversal endpoints, a nested `error.code` on some example bodies — as leftover boilerplate from a shared error schema also used by v2 and v3 (`GET /balances`, `POST /trade`, `POST /trade/reverse`, `POST /trade/reverse-quote`, `POST /trade/settle`). None of that is enforced by the v1 server: real v1 responses stay in the flat `{status, message}` envelope described above, and you can ignore both the idempotency status codes and any `error.code` the spec shows for these operations. To get real idempotency semantics and structured error codes, migrate to v2 or v3 — see [Migration from v1 to v2](/v1/migration-v1-to-v2).

## Migrating off these messages

Because v1 has no `error.code`, every branch in your v1 client has to string-match `message`. That string is the API contract today, but it's not part of any stability guarantee — a future v1 copy edit could rephrase any of the messages above.

v3 fixes this by wrapping every error in a stable, machine-readable envelope:

```json theme={null}
{
  "error": {
    "code": "WITHDRAWAL_INSUFFICIENT_BALANCE",
    "type": "conflict",
    "message": "Account balance is insufficient for this withdrawal.",
    "details": { "required": "1500.00", "available": "1234.56", "currency": "USDC" }
  }
}
```

Clients switch on `error.code` and treat `message` as human-readable copy. Codes are namespaced (`<PRODUCT>_<ERROR>`) and never renamed within a major version. The full v3 code catalog — including the codes that map back to each v1 message above — is at [`/v3/errors`](/v3/errors).
