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

# Errors and retries

> The response envelope, every error code, gateway responses, and which failures to retry.

Every response from Olympex uses the same JSON envelope, with a stable `error.code` on failures and a `meta.requestId` on every response. Some failures happen earlier, at the API gateway, and come back with a smaller body and no `meta.requestId`. Their ID is in the `apigw-requestid` response header instead. Your client needs to handle both, retry only the failures a second attempt can fix, and sign every attempt again.

## The response envelope

<CodeGroup>
  ```json Success theme={null}
  {
    "success": true,
    "data": true,
    "meta": {
      "requestId": "ENUlxjCsIAMEMUA=",
      "version": "v1",
      "accountType": "integrator",
      "apiKeyId": "00000000-0000-4000-8000-000000000000"
    }
  }
  ```

  ```json Error theme={null}
  {
    "success": false,
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "Invalid request body",
      "details": [
        {
          "field": "params.chainId",
          "message": "Invalid input: expected number, received string"
        },
        {
          "field": "params.inTokenAddress",
          "message": "Must be a valid EVM address"
        },
        {
          "field": "params.amount",
          "message": "Invalid input: expected string, received number"
        },
        {
          "field": "params.gasPrice",
          "message": "Invalid input: expected string, received undefined"
        }
      ]
    },
    "meta": {
      "requestId": "ENUlmg3dIAMEMEw=",
      "version": "v1",
      "accountType": "integrator",
      "apiKeyId": "00000000-0000-4000-8000-000000000000"
    }
  }
  ```
</CodeGroup>

Every success is HTTP `200`, including creates (`POST /limit-order`, `POST /dca-order/strategies`) and cancels (`DELETE /limit-order/{id}`).

| Field | Description |
| - | - |
| `success` | `true` on success, `false` on error. |
| `data` | The result, on success. Its shape depends on the endpoint: an object, a boolean, or an array for list endpoints. |
| `error.code` | Stable, machine-readable code. Branch on it. |
| `error.message` | Human-readable summary. Don't parse it. |
| `error.details` | A list of `{field, message}` items for validation errors, where `field` is a dot path such as `params.chainId`. A list of `{message}` items for handler errors. Otherwise empty. |
| `meta.requestId` | Identifies the request. Include it when you contact support. |
| `meta.version` | The API version that served the request: `v1`. |
| `meta.accountType` | On signed requests, the type of the account that signed: `integrator` for accounts created with `POST /accounts`. |
| `meta.apiKeyId` | On signed requests, the API key ID that signed the request. |

New error codes can be added. Treat a code you don't recognize according to its HTTP status.

## Error codes

| `error.code` | HTTP | When | What to do |
| - | - | - | - |
| `VALIDATION_ERROR` | 400 | A field or query parameter is missing, has the wrong type or is out of range (`"Invalid request body"`, `"Invalid query parameters"`), the body is missing (`"Body is required"`), or the body isn't valid JSON (`"Invalid JSON body"`, also the result of a missing `Content-Type: application/json` on `POST /accounts`). Also returned when the chain isn't enabled (`"Chain N is not enabled"` from `GET /tokens` and `GET /transactions/{hash}`, for example `"Chain 324 is not enabled"`), for a malformed transaction hash (`"Invalid transaction hash"`), and when Olympex has no reference price for a limit order's pair (`"Not exist reference price for this pair A/B"`). On limit orders, also returned for a field Olympex sets (`status`, `txHash`, `executorAddress`, `reasonFail`, `allowance`, `estimateGas`, `effectivePriceGas`) and for an `expired` that isn't a Unix timestamp in milliseconds (13 digits), in the future and at most 365 days ahead. | Fix the fields listed in `error.details` and resend. Don't retry unchanged, and don't send the fields Olympex sets. For a missing reference price, check that `tokenASymbol` and `tokenBSymbol` are the tokens' real symbols, then retry later with backoff, because the error can be temporary, or choose another pair. |
| `UNAUTHORIZED` | 401 | The request reached Olympex without an authenticated context: a server-side problem. A missing signing header gets the gateway's `{"message":"Unauthorized"}` instead. | Contact [partners@olympex.io](mailto:partners@olympex.io) with `meta.requestId`. |
| `FORBIDDEN` | 403 | The body does not match the signed `bodyHash` (`"Invalid body hash"`). For a `GET` or `DELETE`, the signed `bodyHash` isn't the hash of an empty body. | Send exactly the canonical body you signed, with `Content-Type: application/json`. For a `GET` or `DELETE`, sign the empty string and send no body. |
| `NOT_FOUND` | 404 | No limit order, DCA strategy or DCA order with this ID belongs to your API key: the ID doesn't exist, or another API key created it (`"Limit order not found"`, `"DCA strategy not found"`, `"DCA order not found"`). Also returned when no endpoint matches the method and the path (`"No route for GET /api/v1/quote"`). | Don't retry unchanged. Check the method and the path against the [API reference](/api-reference/overview), then the ID, and sign with the API key that created the order or strategy. |
| `CONFLICT` | 409 | `PATCH` or `DELETE /limit-order/{id}` on an order that is no longer `pending` (`"Limit order can only be modified while pending (current status: cancelled)"`), including a repeated `DELETE` after the first one cancelled the order. The order doesn't change. | Don't retry. Read the order with `GET /limit-order/{id}` and act on its `status`. After a repeated `DELETE`, `cancelled` means the first attempt worked. |
| `NO_ROUTE` | 422 | `POST /quotes` or `POST /swap` found no route for this pair and amount (`"No route found for this pair and amount. Providers may also be temporarily unavailable; retrying later can succeed."`). The sources can also be briefly unavailable, so it can be temporary. | Retry later with backoff. If it persists, try another amount or pair. From `POST /swap`, build with the next source in the quote's `aggregatorOrder`, or request a new quote for a cross-chain swap. |
| `QUOTE_ERROR`, `CROSS_CHAIN_QUOTE_ERROR` | 500 | An unexpected error while producing the quote. A pair or amount without a route returns `422 NO_ROUTE` instead. | Retry with backoff. |
| `SWAP_ERROR`, `CROSS_CHAIN_SWAP_ERROR` | 500 | Calldata could not be built for the route: an unexpected error, or a source that failed to build it, with the source's message in `error.details`. `POST /swap` can also return `422 NO_ROUTE` for a route it can't build. | Retry once. For `SWAP_ERROR`, then build with the next source in the quote's `aggregatorOrder`. For `CROSS_CHAIN_SWAP_ERROR`, or when no source is left, request a fresh quote and use its `aggregatorId`. |
| `SUPPORT_CHAIN_ERROR` | 500 | The chain check failed. | Retry with backoff. |
| `ENABLED_CHAINS_ERROR` | 500 | `GET /chains` couldn't return the enabled chains. | Retry with backoff, and keep using your cached list meanwhile. |
| `TOKEN_LIST_ERROR` | 500 | `GET /tokens` couldn't return the token list. | Retry with backoff, and keep using your cached list meanwhile. |
| `TX_STATUS_ERROR` | 500 | From `POST /tx-status`: the provider has no status for the transfer yet, or reported a failure. From `GET /transactions/{hash}`: the chain's RPC failed. An unknown hash there returns `200` with the status `not_found`, not this error. | Keep polling with backoff. Right after broadcast this is expected from `POST /tx-status`. |
| `INTERNAL_ERROR` | 500 | An unexpected error occurred (`"Unexpected internal error"`). Limit order and DCA endpoints report their unexpected failures with this code. | Retry with backoff. Never retry `POST /limit-order` or `POST /dca-order/strategies` automatically: each successful call creates a new order or strategy, so list yours first. If the error persists, contact [partners@olympex.io](mailto:partners@olympex.io) with `meta.requestId`. |

## Gateway responses

The API gateway sits in front of Olympex. It authenticates signed requests, enforces a timeout of about 30 seconds and answers some requests itself. Its responses carry only `message`: no `success`, `error` or `meta`, so no `meta.requestId`. They carry an ID in the `apigw-requestid` response header, which browsers can't read on cross-origin calls. The [OpenAPI spec](/api-reference/openapi-spec) names their body `GatewayError`.

| Status | Body | Meaning |
| - | - | - |
| `401` | `{"message":"Unauthorized"}` | A signing header is missing. |
| `403` | `{"message":"Forbidden"}` | Authentication failed: unknown API key ID, wrong passphrase, bad signature (including headers signed for another method, path or query), a nonce that isn't 24 hexadecimal characters, timestamp outside ±300 seconds, reused nonce, inactive account, or signature verification temporarily unavailable. |
| `429` | `{"message":"Too Many Requests"}` | Too many requests in a short time. |
| `500` | `{"message":"Internal Server Error"}` | The gateway could not get a response from Olympex, for example after its timeout of about 30 seconds. The first request after a quiet period can also get this response. |
| `503` | `{"message":"Service Unavailable"}` | The request ran past the gateway timeout of about 30 seconds, or the service is briefly unavailable. |

To tell the two kinds of response apart, check the body for `success`. Every Olympex response has it and no gateway response does. Three statuses can come from either:

* **`401`.** The gateway's `{"message":"Unauthorized"}` means a signing header is missing. Olympex's `UNAUTHORIZED` is a problem on the Olympex side.
* **`403`.** The gateway's `{"message":"Forbidden"}` means authentication failed. Olympex's `FORBIDDEN` means the body doesn't match the signed hash.
* **`500`.** From Olympex, with an error code; from the gateway, with only `message`.

Every `404` comes from Olympex, with the code `NOT_FOUND`. For an unknown path or method, its message names them, for example `"No route for GET /api/v1/quote"`.

[Sign requests](/authentication/sign-requests#troubleshooting) maps each signing mistake to the `401`, `403` or `400` it produces.

## Which errors to retry

| Response | Retry | What to do |
| - | - | - |
| `400` `VALIDATION_ERROR` | Never | Fix the fields listed in `error.details`. The same request fails the same way. |
| `401` or `403` from the gateway | Once, signed again | Sign again with a new timestamp and nonce. That fixes a request that waited too long before it was sent, or a brief failure on the Olympex side. If the new attempt fails too, stop: check that all four signed headers are sent, then the credentials and the clock, with the [troubleshooting table](/authentication/sign-requests#troubleshooting). If correctly signed requests keep failing, contact [partners@olympex.io](mailto:partners@olympex.io) with the `apigw-requestid` header and the UTC time. |
| `401` `UNAUTHORIZED` | Never | The request reached Olympex without its authentication context, a problem on the Olympex side. Signing again doesn't help. Contact [partners@olympex.io](mailto:partners@olympex.io) with `meta.requestId`. |
| `403` `FORBIDDEN` | After a fix | `"Invalid body hash"`: send exactly the body you signed, and for a `GET` or `DELETE`, sign the empty string and send no body. Signing again doesn't help. Follow the [error codes](#error-codes) table. |
| `404` `NOT_FOUND` | Never | No limit order, DCA strategy or DCA order with that ID belongs to your API key (the message names the resource), or no endpoint matches the method and the path (`"No route for …"`). Check the method and the path, then the ID, and sign with the API key that created it. |
| `409` `CONFLICT` | Never | The limit order is no longer `pending`. Read it with [`GET /limit-order/{id}`](/api-reference/limit-orders/get-limit-order) and act on its `status`. |
| `422` `NO_ROUTE` | Yes, later, with backoff | No source has a route for this pair and amount right now, or the sources are briefly unavailable. After a few attempts, change the amount or the pair. On `POST /swap`, build with the next source in the quote's `aggregatorOrder` first, or request a new quote for a cross-chain swap. |
| `429` from the gateway | Yes, with backoff, except creates | Wait, then retry with exponential backoff and jitter, signing each attempt again, and send fewer requests in parallel. See [Rate limits and quotas](/authentication/limits#rate-limits-and-quotas). |
| `500` with an error code | Yes, with backoff, except creates | Applies to `QUOTE_ERROR`, `CROSS_CHAIN_QUOTE_ERROR`, `SWAP_ERROR`, `CROSS_CHAIN_SWAP_ERROR`, `SUPPORT_CHAIN_ERROR`, `ENABLED_CHAINS_ERROR`, `TOKEN_LIST_ERROR` and `INTERNAL_ERROR`. Sign each attempt again, and follow the advice for each code in the [error codes](#error-codes) table. Never retry a [create](#requests-that-create-a-resource) automatically. For `TX_STATUS_ERROR`, see [Polling transaction status](#polling-transaction-status). |
| `500` or `503` from the gateway | Yes, except creates | The gateway got no response from Olympex in time, or the service is briefly unavailable. The first request after a quiet period can also get a `500`. Sign again and retry once, then back off. |
| Network error or client timeout | Yes, with backoff, except creates | You don't know whether the request ran. Every request except the three creates is safe to repeat, with a new signature. |

## Requests that create a resource

`GET`, `PATCH` and `DELETE` requests are safe to repeat. So are `POST /quotes`, `POST /swap`, `POST /support-chain` and `POST /tx-status`, which change nothing: `POST /swap` only returns calldata and never broadcasts. A repeated `DELETE /limit-order/{id}` whose first attempt went through returns `409 CONFLICT`, because the order is already `cancelled`: read the order with `GET /limit-order/{id}` instead of treating the `409` as a failure.

Three endpoints create a new resource on every successful call. Olympex ignores any `id` you send, so you can't use one to make a repeated call recognizable:

| Endpoint | Each success creates |
| - | - |
| [`POST /accounts`](/api-reference/accounts/create-account) | An API account, with its own secret key. |
| [`POST /limit-order`](/api-reference/limit-orders/create-limit-order) | A limit order. |
| [`POST /dca-order/strategies`](/api-reference/dca/create-dca-strategy) | A DCA strategy. |

After a timeout, a network error or a `5xx`, you can't tell whether a create succeeded. Don't retry it blindly: list what exists and match on your own fields first.

* **Limit order.** Call [`GET /limit-order?accountTo=<maker>`](/api-reference/limit-orders/list-limit-orders) and look for an order with the same chain, tokens, `amount` and `priceTrigger`.
* **DCA strategy.** Call [`GET /dca-order/strategies?accountTo=<maker>`](/api-reference/dca/list-dca-strategies), which lists strategies newest first, and look for one with the same tokens, `totalAmount`, `iterations` and `frequency`.
* **API account.** No endpoint lists accounts. If you never received the response, you never saw its secret key, so create another account.

`?accountTo=` matches case-sensitively. Send the maker address in the same EIP-55 checksummed form you used to create the order.

<Warning>
  Don't retry `POST /accounts`, `POST /limit-order` or `POST /dca-order/strategies` automatically. A retry after a timeout can leave you with two accounts and a secret key you never saw, or with two orders that Olympex can both execute against the maker wallet's allowance.
</Warning>

## Back off with jitter

Retry with exponential backoff and full jitter: before each retry, wait a random time between zero and a ceiling that doubles after every failure, up to a cap, and stop after a few attempts. Jitter spreads retries out, so many clients that failed together don't retry together.

Route every call through one helper instead of retrying at each call site. [Handle errors and retries](/guides/handle-errors-and-retries) builds one on the [reference implementations](/authentication/sign-requests#reference-implementations): `olympexRequestWithRetry` in TypeScript and `olympex_request_with_retry` in Python. Both take `(method, path, body)`, like the reference helpers, and never retry the three creates automatically. Their defaults (4 attempts, a 1-second base delay, a 20-second cap) are starting points: tune them to your latency budget.

## Sign every attempt again

The server rejects a nonce it has already seen in the last 5 minutes, and any timestamp more than 300 seconds from server time. Resending the headers of an earlier attempt fails with `403` if that attempt reached Olympex, even if it ended in a `500` or a timeout. After a client timeout you can't tell whether it did, so sign again.

Call your signer for every attempt, so each one gets a new timestamp and a new nonce. `olympexRequest` and `olympex_request` sign on every call, so a retry helper that calls them for each attempt already does this. Never cache, queue or reuse signed headers: each set authenticates one request, once.

## Keep the request ID

`meta.requestId` identifies a request. Log it for every response, next to your own correlation ID. When a failure persists, send it to [partners@olympex.io](mailto:partners@olympex.io): it is the fastest way for Olympex to find your request. The error types in the reference implementations carry it as `requestId` (TypeScript) and `request_id` (Python).

Gateway responses have no `meta.requestId`. Log their `apigw-requestid` response header instead, with the UTC time, the endpoint, the status and your API key ID. The reference helpers read the request ID from the body only, so for a gateway `401` or `403` their error has no `requestId` or `request_id`: read the header where your code receives the HTTP response, for example in your copy of the helper, and log it there. Browsers can't read that header on cross-origin calls, so log it on your server. From a terminal, add `-i` (or `-D -`) to a curl command to print the response headers, including `apigw-requestid`. Never log or send the secret key, the passphrase or the signed headers.

## Polling transaction status

<Note>
  A `500 TX_STATUS_ERROR` from [`POST /tx-status`](/api-reference/transactions/get-transaction-status) right after you broadcast, or for a transfer the provider marks failed, means the status is unknown. It doesn't tell you whether the transfer succeeded or failed.
</Note>

Keep polling with backoff, for example every 15 to 30 seconds, instead of treating the error as final. If the status stays unknown, check the transaction on a block explorer, or contact [partners@olympex.io](mailto:partners@olympex.io) with its `meta.requestId`. [Track a swap to finality](/guides/track-a-swap-to-finality) has the full polling loop.

[`GET /transactions/{hash}`](/api-reference/transactions/get-transaction) returns `TX_STATUS_ERROR` when the chain's RPC fails: retry it with backoff too. A hash the chain doesn't know yet isn't an error there: the response is a `200` with the status `not_found`, so you can poll it right after you broadcast.

## What this means for your integration

* Branch on `success`, `error.code` and the HTTP status, never on `error.message`.
* Retry a gateway `429`, `500`-class responses, `422 NO_ROUTE` and network errors with exponential backoff and jitter, except the three creates: list and match before you create again.
* Sign again once after a gateway `401` or `403`, then stop and check your signer, credentials and clock.
* Never retry `400`, `404` or `409`, and fix the cause before you resend after `UNAUTHORIZED` or `FORBIDDEN`.
* Sign every attempt again, with a new timestamp and nonce.
* Log `meta.requestId`, or the `apigw-requestid` header for gateway responses, for every failure and include it when you contact support.

## Related

<CardGroup cols={2}>
  <Card title="Handle errors and retries" icon="repeat" href="/guides/handle-errors-and-retries">
    A step-by-step guide to a production error-handling layer.
  </Card>

  <Card title="Sign requests" icon="code" href="/authentication/sign-requests">
    The signing algorithm and the troubleshooting table.
  </Card>

  <Card title="Limits" icon="gauge" href="/authentication/limits">
    The timestamp window, nonce reuse and the gateway timeout.
  </Card>

  <Card title="Track a swap to finality" icon="clock" href="/guides/track-a-swap-to-finality">
    Poll a cross-chain transfer until it completes.
  </Card>
</CardGroup>


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