> ## 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.

# API conventions

> Wire-format rules shared by every Olympex endpoint: methods, bodies and signing, the envelope, amounts, addresses, chain IDs, IDs, timestamps, lists, errors, request IDs and forward compatibility.

These rules apply to every endpoint in this reference. Endpoint-specific behavior is on each endpoint page, and field-level types are in the [OpenAPI specification](/api-reference/openapi-spec).

## Requests

### Methods

| Method | Used for | Body | Parameters |
| - | - | - | - |
| `GET` | Reading chains, tokens, transaction status, limit orders, DCA strategies and DCA orders. | None | Path (`/limit-order/{id}`) and query (`/tokens?chainId=137`). |
| `POST` | Quotes, swaps, chain and transfer checks, and creating accounts, limit orders and DCA strategies. | A JSON object. | None |
| `PATCH` | Changing a limit order, and changing or cancelling a DCA strategy. | A JSON object with only the fields you change. | Path (`{id}`). |
| `DELETE` | Cancelling a limit order. | None | Path (`{id}`). |

A method that a path doesn't support returns `404 NOT_FOUND`, with a message that names the method and path, for example `DELETE` on a DCA strategy (`"No route for DELETE /api/v1/dca-order/strategies/…"`): cancel a strategy with `PATCH` instead. The service's own [`GET /openapi.json` and `GET /docs`](/api-reference/openapi-spec#service-hosted-spec-and-swagger-ui) are public.

### Bodies and signing

* **`Content-Type: application/json` on every request**, including `GET`, `DELETE` and the public endpoints. Without it, or with another type such as curl's default `application/x-www-form-urlencoded`, the gateway base64-encodes a body before Olympex reads it, and the request fails with `403` (`Invalid body hash`) on a signed endpoint or `400` (`Invalid JSON body`) on a public one.
* **UTF-8.** Encode the body as UTF-8 JSON.
* **Send the bytes you signed.** On `POST` and `PATCH`, serialize the body once as canonical JSON (keys sorted at every level, no whitespace), hash that string, and send the same string. [Sign requests](/authentication/sign-requests) has the algorithm and reference code.
* **Sign the empty string when there is no body.** `GET` and `DELETE` send no body, and the signature covers the empty string: its `bodyHash` is `47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU`.
* **The signature covers the whole request**: the method, the path (including `/api/v1`), the canonical query and the body hash, with the timestamp and the nonce. Headers signed for one request are rejected on any other. Sign right before you send, and never log or share the headers.
* **Sign every attempt.** Each signature carries a nonce, which the server rejects if it has seen it in the last 5 minutes, and a timestamp it accepts within ±300 seconds of its own clock. The nonce makes each set of headers usable once. A retry needs a new signature, and your server clock must stay synchronized.

## Response envelope

Every endpoint wraps its result in the same envelope, whatever its method. The exceptions are [gateway responses](#gateway-responses) and the service's own `GET /openapi.json` and `GET /docs`, which return the raw spec and an HTML page. Every success is HTTP `200`, including creates (`POST`) and cancels (`DELETE`).

| Field | Type | Present | Description |
| - | - | - | - |
| `success` | boolean | Always | `true` on success, `false` on error. |
| `data` | object, array or boolean | When `success` is `true` | The result. Its shape depends on the endpoint. |
| `error` | object | When `success` is `false` | `code`, `message` and `details`. See [Errors](#errors). |
| `meta.requestId` | string | Always | Unique ID of the request. See [Request IDs](#request-ids). |
| `meta.version` | string | Always | API version that served the request: `v1`. |
| `meta.accountType` | string | Signed endpoints | Account type resolved from your signature: `integrator` for accounts created with `POST /accounts`. |
| `meta.apiKeyId` | string | Signed endpoints | The API key ID that signed the request. |

```ts theme={null}
type Meta = { requestId: string; version: string; accountType?: string; apiKeyId?: string }; // both absent on public endpoints and unknown routes
type ErrorDetail = { field?: string; message: string };

type Envelope<T> =
  | { success: true; data: T; meta: Meta }
  | { success: false; error: { code: string; message: string; details: ErrorDetail[] }; meta: Meta };
```

Branch on `success`, never on the value of `data`: `POST /support-chain` returns `"success": true` with `"data": false` for an unsupported chain, and a list with no matches returns `"data": []`.

| Endpoint | `data` on success |
| - | - |
| `POST /accounts` | `message`, `apiKey` (your API key ID) and `secretKey`. |
| `POST /quotes` | `mode` and `quote`. |
| `POST /swap` | `mode` and `swap`. |
| `POST /tx-status` | The transfer status: `status`, `detailStatus`, chain IDs and the fields the provider reports. |
| `GET /transactions/{hash}` | `hash`, `chainId`, `status` (`pending`, `success`, `reverted` or `not_found`), `blockNumber`, `confirmations` and `gasUsed`. |
| `GET /chains` | `chainIds`: the enabled chain IDs. |
| `GET /tokens` | `chainId` and `tokens`. |
| `POST /support-chain` | `true` or `false`. |
| `GET /limit-order` | An array of limit orders. |
| `POST /limit-order`, and `GET`, `PATCH` and `DELETE /limit-order/{id}` | The limit order. |
| `GET /dca-order/strategies` | An array of DCA strategies, each with its `orders`. |
| `POST /dca-order/strategies`, and `GET` and `PATCH /dca-order/strategies/{id}` | The DCA strategy, without its orders. |
| `GET /dca-order/strategies/{id}/orders` | An array of DCA orders. |
| `GET /dca-order/orders/{id}` | The DCA order. |

## Amounts

Amounts you send are human-readable decimals: `"10"` is 10 USDT, and Olympex resolves the token's decimals itself. Most amounts you receive from quotes and swaps are integer strings in the token's base units: the human-readable amount multiplied by 10 to the power of the token's decimals. Limit order and DCA amounts stay human-readable in both directions.

<Warning>
  Don't convert `params.amount`, a limit order's `amount` or a DCA strategy's `totalAmount` to base units. `"10"` is 10 USDT. `"10000000"` is ten million USDT, not 10.
</Warning>

### Values you send

| Field | Endpoints | Unit | Example |
| - | - | - | - |
| `params.amount` | `/quotes`, `/swap` | Decimal string in human-readable units of the input token. | `"10"` is 10 USDT. |
| `params.slippage` | `/quotes`, `/swap` | Percent, as a decimal string. | `"1"` is 1%. |
| `params.gasPrice` | Single-chain `/quotes` and `/swap` | Gas price hint in whole gwei, as a string, rounded up. Some sources reject fractional gwei. On a chain whose gas price is below 1 gwei, the hint is `"1"`, higher than the chain's gas price. `dataFeeTransaction.effectiveGasPrice` reports the gas price the fee estimate used, in wei. | `"35"` |
| `fees.feeBps` | `/quotes`, `/swap` | Integer basis points from `0` to `100`. | `25` is 0.25%. |
| `amount` | Limit orders | Decimal string in human-readable units of `inTokenAddress`. | `"0.5"` is 0.5 WETH. |
| `priceTrigger`, `price` | Limit orders | Units of `outTokenAddress` per 1 `inTokenAddress`, human-readable, as a decimal string. Send `price` equal to `priceTrigger`, and send both whenever you change one: Olympex stores `price` as sent and never syncs it with `priceTrigger`. | `"4200"` |
| `slippage` | Limit orders | Percent, as a decimal string. | `"1"` is 1%. |
| `gasPrice` | Limit orders | The chain's current gas price in gwei, as a decimal string. Olympex stores it with the order. It isn't a cap: neither the API nor the order contract enforces it, and the gas cost the maker reimburses is the execution transaction's gas used times its actual gas price, converted to `inTokenAddress`. | `"35"` |
| `totalAmount` | DCA strategies | JSON number in human-readable units of `tokenAddressFrom`, for the whole strategy. Each order spends `totalAmount / iterations`. | `100` is 100 USDC. |
| `slippage` | DCA strategies | Percent, as a JSON number. Always send it. | `1` is 1%. |
| `minPrice`, `maxPrice` | DCA strategies | Optional. Units of `tokenAddressTo` per 1 `tokenAddressFrom`, as JSON numbers. | |

To turn a gas price in wei from your RPC into the quote and swap hint, round up to whole gwei, for example `((gasPriceWei + 999_999_999n) / 1_000_000_000n).toString()` with a `bigint`. Below 1 gwei this gives `"1"`: the hint is then higher than the chain's gas price, and your wallet or signer still sets the gas price of the transaction you send. A limit order's `gasPrice` is a decimal string, so it can carry a fraction, for example `"0.05"`.

Quote, swap and limit-order amounts, prices and slippage are decimal strings: quotes and swaps reject a JSON number in these fields with `400 VALIDATION_ERROR`. DCA's `totalAmount`, `slippage`, `minPrice` and `maxPrice` must be JSON numbers: a string returns `400`. The reference signers canonicalize numbers the way JavaScript does, so fractional values such as a `minPrice` of `0.00025` sign correctly. If you write your own signer in another language, format numbers exactly as JavaScript's `JSON.stringify` does (`1.0` becomes `1`, `1e21` becomes `1e+21`, `0.0000001` becomes `1e-7`), and keep integers within ±(2<sup>53</sup> − 1). See [Portability rules](/authentication/sign-requests#portability-rules).

### Values you receive

| Field | Where | Unit |
| - | - | - |
| `quote.outAmount` | Single-chain `/quotes` | Base units of the output token. `"9979975"` is 9.979975 USDC. |
| `quote.toTokenAmount`, `quote.minimumReceived` | Cross-chain `/quotes` | Base units of the output token. |
| `quote.fromTokenAmount` | Cross-chain `/quotes` | Your input `amount`, echoed in human-readable units. |
| `quote.estimateCostInUSD` | Cross-chain `/quotes` | USD: the provider's estimate of the transfer's cost. What it includes varies by provider. |
| `swap.outAmount`, `swap.minOutAmount` | Single-chain `/swap` | Base units of the output token. The transaction reverts below `minOutAmount`. |
| `swap.value` | `/swap` | Wei of the native token. `"0"` for an ERC-20 input. |
| `fromAmount`, `toAmount` | `/tx-status` | As the provider reports them, when it does. The unit isn't normalized: check it before you do arithmetic. |
| `dataFeeTransaction.effectiveGasPrice`, `dataFeeTransaction.transactionFee` | Single-chain `/quotes` with `includeGasInfo` | Wei. |
| `dataFeeTransaction.transactionFeeInToken`, `dataFeeTransaction.valueToApprove` | Single-chain `/quotes` with `includeGasInfo` | Human-readable units of the input token. `valueToApprove` is `amount` plus the fee. The fee is based on `quote.estimatedGas`, which leaves out the Olympex contracts: add a buffer before you approve `valueToApprove`. |
| `dataFeeTransaction.transactionFeeInUSD`, `dataFeeTransaction.nativePrice`, `dataFeeTransaction.tokenPrice` | Single-chain `/quotes` with `includeGasInfo` | USD. |
| `integratorFeeBreakdown.protocolFeeAmount`, `integratorFeeBreakdown.integratorMarginAmount` | `/quotes` | Base units of the output token on single-chain quotes, of the source token on cross-chain quotes. |
| `integratorFeeBreakdown.protocolFeeBps` | `/quotes` | Basis points: `15` is 0.15%, and it can be fractional. |
| `integratorFeeBreakdown.integratorMarginBps` | `/quotes` | Your `feeBps`. |
| `quote.estimatedGas` | Single-chain `/quotes` | Gas units that the source estimates for its own part of the route, without the Olympex contracts: the swap uses more. Estimate the transaction with `eth_estimateGas`. `"0"` means the source gave no estimate. |
| `quote.estimatedGas` | Cross-chain `/quotes` | Depends on the provider: gas units or a native-token fee in wei. Display only. |
| `swap.estimatedGas`, `swap.gasLimit` | `/swap` | Not reliable. `estimatedGas` can be a `"1500000"` placeholder, `"0"`, or on cross-chain routes a wei amount, and `gasLimit` is `estimatedGas × 2`. Estimate gas yourself with `eth_estimateGas` plus a buffer, and use `gasLimit` only as a fallback. |
| `amount`, `price`, `priceTrigger` | Limit orders | Human-readable, as JSON numbers, although you send them as strings. Olympex stores them as double-precision numbers, which keep 15 to 17 significant digits: `"0.123456789123456789"` is stored and returned as `0.12345678912345678`. Send at most 15 significant digits so the stored value equals the one you sent. |
| `totalAmount`, `slippage`, `minPrice`, `maxPrice` | DCA strategies | Human-readable, as JSON numbers. `slippage` is in percent. |
| `amount`, `amountReceived` | DCA orders | Human-readable, as JSON numbers: the amount of the token sold in the order, and the amount of the token bought. |
| `decimals` | `GET /tokens` | The token's decimals. |

To display a base-unit amount, divide by 10 to the power of the token's decimals with integer or decimal arithmetic. Take `decimals` from [`GET /tokens`](/api-reference/tokens/list-tokens), or read `decimals()` from the token contract. Cross-chain quotes also report `decimals` for each asset listed in `middlewareRoute`.

<CodeGroup>
  ```ts TypeScript theme={null}
  // npm install viem
  import { formatUnits } from "viem";

  // quote.outAmount for 10 USDT to USDC on Polygon. USDC has 6 decimals.
  console.log(formatUnits(BigInt("9979975"), 6)); // 9.979975
  ```

  ```python Python theme={null}
  from decimal import Decimal

  # quote.outAmount for 10 USDT to USDC on Polygon. USDC has 6 decimals.
  print(Decimal("9979975").scaleb(-6))  # 9.979975
  ```
</CodeGroup>

<Tip>
  Keep amounts as strings, `BigInt` or `Decimal` from end to end. A JavaScript `number` or a Python `float` loses precision on 18-decimal tokens.
</Tip>

## Addresses

* Addresses are accepted in lowercase or EIP-55 checksum form, as `0x` and 40 hex digits. A mixed-case address whose checksum is wrong returns `400 VALIDATION_ERROR` with the detail `"Must be a valid EVM address"`, which catches most typos.

* The chain's native token uses this pseudo-address wherever a token address is expected, in either casing:

  ```text theme={null}
  0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
  ```

  `GET /tokens` lists it in lowercase, `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee`, so compare it case-insensitively.

* Limit orders and DCA strategies can't sell the native token: `inTokenAddress` and `tokenAddressFrom` must be ERC-20 tokens. Use the wrapped token, for example WETH, WBNB or WPOL.

* Olympex resolves token decimals server-side. You never send them.

* Responses don't normalize addresses. `GET /tokens` returns addresses in varying case. On single-chain quotes, `routes[].subRoutes[].from` and `to` are token addresses as the source reports them: case varies, and the native token can appear as `0xeeee…` or the zero address. `middlewareRoute` can use checksummed addresses in the same quote, and can show a native token as the pseudo-address or the zero address, depending on the provider. Compare addresses case-insensitively.

* Limit orders and DCA strategies store the maker's `accountTo` exactly as you send it, and the `?accountTo=` list filters match case-sensitively. Always send the EIP-55 checksummed form.

* `feeRecipient` can't be the zero address.

## Chain IDs

Chain IDs are standard EVM chain IDs, sent and returned as JSON integers on every endpoint: `137`, not `"137"`. A string in a request body returns `400 VALIDATION_ERROR`, and so does a non-numeric `?chainId=` on `GET /tokens` or `GET /transactions/{hash}`. The `?chainId=` filter of `GET /limit-order` isn't validated: a value that isn't a number returns an empty array.

| Where | Example |
| - | - |
| `GET /chains` `data.chainIds[]` | `137` |
| `POST /quotes`, `POST /swap` `params.chainId`, `params.fromChainId`, `params.toChainId` | `137` |
| `POST /support-chain`, `POST /tx-status` `chainId`; `POST /tx-status` response `fromChainId`, `toChainId` | `137` |
| `GET /tokens` and `GET /transactions/{hash}` `?chainId=`; response `data.chainId` | `?chainId=137`; `137` |
| Limit order `chainId`: request, response and `?chainId=` filter | `137`; `?chainId=137` |
| DCA `chainIdFrom`, `chainIdTo` | `137` |

```json 400 String sent to /support-chain theme={null}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request body",
    "details": [
      {
        "field": "chainId",
        "message": "Invalid input: expected number, received string"
      }
    ]
  },
  "meta": {
    "requestId": "E7ee3hLMoAMEZxg=",
    "version": "v1",
    "accountType": "integrator",
    "apiKeyId": "00000000-0000-4000-8000-000000000000"
  }
}
```

[`GET /chains`](/api-reference/chains/list-chains) returns every enabled chain ID. [Supported chains](/concepts/supported-chains) lists them by name.

## IDs

* **Olympex assigns every ID.** Limit order and DCA strategy IDs are UUIDs, for example `afc47108-d059-473c-b2e1-5f2ca7951466`. DCA order IDs are strings that Olympex creates as a strategy runs. Treat every ID as an opaque string, and pass it in the path: `/limit-order/{id}`.
* **IDs are scoped to your API key.** A limit order or DCA strategy belongs to the API key that created it, and a DCA order belongs to its strategy's key. Lists return only your key's resources. An ID that doesn't exist or belongs to another API key returns [`404 NOT_FOUND`](#not-found).
* **Keep the key with the ID.** If you use more than one API key, store which key created each order or strategy. `meta.apiKeyId` in the create response names it.

## Booleans and enums

* Booleans are JSON `true` and `false`. The string `"true"` is rejected.
* Enum values are case-sensitive. `mode` is `"single-chain"` or `"cross-chain"`; `orderBy` and `gasMultiplier` values are uppercase, for example `"MAX_OUT_AMOUNT"` and `"MEDIUM"`.
* Limit order, DCA strategy and DCA order statuses are lowercase, for example `"pending"` and `"cancelled"`. Each endpoint page lists the values.

```json 400 String boolean and lowercase enum theme={null}
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request body",
    "details": [
      {
        "field": "params.includeGasInfo",
        "message": "Invalid input: expected boolean, received string"
      },
      {
        "field": "params.orderBy",
        "message": "Invalid option: expected one of \"MAX_ESTIMATE_GAS\"|\"MAX_OUT_AMOUNT\"|\"MIN_ESTIMATE_GAS\""
      }
    ]
  },
  "meta": {
    "requestId": "ENiSJiGxoAMEJ5Q=",
    "version": "v1",
    "accountType": "integrator",
    "apiKeyId": "00000000-0000-4000-8000-000000000000"
  }
}
```

## Optional and empty fields

* An optional response field can be missing or `null`. Treat both as "not provided".
* `integratorFeeBreakdown` is present on every quote. It reports the Olympex protocol fee, which applies even when you don't send `fees`, and your fee: `integratorMarginBps` equals `fees.feeBps`, and is `0` when you don't send `fees`.
* `dataFeeTransaction` appears only when the single-chain quote request sets `includeGasInfo: true`.
* `/tx-status` returns `fromTxHash`, `toTxHash`, amounts, token addresses and `bridgeHash` only when the provider reports them. `errorMsg` is always `null` in a `200`, because a provider error comes back as `500 TX_STATUS_ERROR`. Display `detailStatus` instead.
* `GET /transactions/{hash}` returns `blockNumber` and `gasUsed` as `null`, and `confirmations` as `0`, until the transaction is mined.
* A limit order's `txHash` is an empty string until the order executes, `reasonFail` is an empty array unless execution failed, and `deletedAt` is an empty string until you cancel the order.
* A DCA strategy omits `minPrice` and `maxPrice` when you didn't set them. A DCA order carries `amountReceived`, `executionPrice` and `transactionHash` once it executes, and `errorMessage` when it fails.
* Lists that have no entries are empty arrays, for example `"market": []` or `"data": []`.

## Casing

| Element | Casing | Examples |
| - | - | - |
| Paths | kebab-case | `/support-chain`, `/tx-status`, `/limit-order`, `/dca-order/strategies` |
| Query parameters, body and response fields | camelCase | `chainId`, `accountTo`, `inTokenAddress`, `aggregatorId` |
| `mode` values | kebab-case | `single-chain`, `cross-chain` |
| Status values | lowercase | `pending`, `cancelled` |
| Other enum values | SCREAMING\_SNAKE\_CASE | `MAX_OUT_AMOUNT`, `MEDIUM` |
| `error.code` values | SCREAMING\_SNAKE\_CASE | `VALIDATION_ERROR` |
| Header names | Case-insensitive. Code samples write them in lowercase. | `x-api-key-id`, `content-type` |
| `aggregatorId` values | Exactly as returned | `oneInch`, `zeroExV2AllowanceHolder` |

## Timestamps

| Value | Format | Example |
| - | - | - |
| Signing timestamp, inside `x-value-info` | Unix time in **seconds**. A millisecond timestamp is rejected. | `1758700000` |
| `createdAt`, `updatedAt` on limit orders, DCA strategies and DCA orders | ISO 8601 string in UTC. On limit orders and DCA strategies, `updatedAt` changes on every successful `PATCH` or `DELETE`, even one that writes the same values. | `"2026-09-29T16:01:22.237Z"` |
| `deletedAt` on a limit order | ISO 8601 string in UTC once you cancel the order: the time of the `DELETE` call that cancelled it. An empty string until then. | `""` |
| `expired` on a limit order | A Unix timestamp in milliseconds, as a 13-digit string, in the future and at most 365 days ahead. Any other value, such as a time in seconds, returns `400 VALIDATION_ERROR`. | `String(Date.now() + 7 * 86_400_000)` |
| `frequency` on a DCA strategy | Seconds between orders, as an integer. | `86400` is one order a day. |
| `estimatedTime` on a cross-chain quote | The provider's transfer time estimate, as a string, when the provider reports it. | |

The signing timestamp is in seconds and `expired` is in milliseconds. Don't reuse one for the other.

## Lists

* **No pagination.** Each list endpoint returns every match in one response. Use the filters to keep responses small.

* **Unsorted unless stated.** Sort limit orders by `createdAt` yourself when the order matters.

  | Endpoint | Order | Filters |
  | - | - | - |
  | `GET /chains` | Ascending numeric chain ID | None |
  | `GET /tokens` | Unsorted | `chainId` (required) |
  | `GET /limit-order` | Unsorted | `status`, `chainId`, `accountTo` |
  | `GET /dca-order/strategies` | Newest first | `status`, `accountTo` |
  | `GET /dca-order/strategies/{id}/orders` | Oldest first | None |

* **Filters combine with AND.** `accountTo` matches case-sensitively.

* **Unknown query parameters.** The order and strategy lists ignore unknown query parameters, so a misspelt filter such as `?acountTo=` returns the full list. Send each parameter once: a repeated parameter matches nothing. `GET /chains` ignores query parameters too, while `GET /tokens` rejects any parameter other than `chainId`.

* **Cancelled limit orders stay in the list.** Filter by `?status=` to see one status only, for example `pending`.

* **Only your API key's resources.** See [IDs](#ids).

## Errors

Errors come from two layers. If the body has a `success` key, it is an Olympex error envelope. If it has only `message`, it is a [gateway response](#gateway-responses).

### Error envelope

`error.code` is the stable, machine-readable value to branch on, together with the HTTP status. `error.message` is a human-readable summary: don't parse it. `error.details` takes one of three forms:

| Form | When | Example |
| - | - | - |
| `field` and `message` | Schema validation errors (`400` `Invalid request body` or `Invalid query parameters`). `field` is a dot path such as `params.chainId`, empty for the top level. | `{"field": "params.inTokenAddress", "message": "Must be a valid EVM address"}` |
| `message` only | Errors from a handler, such as `NO_ROUTE` when no source has a route. The text is the source's message and varies with the cause: display it, don't parse it. | `{"message": "The quote could be retrieved but the route is not available"}` |
| Empty list | Errors with nothing more to add, such as `400` `Invalid JSON body` or `Body is required`, `FORBIDDEN` `Invalid body hash`, `NOT_FOUND` and `CONFLICT`. | `[]` |

```json 422 No route theme={null}
{
  "success": false,
  "error": {
    "code": "NO_ROUTE",
    "message": "No route found for this pair and amount. Providers may also be temporarily unavailable; retrying later can succeed.",
    "details": [
      {
        "message": "The quote could be retrieved but the route is not available"
      }
    ]
  },
  "meta": {
    "requestId": "E7ef-j_MoAMEPBw=",
    "version": "v1",
    "accountType": "integrator",
    "apiKeyId": "00000000-0000-4000-8000-000000000000"
  }
}
```

### 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`. |

### Not found

A request for an ID that doesn't exist, or that belongs to another API key, returns `404` with the code `NOT_FOUND`. Olympex doesn't tell the two cases apart, and a malformed limit order ID returns the same `404`, not a `400`. The message names the resource: `Limit order not found`, `DCA strategy not found` or `DCA order not found`. Don't retry it: check the ID and the API key you signed with.

```json 404 Unknown limit order theme={null}
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Limit order not found",
    "details": []
  },
  "meta": {
    "requestId": "EdjeXjypoAMEbrQ=",
    "version": "v1",
    "accountType": "integrator",
    "apiKeyId": "00000000-0000-4000-8000-000000000000"
  }
}
```

An unknown path or method also returns `404 NOT_FOUND`. Its message names the method and path, for example `"No route for GET /api/v1/quote"`, and its `meta` has no `accountType` or `apiKeyId`.

```json 404 Unknown route theme={null}
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "No route for GET /api/v1/quote",
    "details": []
  },
  "meta": {
    "requestId": "E7edogG4IAMEPYA=",
    "version": "v1"
  }
}
```

### Gateway responses

The API gateway answers some requests itself, before or instead of Olympex. These bodies have no `success`, `error` or `meta`. The spec names this body `GatewayError`.

| Status | Body | Cause | What to do |
| - | - | - | - |
| `401` | `{"message": "Unauthorized"}` | A signing header is missing or empty. | Send all four signed headers, signed again with a new nonce. If it fails again, stop and check your signer, and log the `apigw-requestid` response header. |
| `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. | Sign again once with a new nonce. If it fails again, stop and check your credentials and server clock. If correctly signed requests keep failing, contact [partners@olympex.io](mailto:partners@olympex.io) with the `apigw-requestid` response header. |
| `429` | `{"message": "Too Many Requests"}` | Too many requests in a short time. | 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` | `{"message": "Internal Server Error"}` | The gateway could not get a response from Olympex. This can happen on the first request after a quiet period, after the gateway's timeout of about 30 seconds, and occasionally in the middle of an active session. | Sign again and retry once, then back off. See [Retries](#retries) before you retry a create. |
| `503` | `{"message": "Service Unavailable"}` | The request ran past the gateway timeout of about 30 seconds, or the service is briefly unavailable. | Retry with backoff, signing each attempt again. See [Retries](#retries) before you retry a create. |

### Retries

* `GET`, `PATCH` and `DELETE` are safe to repeat.
* `PATCH` and `DELETE /limit-order/{id}` return `409 CONFLICT` once the order isn't `pending`, so a repeated `DELETE` whose first attempt went through returns `409`: read the order to confirm it is `cancelled`. Don't retry a `409` unchanged.
* `422 NO_ROUTE` from `POST /quotes` can be temporary: retry with backoff, then change the amount or pair. On `POST /swap`, a `422 NO_ROUTE` or `500 SWAP_ERROR` means that source can't build the route now: build again with the next `aggregatorId` in the quote's `aggregatorOrder`.
* `POST /limit-order`, `POST /dca-order/strategies` and `POST /accounts` create a new resource on every success. After a timeout, don't retry them blindly: list your orders or strategies and match on your own fields first.

[Errors and retries](/authentication/errors-and-retries) explains which errors to retry and how to back off.

## Request IDs

* Every Olympex envelope carries `meta.requestId`. Log it with the HTTP status and `error.code` for every failed call.
* Gateway responses have no `meta.requestId`, but the response carries an `apigw-requestid` header with an ID. On Olympex responses that header has the same value as `meta.requestId`. Browsers can't read the header on cross-origin calls, so read it from a server or a terminal.
* The reference helpers in [Sign requests](/authentication/sign-requests) report no request ID for a gateway response, such as a `401` or `403` with only `message`. Log the `apigw-requestid` response header yourself for those. With curl, add `-i` or `-D -` to print the response headers.
* Include the request ID when you [contact support](/resources/support). It is the fastest way to find your request.

## Timeouts

The gateway stops waiting for a response after about 30 seconds and returns `500` or `503` with only a `message`. Set your HTTP client timeout slightly above that: the reference clients in [Sign requests](/authentication/sign-requests) use 35 seconds. Retry with backoff, and sign each attempt again.

## Forward compatibility

* **Ignore unknown response fields.** Within `v1`, responses can gain new fields, and token, order and strategy objects can carry fields you don't use. Don't fail validation on a field you don't recognize.
* **Handle unknown error codes by HTTP status.** New `error.code` values can be added.
* **Pass `aggregatorId` through.** Send `/swap` the exact value the quote returned instead of hardcoding one.
* **Treat unknown transfer statuses as in progress.** `/tx-status` reports each provider's own status values. [Get cross-chain transfer status](/api-reference/transactions/get-transaction-status) lists the success and failure values.
* **Treat unknown order statuses as not final.** A limit order, DCA strategy or DCA order `status` you don't recognize means the resource can still change: keep polling it.

## What's next

<CardGroup cols={2}>
  <Card title="Errors and retries" icon="circle-exclamation" href="/authentication/errors-and-retries">
    Which errors to retry, and how to back off.
  </Card>

  <Card title="Sign requests" icon="shield-halved" href="/authentication/sign-requests">
    Canonical JSON, the signature and reference code.
  </Card>

  <Card title="List chains" icon="link" href="/api-reference/chains/list-chains">
    The enabled chains.
  </Card>

  <Card title="OpenAPI specification" icon="code" href="/api-reference/openapi-spec">
    Every schema, and how to generate a typed client.
  </Card>
</CardGroup>


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