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

# Build a swap

> Get unsigned transaction calldata for a quoted route, on one chain or across two chains.

`POST /swap` returns unsigned transaction calldata for a route: a swap on one chain (`mode: "single-chain"`) or a transfer between two chains (`mode: "cross-chain"`). Olympex never signs or broadcasts your transaction. You approve the input token when needed, then send the transaction from `account` with your own signer. Get a quote from [`POST /quotes`](/api-reference/quotes/get-quote) first and pass its `aggregatorId` here.

Signed endpoint. Send `Content-Type: application/json`, the canonical JSON body, and the four headers `x-api-key-id`, `x-value-info`, `x-passphrase` and `x-signature` described in [Sign requests](/authentication/sign-requests). The response is real mainnet calldata: broadcasting it moves funds. Cross-chain swap bodies use the same token fields as cross-chain quotes: `inTokenAddress` and `outTokenAddress`. Cross-chain responses return the calldata as `calldata`, single-chain responses as `data`.

<Warning>
  The response is real calldata for mainnet contracts. Broadcasting it from `account` moves that wallet's funds, and there is no testnet or sandbox. Start with small amounts.
</Warning>

## Request body

The swap takes the same trade as the quote, plus the wallet and the route:

| Purpose | Single-chain swap | Cross-chain swap | Cross-chain quote, for comparison |
| - | - | - | - |
| Chains | `chainId` (integer) | `fromChainId`, `toChainId` (integers) | `fromChainId`, `toChainId` |
| Token you sell | `inTokenAddress` | `inTokenAddress` | `inTokenAddress` |
| Token you receive | `outTokenAddress` | `outTokenAddress` | `outTokenAddress` |
| Size and protection | `amount`, `slippage` | `amount`, `slippage` | `amount`, `slippage` |
| Gas price hint | `gasPrice` (whole gwei, rounded up) | Not sent | Not sent |
| Wallet | `account` | `account` | Not sent |
| Route | `aggregatorId` | `aggregatorId` | Returned as `quote.aggregatorId` |

* **`aggregatorId`** is the value `POST /quotes` returned for the same pair and amount. Cross-chain swaps from signed API accounts can use `okx` or `rango`. On single-chain quotes, `quote.aggregatorOrder` lists every source that quoted, best first: if the swap fails with `422 NO_ROUTE` or `500 SWAP_ERROR`, or `eth_estimateGas` of its calldata reverts, build it again with the next one.
* **`account`** is the wallet that sends the transaction and receives the output (on the destination chain, for cross-chain). The calldata is bound to it.
* **Keep the quote's inputs.** Send the same `amount`, `slippage`, `gasPrice` (single-chain) and, if you used it, `fees` object. `amount` stays human-readable: `"10"` is 10 USDT. See [Gas and fees](/concepts/gas-and-fees) for `fees`.
* **`dryRun`** is accepted on single-chain bodies for compatibility and has no effect, because this endpoint never broadcasts. Cross-chain bodies reject it with `400`, like any other unknown top-level key.

## Response fields

| Field | Mode | Meaning |
| - | - | - |
| `to` | Both | The Olympex aggregator contract on the chain you send from. The transaction's `to`. |
| `data` | Single-chain | Transaction calldata. |
| `calldata` | Cross-chain | Transaction calldata. Same role as `data`, different name. |
| `value` | Both | Native token to send with the transaction, in wei. `"0"` for ERC-20 input. |
| `contractToApprove` | Both | The ERC-20 spender for this swap. Approve this address, not `to`: they can differ. It isn't the [order contract](/concepts/order-authorization#the-order-contract) that limit orders and DCA use. |
| `outAmount` | Single-chain | Expected output, in base units of the output token. `"9991223"` is 9.991223 USDC. |
| `minOutAmount` | Single-chain | Minimum output after slippage, in base units. The transaction reverts below it. |
| `dexHash` | Cross-chain | Identifies the provider. Store it with the transaction hash for [`POST /tx-status`](/api-reference/transactions/get-transaction-status). |
| `gasLimit`, `estimatedGas` | Both | Unreliable. See [Estimate gas yourself](#estimate-gas-yourself). |

Prices move between the quote and the swap. Show your user `outAmount` and `minOutAmount` from this response, not from the quote.

## Estimate gas yourself

`gasLimit` is `estimatedGas × 2`, and `estimatedGas` is unreliable: it's `"1500000"` as a placeholder when the source gives no estimate, it can be `"0"`, and on cross-chain routes it can be a wei amount rather than gas units (on `okx` routes, the gas price). A source's estimate also covers only its own part of the route, not the Olympex contracts, so the transaction uses more gas than the quote's `estimatedGas`. Estimate gas for the exact transaction with `eth_estimateGas` (`from: account`, `to`, `data` or `calldata`, `value`) and add a buffer, for example 20%. Use `gasLimit` only as a fallback, and never when it's `"0"`. [Gas and fees](/concepts/gas-and-fees) has more on both estimates.

## Check that the calldata executes

A `200` means Olympex built calldata for the route, not that the transaction succeeds: a source can return calldata that reverts. Always run `eth_estimateGas` on the exact transaction before you send it, and treat a revert as a failed build:

* **Rule out your side first.** Check the allowance to `contractToApprove`, the balance of `account` and the calldata's expiry.
* **Fall back to another route.** If those are fine, the route can't execute. For a single-chain swap, build it again with the next `aggregatorId` in the quote's `aggregatorOrder`. For a cross-chain swap, request a new quote and build with its `aggregatorId`. Show the user the new `outAmount` and `minOutAmount` before they sign.
* **Send right after you build.** Some routes include a market maker's firm quote (an RFQ leg) whose price expires within seconds of the build. If the estimate or the transaction reverts because that quote expired, build the swap again and send it at once.

## Execute the transaction

<Steps>
  <Step title="Approve the exact amount (ERC-20 input only)">
    Read `allowance(account, contractToApprove)` on the input token. If it's below `amount` in base units, send `approve(contractToApprove, amount)` for exactly that amount, never an unlimited one, and wait for it to confirm. Some tokens, such as USDT, require you to set the allowance to 0 before you change it: do that first. Native-token input needs no approval.
  </Step>

  <Step title="Estimate gas">
    Call `eth_estimateGas` from `account` with `to`, `data` (or `calldata`) and `value`, then add your buffer. Estimation usually fails until the approval confirms. If it still reverts once the approval has confirmed, don't send: [fall back to another route](#check-that-the-calldata-executes).
  </Step>

  <Step title="Send right away">
    Send `{to, data | calldata, value, gas}` from `account` as soon as you have the estimate. If the approval took a while to confirm, call `POST /swap` again before you send.
  </Step>

  <Step title="Track the result">
    Poll [`GET /transactions/{hash}`](/api-reference/transactions/get-transaction) with the chain ID until `status` is `success` or `reverted`, or read the receipt from your RPC. For a cross-chain transfer, then poll [`POST /tx-status`](/api-reference/transactions/get-transaction-status) with the source transaction hash, the source chain ID and `dexHash`.
  </Step>
</Steps>

<Note>
  The calldata carries an on-chain expiry, 5 minutes on most routes. Routes with a market maker's firm quote expire sooner, within seconds of the build. The calldata is also bound to `account`: another Olympex swap from the same account can invalidate calldata you built earlier. Call `POST /swap` right before the user signs, not when you first show the quote, and send at once.
</Note>

[Execute a swap](/guides/execute-a-swap) and [Cross-chain swap end-to-end](/guides/cross-chain-swap-end-to-end) cover the full flow, from quote to confirmed transaction.

Both `200` examples shorten the calldata with `…`: a real response carries the full hex string. The cross-chain `200` example goes from 10 USDT on Polygon to USDT on Ethereum through `rango`. Its `gasLimit`, `"323438069024099968"`, is derived from a wei amount, not a number of gas units: estimate gas yourself.

<RequestExample>
  ```bash cURL theme={null}
  # Single-chain swap calldata: 10 USDT to USDC on Polygon, routed through the quote's aggregatorId.
  # Needs openssl, curl and OLYMPEX_API_KEY_ID, OLYMPEX_SECRET_KEY, OLYMPEX_PASSPHRASE.
  METHOD=POST
  ENDPOINT=/swap
  QUERY=''
  BODY='{"mode":"single-chain","params":{"account":"0x1E67cb01969D79B2B895179e4A07D24a839dBb52","aggregatorId":"oneInch","amount":"10","chainId":137,"gasPrice":"35","inTokenAddress":"0xc2132d05d31c914a87c6611c10748aeb04b58e8f","outTokenAddress":"0x3c499c542cef5e3811e1192ce70d8cc03d5c3359","slippage":"1"}}'
  TS="${OLYMPEX_TIMESTAMP:-$(date +%s)}"
  NONCE="${OLYMPEX_NONCE:-$(openssl rand -hex 12)}"
  BODY_HASH="$(printf '%s' "$BODY" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')"
  VALUE_INFO="$(printf '%s\n%s\n%s' "$TS" "$NONCE" "$BODY_HASH")"
  MESSAGE="$(printf 'OLPX-HMAC-SHA256-V2\n%s\n%s\n%s\n%s\n%s\n%s' "$TS" "$NONCE" "$METHOD" "/api/v1$ENDPOINT" "$QUERY" "$BODY_HASH")"
  curl -sS -X "$METHOD" "https://api-rest.olympex.io/api/v1$ENDPOINT${QUERY:+?$QUERY}" \
    -H "content-type: application/json" \
    -H "x-api-key-id: $OLYMPEX_API_KEY_ID" \
    -H "x-value-info: $(printf '%s' "$VALUE_INFO" | openssl base64 -A)" \
    -H "x-passphrase: $OLYMPEX_PASSPHRASE" \
    -H "x-signature: $(printf '%s' "$MESSAGE" | openssl dgst -sha256 -hmac "$OLYMPEX_SECRET_KEY" -binary | od -An -v -tx1 | tr -d ' \n')" \
    --data-raw "$BODY"
  ```

  ```ts TypeScript theme={null}
  import { olympexRequest } from "./sign-request.ts"; // /authentication/sign-requests

  type QuoteData = { mode: "single-chain"; quote: { aggregatorId: string; aggregatorOrder: string[] | null; outAmount: string } };
  type SwapData = {
    mode: "single-chain";
    swap: { to: string; data: string; value: string; contractToApprove: string; outAmount: string; minOutAmount: string };
  };

  const params = {
    chainId: 137,
    inTokenAddress: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", // USDT
    outTokenAddress: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359", // USDC
    amount: "10",
    slippage: "1",
    gasPrice: "35",
  };

  const { quote } = await olympexRequest<QuoteData>("POST", "/quotes", { mode: "single-chain", params });
  const { swap } = await olympexRequest<SwapData>("POST", "/swap", {
    mode: "single-chain",
    params: { ...params, account: "0x1E67cb01969D79B2B895179e4A07D24a839dBb52", aggregatorId: quote.aggregatorId },
  });

  // Next: approve swap.contractToApprove for exactly 10 USDT, then run eth_estimateGas and send from `account` right away.
  // If the estimate reverts, build again with the next aggregatorId in quote.aggregatorOrder.
  const transaction = { to: swap.to, data: swap.data, value: BigInt(swap.value) };
  console.log(transaction, swap.contractToApprove, swap.minOutAmount);
  ```

  ```python Python theme={null}
  from sign_request import olympex_request  # /authentication/sign-requests

  params = {
      "chainId": 137,
      "inTokenAddress": "0xc2132d05d31c914a87c6611c10748aeb04b58e8f",  # USDT
      "outTokenAddress": "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",  # USDC
      "amount": "10",
      "slippage": "1",
      "gasPrice": "35",
  }

  quote = olympex_request("POST", "/quotes", {"mode": "single-chain", "params": params})["quote"]
  swap = olympex_request("POST", "/swap", {
      "mode": "single-chain",
      "params": {**params, "account": "0x1E67cb01969D79B2B895179e4A07D24a839dBb52", "aggregatorId": quote["aggregatorId"]},
  })["swap"]

  # Next: approve swap["contractToApprove"] for exactly 10 USDT, then run eth_estimateGas and send from `account` right away.
  # If the estimate reverts, build again with the next aggregatorId in quote["aggregatorOrder"].
  transaction = {"to": swap["to"], "data": swap["data"], "value": int(swap["value"])}
  print(transaction, swap["contractToApprove"], swap["minOutAmount"])
  ```
</RequestExample>

<ResponseExample>
  ```json 200 Single-chain theme={null}
  {
    "success": true,
    "data": {
      "mode": "single-chain",
      "swap": {
        "to": "0xA91e13e8BfEbbC57ea008bfb2dE94AdD3F484E68",
        "gasLimit": "3000000",
        "contractToApprove": "0xa754451D6d32aB624111e5120409a474D29C2364",
        "data": "0x8ad0a76c0000000000000000000000000000000000000000000000000000000000000020…0000000000000000",
        "minOutAmount": "9891061",
        "outAmount": "9991223",
        "value": "0",
        "estimatedGas": "1500000"
      }
    },
    "meta": {
      "requestId": "Edt_pjJloAMEVjg=",
      "version": "v1",
      "accountType": "integrator",
      "apiKeyId": "00000000-0000-4000-8000-000000000000"
    }
  }
  ```

  ```json 200 Cross-chain theme={null}
  {
    "success": true,
    "data": {
      "mode": "cross-chain",
      "swap": {
        "to": "0xA91e13e8BfEbbC57ea008bfb2dE94AdD3F484E68",
        "gasLimit": "323438069024099968",
        "calldata": "0x0044fcef0000000000000000000000000000000000000000000000000000000000000020…0000000000000000",
        "value": "0",
        "estimatedGas": "161719034512049984",
        "contractToApprove": "0x9f770bC130f57C2D1C7Bf9bb4965Ab7c79AdE22E",
        "dexHash": "0x2676e2ee99986d45baa8242aea50a63a90b996df49a22e8975ba756b3706279f"
      }
    },
    "meta": {
      "requestId": "EduNEjxWIAMEZew=",
      "version": "v1",
      "accountType": "integrator",
      "apiKeyId": "00000000-0000-4000-8000-000000000000"
    }
  }
  ```

  ```json 400 theme={null}
  {
    "success": false,
    "error": {
      "code": "VALIDATION_ERROR",
      "message": "Invalid request body",
      "details": [
        {
          "field": "params.account",
          "message": "Invalid input: expected string, received undefined"
        },
        {
          "field": "params.aggregatorId",
          "message": "Invalid input: expected string, received undefined"
        }
      ]
    },
    "meta": {
      "requestId": "ENJ8rjAeoAMEbpQ=",
      "version": "v1",
      "accountType": "integrator",
      "apiKeyId": "00000000-0000-4000-8000-000000000000"
    }
  }
  ```

  ```json 403 Gateway theme={null}
  {
    "message": "Forbidden"
  }
  ```

  ```json 500 theme={null}
  {
    "success": false,
    "error": {
      "code": "SWAP_ERROR",
      "message": "Error fetching single-chain swap",
      "details": [
        {
          "message": "Request failed with status code 400"
        }
      ]
    },
    "meta": {
      "requestId": "E7egOiScoAMEZWw=",
      "version": "v1",
      "accountType": "integrator",
      "apiKeyId": "00000000-0000-4000-8000-000000000000"
    }
  }
  ```
</ResponseExample>


## OpenAPI

````yaml api-reference/openapi.json POST /swap
openapi: 3.0.3
info:
  title: Olympex REST API
  version: 1.0.0
  description: >-
    Aggregated swap quotes and transactions, limit orders and DCA strategies on
    EVM chains, plus the chain and token catalog. Every response uses the same
    envelope, except the API gateway's own responses (`{"message": …}`): `401`
    or `403` when the signed headers are missing or rejected, `429` when it
    throttles requests, and `500` or `503` when it can't get a response from
    Olympex in time. Chain IDs are integers on every endpoint. Signed endpoints
    require four signed headers; see [Sign
    requests](https://docs.olympex.io/authentication/sign-requests).
  termsOfService: https://docs.olympex.io/legal/terms
  contact:
    name: Olympex partnerships
    email: partners@olympex.io
    url: https://docs.olympex.io
servers:
  - url: https://api-rest.olympex.io/api/v1
    description: Olympex REST API v1
security: []
tags:
  - name: Accounts
    description: Create API credentials.
  - name: Quotes
    description: Aggregated single-chain and cross-chain quotes.
  - name: Swap
    description: Unsigned transaction calldata for a quoted route.
  - name: TxStatus
    description: Track a cross-chain transfer after you broadcast it.
  - name: Transactions
    description: On-chain status of a transaction you broadcast.
  - name: Chains and tokens
    description: Enabled chains and the tokens listed on each.
  - name: Limit orders
    description: Orders that Olympex executes when the market reaches your price.
  - name: DCA
    description: Strategies that buy a token in equal orders over time.
  - name: Docs
    description: Machine-readable API description.
paths:
  /swap:
    post:
      tags:
        - Swap
      summary: Build a swap transaction
      description: >-
        Returns unsigned transaction calldata for a route. Olympex never signs
        or broadcasts the transaction: you approve `contractToApprove` for
        ERC-20 input, then send `{to, data | calldata, value}` from `account`
        with your own gas estimate. The calldata carries an on-chain expiry (5
        minutes on most routes), so request it right before you send. Signed API
        accounts can build cross-chain swaps with `okx` or `rango` only. Olympex
        doesn't simulate the calldata, so a `200` doesn't prove it executes: run
        `eth_estimateGas` from `account` before you send, and if it reverts,
        build the swap with the next `aggregatorId` in the quote's
        `aggregatorOrder` (cross-chain: request a new quote). Do the same when
        the build fails with `422 NO_ROUTE`, `500 SWAP_ERROR` or `500
        CROSS_CHAIN_SWAP_ERROR`. Routes with RFQ liquidity can expire within
        seconds, so send right after building.
      operationId: buildSwap
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SwapRequest'
            examples:
              singleChain:
                summary: 'Single-chain: 10 USDT to USDC on Polygon'
                value:
                  mode: single-chain
                  params:
                    account: '0x1E67cb01969D79B2B895179e4A07D24a839dBb52'
                    aggregatorId: oneInch
                    amount: '10'
                    chainId: 137
                    gasPrice: '35'
                    inTokenAddress: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f'
                    outTokenAddress: '0x3c499c542cef5e3811e1192ce70d8cc03d5c3359'
                    slippage: '1'
              crossChain:
                summary: 'Cross-chain: 10 USDT on Polygon to USDT on Ethereum'
                value:
                  mode: cross-chain
                  params:
                    account: '0x1E67cb01969D79B2B895179e4A07D24a839dBb52'
                    aggregatorId: rango
                    amount: '10'
                    fromChainId: 137
                    inTokenAddress: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f'
                    outTokenAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7'
                    slippage: '1'
                    toChainId: 1
      responses:
        '200':
          description: Transaction calldata. `data.mode` matches the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SwapSuccessResponse'
              examples:
                singleChain:
                  summary: Single-chain (calldata shortened)
                  value:
                    success: true
                    data:
                      mode: single-chain
                      swap:
                        to: '0xA91e13e8BfEbbC57ea008bfb2dE94AdD3F484E68'
                        gasLimit: '3000000'
                        contractToApprove: '0xa754451D6d32aB624111e5120409a474D29C2364'
                        data: >-
                          0x8ad0a76c0000000000000000000000000000000000000000000000000000000000000020…0000000000000000
                        minOutAmount: '9891061'
                        outAmount: '9991223'
                        value: '0'
                        estimatedGas: '1500000'
                    meta:
                      requestId: Edt_pjJloAMEVjg=
                      version: v1
                      accountType: integrator
                      apiKeyId: 00000000-0000-4000-8000-000000000000
                crossChain:
                  summary: >-
                    Cross-chain (calldata shortened); gasLimit here is not a gas
                    amount: estimate gas yourself
                  value:
                    success: true
                    data:
                      mode: cross-chain
                      swap:
                        to: '0xA91e13e8BfEbbC57ea008bfb2dE94AdD3F484E68'
                        gasLimit: '323438069024099968'
                        calldata: >-
                          0x0044fcef0000000000000000000000000000000000000000000000000000000000000020…0000000000000000
                        value: '0'
                        estimatedGas: '161719034512049984'
                        contractToApprove: '0x9f770bC130f57C2D1C7Bf9bb4965Ab7c79AdE22E'
                        dexHash: >-
                          0x2676e2ee99986d45baa8242aea50a63a90b996df49a22e8975ba756b3706279f
                    meta:
                      requestId: EduNEjxWIAMEZew=
                      version: v1
                      accountType: integrator
                      apiKeyId: 00000000-0000-4000-8000-000000000000
        '400':
          description: >-
            The body failed validation. `error.details` lists each field and why
            it failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: VALIDATION_ERROR
                  message: Invalid request body
                  details:
                    - field: params.account
                      message: 'Invalid input: expected string, received undefined'
                    - field: params.aggregatorId
                      message: 'Invalid input: expected string, received undefined'
                meta:
                  requestId: ENJ8rjAeoAMEbpQ=
                  version: v1
                  accountType: integrator
                  apiKeyId: 00000000-0000-4000-8000-000000000000
        '401':
          description: >-
            A signing header is missing. The gateway answers with
            `{"message":"Unauthorized"}` before your request reaches Olympex;
            the handler answers with the `UNAUTHORIZED` envelope when the
            authorizer context is missing.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/GatewayError'
                  - $ref: '#/components/schemas/ErrorResponse'
              example:
                message: Unauthorized
        '403':
          description: >-
            Authentication failed: unknown key, wrong passphrase, invalid
            signature, timestamp outside the ±300 s window, reused nonce, or
            inactive account (gateway `{"message":"Forbidden"}`). The handler
            answers with the `FORBIDDEN` envelope when the body does not match
            the signed `bodyHash`.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/GatewayError'
                  - $ref: '#/components/schemas/ErrorResponse'
              examples:
                gateway:
                  summary: Signature rejected by the gateway
                  value:
                    message: Forbidden
                bodyHash:
                  summary: Body does not match the signed hash
                  value:
                    success: false
                    error:
                      code: FORBIDDEN
                      message: Invalid body hash
                      details: []
                    meta:
                      requestId: ENXGDhsXIAMEMEw=
                      version: v1
                      accountType: integrator
                      apiKeyId: 00000000-0000-4000-8000-000000000000
        '404':
          description: >-
            Unknown path or wrong method (`NOT_FOUND`). `error.message` names
            the method and path.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: NOT_FOUND
                  message: No route for GET /api/v1/quote
                  details: []
                meta:
                  requestId: E7edogG4IAMEPYA=
                  version: v1
        '422':
          description: >-
            `NO_ROUTE`: no liquidity source returned a usable route for this
            pair and amount. Olympex doesn't yet tell apart no liquidity, an
            unsupported pair or amount, and every source failing or timing out,
            so it's safe to retry later. On a single-chain swap, build with the
            next `aggregatorId` in the quote's `aggregatorOrder`; on a
            cross-chain swap, request a new quote.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: >-
            Olympex could not build the swap (`SWAP_ERROR` or
            `CROSS_CHAIN_SWAP_ERROR`, with the source's message in `details`) or
            hit an unexpected error (`INTERNAL_ERROR`), or the gateway could not
            get a response (`{"message":"Internal Server Error"}`), for example
            after a timeout of about 30 seconds. Treat `SWAP_ERROR` like `422
            NO_ROUTE`: build with the next `aggregatorId` in the quote's
            `aggregatorOrder`. On `CROSS_CHAIN_SWAP_ERROR`, request a new quote.
            Otherwise, retry with backoff.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/ErrorResponse'
                  - $ref: '#/components/schemas/GatewayError'
              example:
                success: false
                error:
                  code: SWAP_ERROR
                  message: Error fetching single-chain swap
                  details:
                    - message: Request failed with status code 400
                meta:
                  requestId: E7egOiScoAMEZWw=
                  version: v1
                  accountType: integrator
                  apiKeyId: 00000000-0000-4000-8000-000000000000
        '503':
          description: >-
            The gateway could not get a response in time (the integration
            timeout is about 30 seconds) or the service is briefly unavailable.
            Returned by the gateway. Retry with backoff.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GatewayError'
              example:
                message: Service Unavailable
      security:
        - ApiKeyId: []
          ValueInfo: []
          Passphrase: []
          Signature: []
components:
  schemas:
    SwapRequest:
      oneOf:
        - $ref: '#/components/schemas/SingleChainSwapRequest'
        - $ref: '#/components/schemas/CrossChainSwapRequest'
      discriminator:
        propertyName: mode
        mapping:
          single-chain: '#/components/schemas/SingleChainSwapRequest'
          cross-chain: '#/components/schemas/CrossChainSwapRequest'
    SwapSuccessResponse:
      type: object
      required:
        - success
        - data
        - meta
      properties:
        success:
          type: boolean
          enum:
            - true
        data:
          oneOf:
            - title: Single-chain
              type: object
              required:
                - mode
                - swap
              properties:
                mode:
                  type: string
                  enum:
                    - single-chain
                swap:
                  $ref: '#/components/schemas/SingleChainSwap'
            - title: Cross-chain
              type: object
              required:
                - mode
                - swap
              properties:
                mode:
                  type: string
                  enum:
                    - cross-chain
                swap:
                  $ref: '#/components/schemas/CrossChainSwap'
        meta:
          $ref: '#/components/schemas/Meta'
    ErrorResponse:
      type: object
      required:
        - success
        - error
        - meta
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          $ref: '#/components/schemas/ErrorBody'
        meta:
          $ref: '#/components/schemas/Meta'
    GatewayError:
      type: object
      required:
        - message
      description: >-
        Returned by the API gateway itself: `401` or `403` when the signed
        headers are missing or rejected, `429` when it throttles requests, and
        `500` or `503` when it can't get a response from Olympex in time. It has
        no `success`, `error` or `meta`.
      properties:
        message:
          type: string
          example: Forbidden
    SingleChainSwapRequest:
      title: Single-chain
      type: object
      required:
        - mode
        - params
      properties:
        mode:
          type: string
          enum:
            - single-chain
        dryRun:
          type: boolean
          default: true
          description: >-
            Accepted for compatibility and ignored: `POST /swap` never
            broadcasts, whatever the value.
        params:
          type: object
          required:
            - chainId
            - inTokenAddress
            - outTokenAddress
            - account
            - amount
            - slippage
            - gasPrice
            - aggregatorId
          properties:
            chainId:
              type: integer
              minimum: 0
              exclusiveMinimum: true
              maximum: 9007199254740991
              description: EVM chain ID as an integer.
            inTokenAddress:
              type: string
              minLength: 1
              description: >-
                Token you sell. Use `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`
                for the native token. Any valid EVM address (lowercase or EIP-55
                checksum).
            outTokenAddress:
              type: string
              minLength: 1
              description: >-
                Token you buy. Any valid EVM address (lowercase or EIP-55
                checksum).
            account:
              type: string
              minLength: 1
              description: >-
                Wallet that sends the transaction and receives the output. The
                calldata is bound to it. Any valid EVM address (lowercase or
                EIP-55 checksum).
            amount:
              type: string
              minLength: 1
              description: >-
                Amount of the input token as a decimal string in human-readable
                units, for example `"1.5"` for 1.5 USDC. Do not convert to base
                units.
              example: '10'
            slippage:
              type: string
              minLength: 1
              description: Maximum slippage as a percentage string. `"1"` means 1%.
              example: '1'
            gasPrice:
              type: string
              minLength: 1
              description: >-
                Gas price hint in whole gwei as a string, for example `"35"`.
                Round up: some sources reject fractional gwei.
              example: '35'
            aggregatorId:
              type: string
              minLength: 1
              description: >-
                Liquidity source to build the transaction with. Pass the
                `aggregatorId` returned by `POST /quotes` for the same pair and
                amount.
        fees:
          $ref: '#/components/schemas/FeeOptions'
    CrossChainSwapRequest:
      title: Cross-chain
      type: object
      required:
        - mode
        - params
      additionalProperties: false
      properties:
        mode:
          type: string
          enum:
            - cross-chain
        params:
          type: object
          required:
            - account
            - inTokenAddress
            - outTokenAddress
            - fromChainId
            - toChainId
            - amount
            - slippage
            - aggregatorId
          properties:
            account:
              type: string
              minLength: 1
              description: >-
                Wallet that sends the transaction on the source chain and
                receives funds on the destination chain. Any valid EVM address
                (lowercase or EIP-55 checksum).
            inTokenAddress:
              type: string
              minLength: 1
              description: >-
                Token you send on the source chain. Any valid EVM address
                (lowercase or EIP-55 checksum).
            outTokenAddress:
              type: string
              minLength: 1
              description: >-
                Token you receive on the destination chain. Any valid EVM
                address (lowercase or EIP-55 checksum).
            fromChainId:
              type: integer
              minimum: 0
              exclusiveMinimum: true
              maximum: 9007199254740991
              description: Source chain ID as an integer.
            toChainId:
              type: integer
              minimum: 0
              exclusiveMinimum: true
              maximum: 9007199254740991
              description: Destination chain ID as an integer.
            amount:
              type: string
              minLength: 1
              description: >-
                Amount of the input token as a decimal string in human-readable
                units, for example `"1.5"` for 1.5 USDC. Do not convert to base
                units.
              example: '10'
            slippage:
              type: string
              minLength: 1
              description: Maximum slippage as a percentage string. `"1"` means 1%.
              example: '1'
            aggregatorId:
              type: string
              minLength: 1
              description: >-
                Cross-chain provider from your quote. Signed API accounts can
                use `okx` or `rango`.
        fees:
          $ref: '#/components/schemas/FeeOptions'
    SingleChainSwap:
      type: object
      required:
        - to
        - gasLimit
        - data
        - outAmount
        - minOutAmount
        - value
        - contractToApprove
      properties:
        to:
          type: string
          description: Olympex aggregator contract on the chain. The transaction's `to`.
        data:
          type: string
          description: Transaction calldata.
        value:
          type: string
          description: >-
            Native token to send with the transaction, in wei. `"0"` for ERC-20
            input.
        contractToApprove:
          type: string
          description: >-
            Spender to approve for ERC-20 input before you send. Not needed for
            native-token input.
        outAmount:
          type: string
          description: Expected output in base units of `outTokenAddress`.
        minOutAmount:
          type: string
          description: >-
            Minimum output after slippage, in base units. The transaction
            reverts below it.
        gasLimit:
          type: string
          description: >-
            `estimatedGas × 2`. Not reliable for every source; estimate gas
            yourself (`eth_estimateGas` plus a buffer).
        estimatedGas:
          type: string
          nullable: true
          description: >-
            Source estimate in gas units. `"1500000"` is a placeholder used when
            the source gives none; `"0"` means unknown.
    CrossChainSwap:
      type: object
      required:
        - to
        - gasLimit
        - calldata
        - value
        - contractToApprove
        - dexHash
      properties:
        to:
          type: string
          description: >-
            Olympex aggregator contract on the source chain. The transaction's
            `to`.
        calldata:
          type: string
          description: >-
            Transaction calldata. Cross-chain responses name it `calldata`, not
            `data`.
        value:
          type: string
          description: Native token to send, in wei.
        contractToApprove:
          type: string
          description: Spender to approve for ERC-20 input before you send.
        dexHash:
          type: string
          description: >-
            Identifies the provider. Pass it to `POST /tx-status` with the
            source transaction hash.
        gasLimit:
          type: string
          description: >-
            Derived from the provider's estimate, whose unit varies by provider.
            Estimate gas yourself.
        estimatedGas:
          type: string
          nullable: true
          description: Provider-reported estimate. Display only.
    Meta:
      type: object
      required:
        - requestId
        - version
      properties:
        requestId:
          type: string
          description: Unique ID for this request. Include it when you contact support.
        version:
          type: string
          default: v1
          description: API version that served the request.
        accountType:
          type: string
          description: >-
            Account type resolved by the signature check, for example
            `integrator`. Present when the request was authenticated; absent on
            the `404` for an unknown path or method.
        apiKeyId:
          type: string
          format: uuid
          description: >-
            The API key ID that signed the request. Present when the request was
            authenticated; absent on the `404` for an unknown path or method.
    ErrorBody:
      type: object
      required:
        - code
        - message
        - details
      properties:
        code:
          type: string
          description: >-
            Machine-readable error code. `VALIDATION_ERROR` (400),
            `UNAUTHORIZED` (401), `FORBIDDEN` (403), `NOT_FOUND` (404, also for
            an unknown path or method), `CONFLICT` (409), `NO_ROUTE` (422), and
            for 500: `QUOTE_ERROR`, `CROSS_CHAIN_QUOTE_ERROR`, `SWAP_ERROR`,
            `CROSS_CHAIN_SWAP_ERROR`, `SUPPORT_CHAIN_ERROR`,
            `ENABLED_CHAINS_ERROR`, `TOKEN_LIST_ERROR`, `TX_STATUS_ERROR`,
            `INTERNAL_ERROR`. Olympex can add codes: handle an unknown code by
            its HTTP status.
        message:
          type: string
          description: Human-readable summary. Don't parse it.
        details:
          type: array
          items:
            $ref: '#/components/schemas/ErrorDetail'
    FeeOptions:
      type: object
      description: >-
        Your integrator fee. It appears in
        `integratorFeeBreakdown.integratorMarginBps` and
        `integratorMarginAmount`.
      properties:
        feeBps:
          type: integer
          minimum: 0
          maximum: 100
          description: Your fee in basis points, from 0 to 100 (1%).
          example: 25
        feeRecipient:
          type: string
          minLength: 1
          description: >-
            EVM address that receives your fee. Required when `feeBps` is
            greater than 0; the zero address is rejected.
          example: '0x1E67cb01969D79B2B895179e4A07D24a839dBb52'
    ErrorDetail:
      type: object
      additionalProperties: true
      description: >-
        Validation errors carry `field` and `message`; other errors carry
        `message` only.
      properties:
        field:
          type: string
          description: >-
            Dot path of the invalid field, for example `params.chainId`. Empty
            for the top level.
        message:
          type: string
  securitySchemes:
    ApiKeyId:
      type: apiKey
      in: header
      name: x-api-key-id
      description: >-
        Your API key ID (UUID). See [Sign
        requests](https://docs.olympex.io/authentication/sign-requests).
    ValueInfo:
      type: apiKey
      in: header
      name: x-value-info
      description: >-
        Base64 of `timestamp + "\n" + nonce + "\n" + bodyHash`, where
        `timestamp` is Unix seconds, `nonce` is 24 new hexadecimal characters
        and `bodyHash` is the unpadded base64url SHA-256 of the canonical body.
    Passphrase:
      type: apiKey
      in: header
      name: x-passphrase
      description: Your account passphrase. Treat it like the secret key.
    Signature:
      type: apiKey
      in: header
      name: x-signature
      description: >-
        Lowercase hex HMAC-SHA256 (signature v2), keyed with your secret key
        string (UTF-8, not decoded), of seven lines joined with `\n`, with no
        trailing newline: `OLPX-HMAC-SHA256-V2`, `timestamp`, `nonce`, the
        uppercase method, `path`, `canonicalQuery` and `bodyHash`. `path` is the
        request path including `/api/v1`, without the query string, for example
        `/api/v1/limit-order/<id>`. `canonicalQuery` is the query parameters
        decoded (`+` is a space), sorted by key and then by value, re-encoded
        per RFC 3986 and joined with `&`, or the empty string when there is no
        query. Headers signed for one request are rejected on any other. See
        [Sign requests](https://docs.olympex.io/authentication/sign-requests).

````

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