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

# Multi-Swap Flow

> Create and manage multiple swaps in a single batch — supports CEX and anonymous routing

## Overview

Multi-swap lets partners create multiple independent swap orders in a single API call, grouped under a shared `multiId`. Each order is individually priced and executed, but they can all be tracked together via a single status endpoint.

<Info>
  **Best For**: Platforms distributing payouts to multiple recipients, batch airdrop tools, or any use case requiring several simultaneous swaps from the same source token.
</Info>

<Warning>
  Multi-swap supports **CEX and anonymous routing only**. DEX orders and fixed rate are not currently supported. Each order in the batch is created using the standard `from`/`to`/`amount`/`addressTo` fields — no `quoteId` is needed.
</Warning>

## Key Features

<CardGroup cols={2}>
  <Card title="Single Request, Multiple Orders" icon="layer-group">
    Create up to many orders in one request, all linked by a shared `multiId`
  </Card>

  <Card title="CEX & Anonymous Routing" icon="lock">
    Supports standard (CEX) and private (anonymous) routing per order
  </Card>

  <Card title="Per-Order Status" icon="chart-line">
    Track all orders in the group with a single `GET /exchanges/multi/{multiId}` call
  </Card>
</CardGroup>

## How It Works

<Steps>
  <Step title="Get Tokens">
    Fetch CEX-supported tokens and note each token's `id`.
  </Step>

  <Step title="Create Multi-Swap">
    Call `POST /exchanges/multi` with an array of orders. Each order specifies token IDs, amount, destination address, and optional routing flags.
  </Step>

  <Step title="Monitor Status">
    Poll `GET /exchanges/multi/{multiId}` to track all orders in the group together.
  </Step>

  <Step title="Generate Batch Transaction (Solana only)">
    For Solana source tokens, call `GET /exchanges/multi/{multiId}/tx?sender={senderAddress}` to get pre-built batched transactions. Each transaction covers up to 10 deposit addresses — if your batch has more than 10 orders, the response returns multiple transactions to submit separately.
  </Step>
</Steps>

## Integration Guide

### Step 1: Get Tokens

Use token IDs (not symbols) when building your orders. Fetch and cache the token list from `/tokens`:

<Tabs>
  <Tab title="Bulk Fetch + Cache">
    <Warning>
      Cache the token list in your backend database. Never call `/tokens` on every user request — load on server startup or via a scheduled job and refresh every 24 hours.
    </Warning>

    <CodeGroup>
      ```javascript JavaScript theme={null}
      async function fetchAllCexTokens() {
        let page = 1;
        let allTokens = [];

        while (true) {
          const response = await fetch(
            `https://api-partner.houdiniswap.com/v2/tokens?hasCex=true&pageSize=20&page=${page}`,
            {
              headers: { 'Authorization': `${API_KEY}:${API_SECRET}` }
            }
          );

          const { tokens, totalPages } = await response.json();
          allTokens = allTokens.concat(tokens);

          if (page >= totalPages) break;
          page++;
        }

        return allTokens; // save to your DB
      }
      ```

      ```bash cURL theme={null}
      curl -X GET "https://api-partner.houdiniswap.com/v2/tokens?hasCex=true&pageSize=20&page=1" \
        -H "Authorization: your_api_key:your_api_secret"
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Token Search">
    <CodeGroup>
      ```javascript JavaScript theme={null}
      async function searchTokens(query) {
        const params = new URLSearchParams({
          term: query,
          hasCex: 'true',
          pageSize: '20',
          page: '1'
        });

        const response = await fetch(
          `https://api-partner.houdiniswap.com/v2/tokens?${params}`,
          { headers: { 'Authorization': `${API_KEY}:${API_SECRET}` } }
        );

        const { tokens } = await response.json();
        return tokens;
      }
      ```

      ```bash cURL theme={null}
      curl -X GET "https://api-partner.houdiniswap.com/v2/tokens?term=solana&hasCex=true&pageSize=20&page=1" \
        -H "Authorization: your_api_key:your_api_secret"
      ```
    </CodeGroup>
  </Tab>
</Tabs>

### Step 2: Create Multi-Swap

Call `POST /exchanges/multi` with an `orders` array. Each order is independent — different token pairs, amounts, and destination addresses are all supported in the same batch. Set `anonymous: true` on any order to enable private routing for that order.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const response = await fetch('https://api-partner.houdiniswap.com/v2/exchanges/multi', {
    method: 'POST',
    headers: {
      'Authorization': `${API_KEY}:${API_SECRET}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      orders: [
        {
          from: '6689b73ec90e45f3b3e51577',       // SOL token id
          to:   '6689b73ec90e45f3b3e51566',       // ETH token id
          amount: 10,
          addressTo: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'
        },
        {
          from: '6689b73ec90e45f3b3e51577',       // SOL token id
          to:   '6689b73ec90e45f3b3e51558',       // USDC token id
          amount: 25,
          addressTo: '0xABcD1234abcd1234ABCD1234abcd1234ABCD1234',
          anonymous: true                          // enable private routing for this order
        },
        {
          from: '6689b73ec90e45f3b3e51577',       // SOL token id
          to:   '6689b73ec90e45f3b3e51577',       // SOL token id
          amount: 5,
          addressTo: 'RecipientSolanaAddressHere1111111111111111',
          anonymous: true,
          useXmr: true                            // force XMR as anonymous bridge
        }
      ]
    })
  });

  const result = await response.json();
  console.log('Multi ID:', result.multiId);       // shared group identifier

  result.orders.forEach((item, i) => {
    if (item.error) {
      console.error(`Order ${i} failed:`, item.error.message, item.error.code);
    } else {
      console.log(`Order ${i} ID:`, item.order.houdiniId);
      console.log(`  Deposit to:`, item.order.depositAddress);
      console.log(`  Send:`, item.order.inAmount, item.order.inSymbol);
      console.log(`  Expires:`, item.order.expires);
    }
  });
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api-partner.houdiniswap.com/v2/exchanges/multi" \
    -H "Authorization: your_api_key:your_api_secret" \
    -H "Content-Type: application/json" \
    -d '{
      "orders": [
        {
          "from": "6689b73ec90e45f3b3e51577",
          "to": "6689b73ec90e45f3b3e51566",
          "amount": 10,
          "addressTo": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e"
        },
        {
          "from": "6689b73ec90e45f3b3e51577",
          "to": "6689b73ec90e45f3b3e51558",
          "amount": 25,
          "addressTo": "0xABcD1234abcd1234ABCD1234abcd1234ABCD1234",
          "anonymous": true
        }
      ]
    }'
  ```
</CodeGroup>

### Create Multi-Swap Response

```json theme={null}
{
  "multiId": "multi_a1b2c3d4e5f6",
  "orders": [
    {
      "order": {
        "houdiniId": "iBQMRX3xvXrFMGQi71ogo9",
        "multiId": "multi_a1b2c3d4e5f6",
        "created": "2025-12-25T06:13:46.673Z",
        "expires": "2025-12-25T06:43:46.673Z",
        "depositAddress": "SolDepositAddress111111111111111111111111111",
        "receiverAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
        "anonymous": false,
        "statusLabel": "NEW",
        "inAmount": 10,
        "inSymbol": "SOL",
        "outAmount": 0.0412,
        "outSymbol": "ETH",
        "eta": 15
      }
    },
    {
      "order": {
        "houdiniId": "kJRNSY4ywYsGNHRmO82ph0",
        "multiId": "multi_a1b2c3d4e5f6",
        "created": "2025-12-25T06:13:46.673Z",
        "expires": "2025-12-25T06:43:46.673Z",
        "depositAddress": "SolDepositAddress222222222222222222222222222",
        "receiverAddress": "0xABcD1234abcd1234ABCD1234abcd1234ABCD1234",
        "anonymous": true,
        "statusLabel": "NEW",
        "inAmount": 25,
        "inSymbol": "SOL",
        "outAmount": 2431.75,
        "outSymbol": "USDC",
        "eta": 35
      }
    },
    {
      "error": {
        "message": "Token pair not available",
        "code": "PAIR_UNAVAILABLE"
      }
    }
  ]
}
```

**Key Response Fields:**

* `multiId`: Shared identifier for the batch — use this to poll status for all orders
* `orders[].order`: Full order object for successfully created orders (see [Order Lifecycle](/developer-hub/core-concepts/order-lifecycle))
* `orders[].error`: Per-order error if creation failed — the rest of the batch is unaffected
* `depositAddress`: Each order has its own unique deposit address
* `statusLabel`: Starts as `NEW` — orders are initialized asynchronously

<Warning>
  Orders are created asynchronously. Some orders may return with `statusLabel: "INITIALIZING"` immediately after creation. Poll the multi status endpoint to confirm all orders reach `NEW` before proceeding.
</Warning>

### Order Fields Reference

| Field            | Description                                                        |
| ---------------- | ------------------------------------------------------------------ |
| `from`           | Token ID of input token (24-char MongoDB ObjectId)                 |
| `to`             | Token ID of output token                                           |
| `amount`         | Input swap amount                                                  |
| `addressTo`      | Destination wallet address for output funds                        |
| `anonymous`      | `true` to enable private multi-hop routing                         |
| `destinationTag` | Memo/tag for chains that require it (e.g. XRP, XLM) — max 64 chars |
| `useXmr`         | `true` to force XMR as the anonymous bridge layer                  |
| `walletInfo`     | Additional wallet metadata — max 256 chars                         |

### Step 3: Monitor Multi-Swap Status

Poll `GET /exchanges/multi/{multiId}` to retrieve status for all orders in the group at once:

<CodeGroup>
  ```javascript JavaScript theme={null}
  async function pollMultiStatus(multiId) {
    const response = await fetch(
      `https://api-partner.houdiniswap.com/v2/exchanges/multi/${multiId}`,
      {
        headers: { 'Authorization': `${API_KEY}:${API_SECRET}` }
      }
    );

    const { orders } = await response.json();

    orders.forEach(order => {
      console.log(`${order.houdiniId}: ${order.statusLabel}`);
      // statusLabel values:
      // INITIALIZING  — order being set up
      // NEW / WAITING — awaiting deposit
      // CONFIRMING    — deposit detected, awaiting confirmations
      // EXCHANGING    — CEX is processing
      // ANONYMIZING   — routing through privacy layer (anonymous orders only)
      // FINISHED      — complete, funds sent
      // EXPIRED       — deposit not received in time
      // FAILED        — swap failed
      // REFUNDED      — funds returned to sender
    });

    const allDone = orders.every(o =>
      ['FINISHED', 'FAILED', 'EXPIRED', 'REFUNDED'].includes(o.statusLabel)
    );

    return { orders, allDone };
  }

  // Poll every 30 seconds until all orders are terminal
  const interval = setInterval(async () => {
    const { orders, allDone } = await pollMultiStatus('multi_a1b2c3d4e5f6');
    if (allDone) clearInterval(interval);
  }, 30_000);
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api-partner.houdiniswap.com/v2/exchanges/multi/multi_a1b2c3d4e5f6" \
    -H "Authorization: your_api_key:your_api_secret"
  ```
</CodeGroup>

### Multi-Status Response

```json theme={null}
{
  "multiId": "multi_a1b2c3d4e5f6",
  "orders": [
    {
      "houdiniId": "iBQMRX3xvXrFMGQi71ogo9",
      "multiId": "multi_a1b2c3d4e5f6",
      "statusLabel": "WAITING",
      "displayStatus": "WAITING_FOR_DEPOSIT",
      "inAmount": 10,
      "inSymbol": "SOL",
      "outAmount": 0.0412,
      "outSymbol": "ETH",
      "depositAddress": "SolDepositAddress111111111111111111111111111",
      "receiverAddress": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
      "eta": 15,
      "created": "2025-12-25T06:13:46.673Z",
      "modified": "2025-12-25T06:13:50.001Z"
    },
    {
      "houdiniId": "kJRNSY4ywYsGNHRmO82ph0",
      "multiId": "multi_a1b2c3d4e5f6",
      "statusLabel": "EXCHANGING",
      "displayStatus": "DEPOSIT_DETECTED",
      "inAmount": 25,
      "inSymbol": "SOL",
      "outAmount": 2431.75,
      "outSymbol": "USDC",
      "depositAddress": "SolDepositAddress222222222222222222222222222",
      "receiverAddress": "0xABcD1234abcd1234ABCD1234abcd1234ABCD1234",
      "eta": 20,
      "created": "2025-12-25T06:13:46.673Z",
      "modified": "2025-12-25T06:20:12.443Z"
    }
  ]
}
```

<Tip>
  Poll every 30 seconds. Track each order's `statusLabel` independently — orders in the same batch progress at different rates. For a full list of statuses and transitions, see the [Order Lifecycle](/developer-hub/core-concepts/order-lifecycle) guide.
</Tip>

### Step 4: Generate Solana Batch Transaction

For Solana source tokens, you can fund all deposit addresses with pre-built transactions instead of sending separate transfers. Call `GET /exchanges/multi/{multiId}/tx?sender={senderAddress}`:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const senderAddress = 'YourSolanaWalletAddressHere111111111111111111';

  const response = await fetch(
    `https://api-partner.houdiniswap.com/v2/exchanges/multi/${multiId}/tx?sender=${senderAddress}`,
    {
      headers: { 'Authorization': `${API_KEY}:${API_SECRET}` }
    }
  );

  const { chain, transactions } = await response.json();

  console.log('Chain:', chain); // "solana"
  console.log('Transactions to submit:', transactions.length);

  for (const batch of transactions) {
    console.log('Covers order IDs:', batch.houdiniIds); // up to 10 per batch

    // batch.txData.data is a base64-encoded serialized Solana transaction
    const txBytes = Buffer.from(batch.txData.data, 'base64');

    const { Transaction, Connection, clusterApiUrl } = require('@solana/web3.js');
    const connection = new Connection(clusterApiUrl('mainnet-beta'));
    const tx = Transaction.from(txBytes);

    // Sign with your keypair or wallet adapter, then submit
    const signature = await connection.sendRawTransaction(tx.serialize());
    console.log('Submitted batch tx:', signature);
  }
  ```

  ```bash cURL theme={null}
  curl -X GET "https://api-partner.houdiniswap.com/v2/exchanges/multi/multi_a1b2c3d4e5f6/tx?sender=YourSolanaWalletAddressHere111111111111111111" \
    -H "Authorization: your_api_key:your_api_secret"
  ```
</CodeGroup>

### Batch Transaction Response

```json theme={null}
{
  "multiId": "multi_a1b2c3d4e5f6",
  "chain": "solana",
  "transactions": [
    {
      "houdiniIds": [
        "iBQMRX3xvXrFMGQi71ogo9",
        "kJRNSY4ywYsGNHRmO82ph0"
      ],
      "txData": {
        "data": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDa2V5..."
      }
    }
  ]
}
```

**Key Fields:**

* `chain`: Always `"solana"` — only Solana is supported for batch transactions
* `transactions`: Array of `TxBatch` objects — each covers up to 10 orders. Batches with more than 10 orders return multiple items; submit each transaction separately
* `houdiniIds`: The order IDs funded by this specific transaction
* `txData.data`: Base64-encoded serialized Solana transaction — deserialize, sign, and submit

<Warning>
  Batch transactions are only supported when all orders in the multi-swap share the same **Solana** source token.
</Warning>

## Best Practices

<AccordionGroup>
  <Accordion title="Handle Per-Order Errors" icon="triangle-exclamation">
    The multi-swap endpoint returns a partial success response — some orders may fail while others succeed. Always check each item in `orders[]` for an `error` field before proceeding, and handle failed orders independently without canceling the batch.
  </Accordion>

  <Accordion title="Wait for INITIALIZING to Resolve" icon="clock">
    Orders returned with `statusLabel: "INITIALIZING"` are still being set up. Poll `GET /exchanges/multi/{multiId}` until all orders reach `NEW` or `WAITING` before sending deposits.
  </Accordion>

  <Accordion title="Expiry Windows" icon="hourglass">
    Each order has its own `expires` timestamp (typically 30 minutes from creation). For Solana batch transactions, sign and submit all transactions promptly — if any order expires before confirmation, that order will not be funded.
  </Accordion>

  <Accordion title="Submitting Multiple Batch Transactions" icon="bolt">
    If your multi-swap has more than 10 Solana orders, the `transactions` array will contain multiple items. Submit each transaction independently — they are not dependent on each other and can be submitted in parallel.
  </Accordion>

  <Accordion title="Security" icon="shield">
    Never expose API keys in frontend code. Validate all `addressTo` values before submission. Store `multiId` and each `houdiniId` for audit trails and support lookups.
  </Accordion>
</AccordionGroup>

## Common Issues

<AccordionGroup>
  <Accordion title="Some Orders Return Errors">
    **Cause**: Individual orders can fail validation (unsupported pair, amount out of range, invalid address) while the rest of the batch succeeds.

    **Solution**: Check each `orders[].error` in the response. Re-submit failed orders individually with corrected parameters. The valid orders in the batch are unaffected.
  </Accordion>

  <Accordion title="Batch Transaction Not Available">
    **Cause**: Batch transactions require all orders to share the same Solana source token.

    **Solution**: Use separate individual deposits for non-Solana source tokens.
  </Accordion>

  <Accordion title="Order Expired Before Deposit">
    **Cause**: The 30-minute deposit window elapsed before funds were sent.

    **Solution**: Re-create the expired orders with a new multi-swap request. Deposit promptly after creation.
  </Accordion>

  <Accordion title="Orders Stuck at INITIALIZING">
    **Cause**: Order setup is asynchronous and may take a few seconds.

    **Solution**: Poll `GET /exchanges/multi/{multiId}` every 5–10 seconds until all orders leave the `INITIALIZING` state before proceeding.
  </Accordion>

  <Accordion title="Anonymous Order Taking Longer">
    **Cause**: Private routing passes through an additional `ANONYMIZING` stage.

    **Solution**: This is expected. Monitor `statusLabel` per order — anonymous orders typically complete in 15–45 minutes.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Standard Swap" icon="bolt" href="/developer-hub/swap-flows/standard-swap">
    Single-order CEX swaps
  </Card>

  <Card title="Private Swap" icon="lock" href="/developer-hub/swap-flows/private-swap">
    Multi-hop anonymous swaps
  </Card>

  <Card title="Order Lifecycle" icon="rotate" href="/developer-hub/core-concepts/order-lifecycle">
    Understand all order statuses
  </Card>

  <Card title="Error Handling" icon="triangle-exclamation" href="/developer-hub/troubleshooting/errors">
    Handle errors and edge cases
  </Card>
</CardGroup>
