> For the complete documentation index, see [llms.txt](https://docs.veda.tech/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.veda.tech/api/swap-api.md).

# Swap API

The Swap API discovers the swap routes configured for a Veda vault, returns short-lived quotes, and builds ready-to-submit calldata for market orders and limit orders. Like the [Transaction Builder](/api/transaction-builder.md), it is build-only: the API never holds keys, signs, or broadcasts transactions.

> **Always verify calldata independently before signing**
>
> Confirm the `transaction_request.to`, decode `transaction_request.data` against the target contract ABI, and simulate the transaction. A successful API simulation is useful evidence, but it is not a substitute for your own review.

## Concepts

The API is scoped to swaps executed from a BoringVault through one of its registered BoringSwapper contracts.

* A **pair** is directional: `token_in → token_out`. The reverse direction is a separate pair.
* A **capability** describes a configured market order or limit order path for that pair.
* An **adapter** connects the BoringSwapper to a downstream liquidity protocol.
* A **market order quote** is short-lived and binds the selected adapter, amounts, fee, and expiration to a `quote_id`.
* A **transaction request** contains `to`, `data`, `value`, and, when available, `gas_limit`. Submit it from the strategist address after review.

### Market order workflow

Market orders use a short-lived quote:

1. **Discover** configured pairs and adapters — `GET /capabilities`
2. **Quote** an exact-input trade — `POST /quote`
3. **Select** a returned `quote_id` before its `expires_at`
4. **Build** the transaction — `POST /market`
5. **Verify, sign, and submit** the returned `transaction_request`

### Limit order workflow

Limit orders do **not** call `POST /quote` and do not use a `quote_id`. You specify the order terms directly when building the limit order:

1. **Discover** configured pairs and adapters — `GET /capabilities`
2. **Build** the limit order submission transaction — `POST /limit`
3. **Verify, sign, and submit** the returned `transaction_request`
4. **Cancel or replace** the order when needed — `POST /limit/{order_id}/cancel` or `POST /limit/{order_id}/replace`

***

The examples use illustrative addresses and a reference time of October 1, 2026 at 12:00 UTC. Use your configured addresses, a fresh quote ID, and future deadlines when making requests.

## 1. Check service health

```
GET /v1/swap/health
```

Returns `200` when the Swap API is ready and `503` when it is unavailable.

### Example request

```bash
curl https://api.veda.tech/v1/swap/health \
  -H "Authorization: Bearer $VEDA_API_KEY"
```

### Example response

```json
{
  "status": "healthy",
  "service": "swap-api"
}
```

***

## 2. Discover swap capabilities

```
GET /v1/swap/{chain}/{vault_address}/capabilities
```

Returns the BoringSwappers registered for the vault, their configured directional pairs, and the market order or limit order capabilities available for each pair.

> **Strategist authorization**
>
> Capabilities describe configured and runtime-supported routes. They do not by themselves prove that a particular strategist is authorized to execute the corresponding action. Strategist authorization is enforced when calldata is built and onchain.

### Path parameters

| Param           | Meaning                        |
| --------------- | ------------------------------ |
| `chain`         | Canonical lowercase chain slug |
| `vault_address` | BoringVault contract address   |

### Example request

```bash
curl "https://api.veda.tech/v1/swap/ethereum/0x1111111111111111111111111111111111111111/capabilities" \
  -H "Authorization: Bearer $VEDA_API_KEY"
```

### Response structure

The response is correlated as `vault → swappers → pairs → capabilities`:

| Field                                      | Meaning                                               |
| ------------------------------------------ | ----------------------------------------------------- |
| `data.chain`                               | Canonical chain slug                                  |
| `data.chain_id`                            | EVM chain ID                                          |
| `data.vault_address`                       | BoringVault address                                   |
| `data.swappers[].boring_swapper`           | Registered BoringSwapper address                      |
| `data.swappers[].pairs[].token_in`         | Input token for this directional pair                 |
| `data.swappers[].pairs[].token_out`        | Output token for this directional pair                |
| `data.swappers[].pairs[].max_slippage_bps` | Maximum configured slippage for the pair              |
| `data.swappers[].pairs[].rate_limit`       | Cached rate-limit snapshot, or `null`                 |
| `data.swappers[].pairs[].capabilities[]`   | Market order or limit order integrations for the pair |

Each pair can also include a nullable `rate_limit` snapshot. Capacity and availability are decimal strings in input-token display units; `refill_rate` is input tokens per second:

| Field           | Meaning                                                                                           |
| --------------- | ------------------------------------------------------------------------------------------------- |
| `status`        | `cached` when a snapshot is available; `unavailable` otherwise                                    |
| `enabled`       | Whether rate limiting is enabled; nullable                                                        |
| `capacity`      | Maximum bucket capacity in input-token display units; nullable                                    |
| `remaining`     | Stored bucket remainder at `last_refill`, in input-token display units; nullable                  |
| `last_refill`   | Onchain Unix timestamp for the refill baseline; nullable                                          |
| `refill_rate`   | Refill per second in input-token display units; nullable                                          |
| `available_now` | Projected availability in input-token display units from the cached bucket; nullable and advisory |
| `cached_at`     | Unix timestamp of the configuration refresh; nullable                                             |

Every capability includes:

| Field                 | Meaning                                                    |
| --------------------- | ---------------------------------------------------------- |
| `kind`                | `market` or `limit`                                        |
| `protocol`            | Downstream protocol identifier                             |
| `adapter_address`     | Adapter contract used by the BoringSwapper                 |
| `platform_fee_bps`    | Configured platform fee rate in basis points               |
| `protocol_parameters` | Limit-only settings such as partial-fill and sender policy |

### Example response

```json
{
  "data": {
    "chain": "ethereum",
    "chain_id": 1,
    "vault_address": "0x1111111111111111111111111111111111111111",
    "swappers": [
      {
        "boring_swapper": "0x2222222222222222222222222222222222222222",
        "pairs": [
          {
            "token_in": "0x3333333333333333333333333333333333333333",
            "token_out": "0x4444444444444444444444444444444444444444",
            "max_slippage_bps": 50,
            "capabilities": [
              {
                "kind": "market",
                "protocol": "oneinch",
                "adapter_address": "0x5555555555555555555555555555555555555555",
                "platform_fee_bps": 5
              },
              {
                "kind": "limit",
                "protocol": "cowswap",
                "adapter_address": "0x6666666666666666666666666666666666666666",
                "platform_fee_bps": 5,
                "protocol_parameters": {
                  "supports_partial_fills": true,
                  "sender_policy": "optional"
                }
              }
            ],
            "rate_limit": {
              "status": "cached",
              "enabled": true,
              "capacity": "10000",
              "remaining": "6000",
              "last_refill": "1790855940",
              "refill_rate": "1",
              "available_now": "6060",
              "cached_at": 1790856000
            }
          }
        ]
      }
    ]
  }
}
```

The rate-limit example has a capacity of 10,000 input tokens and a refill rate of 1 token per second. The stored remainder is 6,000 tokens. After 60 seconds, projected availability is 6,060 tokens. These values are illustrative; `available_now` is advisory because swaps after the last refresh may have consumed capacity.

***

## 3. Request a market order quote

```
POST /v1/swap/quote
```

Returns one or more short-lived quotes. Currently, only `exact_input` is supported. Set `direction` to `exact_input` and specify the input-token quantity in `amount`.

### Request body

| Field            | Type            | Required | Meaning                                                                                  |
| ---------------- | --------------- | -------- | ---------------------------------------------------------------------------------------- |
| `chain`          | string          | Yes      | Canonical chain slug                                                                     |
| `vault_address`  | string          | Yes      | BoringVault address                                                                      |
| `boring_swapper` | string          | Yes      | Restrict the request to one eligible BoringSwapper                                       |
| `token_in`       | string          | Yes      | ERC-20 input token address                                                               |
| `token_out`      | string          | Yes      | ERC-20 output token address                                                              |
| `direction`      | string          | No       | Defaults to `exact_input`. Currently, only `exact_input` is supported.                   |
| `amount`         | string          | Yes      | Positive input-token amount in display units                                             |
| `slippage_bps`   | integer         | Yes      | Slippage tolerance in basis points, from `0` to `9999`; capped at the route maximum      |
| `adapters`       | array or `null` | No       | Allow-list of 1–20 unique adapter addresses; omit to quote every eligible active adapter |

### Example request

Request a quote to swap 1.5 input tokens on Ethereum with a slippage tolerance of 50 bps (0.5%). The addresses below are illustrative and match the example responses. Replace them with your vault, BoringSwapper, and token addresses from the capabilities response.

```bash
curl -X POST https://api.veda.tech/v1/swap/quote \
  -H "Authorization: Bearer $VEDA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "chain": "ethereum",
    "vault_address": "0x1111111111111111111111111111111111111111",
    "boring_swapper": "0x2222222222222222222222222222222222222222",
    "token_in": "0x3333333333333333333333333333333333333333",
    "token_out": "0x4444444444444444444444444444444444444444",
    "direction": "exact_input",
    "amount": "1.5",
    "slippage_bps": 50
  }'
```

### Response structure

The endpoint returns `201 Created`. The response `data` contains `chain`, `chain_id`, `vault_address`, `token_in`, `token_out`, `direction`, the requested display-unit `amount`, and a `quotes` array.

Token amounts in the response are decimal strings in human-readable display units. For example, `"998"` means 998 tokens. Keep these values as strings to preserve precision.

Common quote fields:

| Field                      | Meaning                                                                                             |
| -------------------------- | --------------------------------------------------------------------------------------------------- |
| `boring_swapper`           | BoringSwapper used for this quote                                                                   |
| `price_impact_bps`         | Adverse deviation from the BoringSwapper oracle reference, in basis points; `null` when unavailable |
| `quote_id`                 | Opaque identifier passed to `POST /market`                                                          |
| `adapter` / `adapter_name` | Selected adapter address and display name                                                           |
| `protocol`                 | Downstream liquidity protocol                                                                       |
| `expires_at`               | Quote-session expiration in ISO-8601 UTC                                                            |
| `direction`                | `exact_input`                                                                                       |
| `platform_fee`             | Fee token and amount in token display units                                                         |
| `net_amount_out`           | Expected output after the platform fee, in output-token display units                               |
| `best_quote_delta_bps`     | Difference from the best returned quote; `0` is best                                                |
| `gas_estimate`             | Estimated gas, or `null`                                                                            |

Exact-input quote amount fields:

| Field                  | Meaning                                                                           |
| ---------------------- | --------------------------------------------------------------------------------- |
| `estimated_amount_out` | Expected gross output in output-token display units                               |
| `min_amount_out`       | Minimum gross output enforced by protocol calldata, in output-token display units |

### Example response

```json
{
  "data": {
    "chain": "ethereum",
    "chain_id": 1,
    "vault_address": "0x1111111111111111111111111111111111111111",
    "token_in": "0x3333333333333333333333333333333333333333",
    "token_out": "0x4444444444444444444444444444444444444444",
    "direction": "exact_input",
    "quotes": [
      {
        "quote_id": "quote_7MVmN4N0dQ",
        "adapter": "0x5555555555555555555555555555555555555555",
        "adapter_name": "1inch adapter",
        "protocol": "oneinch",
        "expires_at": "2026-10-01T12:05:00Z",
        "platform_fee": {
          "token": "0x4444444444444444444444444444444444444444",
          "amount": "0.5"
        },
        "best_quote_delta_bps": 0,
        "gas_estimate": 310000,
        "direction": "exact_input",
        "min_amount_out": "993.01",
        "boring_swapper": "0x2222222222222222222222222222222222222222",
        "estimated_amount_out": "998",
        "net_amount_out": "997.5",
        "price_impact_bps": null
      }
    ],
    "amount": "1.5"
  }
}
```

***

## 4. Build a market order

```
POST /v1/swap/market
```

Builds calldata for one quote. Call this endpoint before the selected quote's `expires_at`.

### Request body

| Field                | Type    | Required | Meaning                                       |
| -------------------- | ------- | -------- | --------------------------------------------- |
| `quote_id`           | string  | Yes      | `quote_id` returned by `POST /quote`          |
| `deadline_epoch`     | integer | Yes      | Unix deadline encoded into the transaction    |
| `strategist_address` | string  | Yes      | Strategist that will submit the transaction   |
| `skip_sim`           | boolean | No       | Skip API-side simulation; defaults to `false` |

### Example request

```bash
curl -X POST https://api.veda.tech/v1/swap/market \
  -H "Authorization: Bearer $VEDA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "quote_id": "quote_7MVmN4N0dQ",
    "deadline_epoch": 1790856240,
    "strategist_address": "0x8888888888888888888888888888888888888888",
    "skip_sim": false
  }'
```

### Response structure

| Field                            | Meaning                                                  |
| -------------------------------- | -------------------------------------------------------- |
| `data.order_type`                | `market`                                                 |
| `data.quote_id`                  | Quote used to build the transaction                      |
| `data.chain` / `data.chain_id`   | Resolved chain                                           |
| `data.vault_address`             | BoringVault whose assets are swapped                     |
| `data.boring_swapper`            | BoringSwapper used for execution                         |
| `data.protocol` / `data.adapter` | Selected downstream protocol and adapter                 |
| `data.platform_fee`              | Fee token and fee amount in token display units          |
| `data.transaction_request`       | Unsigned `to`, `data`, `value`, and optional `gas_limit` |
| `data.simulation`                | `success`, `simulation_url`, `gas_used`, and `skipped`   |

### Example response

```json
{
  "data": {
    "chain": "ethereum",
    "chain_id": 1,
    "vault_address": "0x1111111111111111111111111111111111111111",
    "boring_swapper": "0x2222222222222222222222222222222222222222",
    "protocol": "oneinch",
    "adapter": "0x5555555555555555555555555555555555555555",
    "platform_fee": {
      "token": "0x4444444444444444444444444444444444444444",
      "amount": "0.5"
    },
    "transaction_request": {
      "to": "0x7777777777777777777777777777777777777777",
      "data": "0x1234abcd...",
      "value": "0",
      "gas_limit": "310000"
    },
    "simulation": {
      "success": true,
      "simulation_url": "https://www.tdly.co/shared/simulation/example",
      "gas_used": 297421,
      "skipped": false
    },
    "order_type": "market",
    "quote_id": "quote_7MVmN4N0dQ"
  }
}
```

***

## 5. Build a limit order

```
POST /v1/swap/limit
```

Builds a limit order submission transaction and returns its public `order_id`. This endpoint does not consume a `quote_id`; the request supplies the limit price terms directly through `amount_in` and `amount_out`. Use the capability response to select a `boring_swapper` and an adapter that supports limit orders.

### Request body

| Field                    | Type             | Required | Meaning                                           |
| ------------------------ | ---------------- | -------- | ------------------------------------------------- |
| `chain`                  | string           | Yes      | Canonical chain slug                              |
| `vault_address`          | string           | Yes      | BoringVault address                               |
| `boring_swapper`         | string           | Yes      | BoringSwapper address                             |
| `adapter`                | string           | Yes      | Limit order adapter address                       |
| `strategist_address`     | string           | Yes      | Strategist that will submit the transaction       |
| `token_in` / `token_out` | string           | Yes      | Directional token pair                            |
| `amount_in`              | string           | Yes      | Input-token principal in display units            |
| `amount_out`             | string           | Yes      | Requested output amount in display units          |
| `valid_to`               | integer          | Yes      | Unix expiry timestamp                             |
| `protocol_parameters`    | object or `null` | No       | Protocol-specific partial-fill or sender settings |
| `skip_sim`               | boolean          | No       | Skip API-side simulation; defaults to `false`     |

`protocol_parameters` can contain:

| Field                | Meaning                                                 |
| -------------------- | ------------------------------------------------------- |
| `partially_fillable` | Requested partial-fill mode; must match adapter support |
| `allow_any_sender`   | Allow any solver to fill when supported                 |
| `allowed_sender`     | Restrict fills to one solver address when supported     |

### Example request

Offer 1,000 input tokens for 1,010 output tokens on Ethereum, with partial fills enabled. The order expires on October 2, 2026 at 12:00 UTC (`1790942400`). Addresses are illustrative; replace them with your configured vault, BoringSwapper, adapter, tokens, and strategist. Use a future expiry when submitting a request.

```bash
curl -X POST https://api.veda.tech/v1/swap/limit \
  -H "Authorization: Bearer $VEDA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "chain": "ethereum",
    "vault_address": "0x1111111111111111111111111111111111111111",
    "boring_swapper": "0x2222222222222222222222222222222222222222",
    "adapter": "0x6666666666666666666666666666666666666666",
    "strategist_address": "0x8888888888888888888888888888888888888888",
    "token_in": "0x3333333333333333333333333333333333333333",
    "token_out": "0x4444444444444444444444444444444444444444",
    "amount_in": "1000",
    "amount_out": "1010",
    "valid_to": 1790942400,
    "protocol_parameters": {
      "partially_fillable": true
    },
    "skip_sim": false
  }'
```

### Response structure

The `data` object contains `chain`, `chain_id`, `vault_address`, `boring_swapper`, `protocol`, `adapter`, `transaction_request`, and `simulation`, plus:

| Field          | Meaning                                                                     |
| -------------- | --------------------------------------------------------------------------- |
| `order_type`   | `limit`                                                                     |
| `order_id`     | Public order identifier in the form `ord_` plus 64 lowercase hex characters |
| `platform_fee` | Fee token and amount in token display units                                 |

### Example response

This illustrative response includes every response field. The calldata and simulation URL are placeholders. The fee uses the planned human-readable response format.

```json
{
  "data": {
    "chain": "ethereum",
    "chain_id": 1,
    "vault_address": "0x1111111111111111111111111111111111111111",
    "boring_swapper": "0x2222222222222222222222222222222222222222",
    "adapter": "0x6666666666666666666666666666666666666666",
    "protocol": "cowswap",
    "order_id": "ord_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "platform_fee": {
      "token": "0x3333333333333333333333333333333333333333",
      "amount": "0.5"
    },
    "transaction_request": {
      "to": "0x7777777777777777777777777777777777777777",
      "data": "0x1234abcd...",
      "value": "0",
      "gas_limit": "310000"
    },
    "simulation": {
      "success": true,
      "simulation_url": "https://www.tdly.co/shared/simulation/example",
      "gas_used": 297421,
      "skipped": false
    },
    "order_type": "limit"
  }
}
```

The API builds but does not broadcast the transaction. The order does not become active onchain until the returned transaction is submitted successfully.

***

## 6. Cancel or replace a limit order

Both endpoints build unsigned transactions. Review, sign, and broadcast the returned `transaction_request`.

### Cancel

```
POST /v1/swap/limit/{order_id}/cancel
```

The body requires `chain`, `vault_address`, `boring_swapper`, and `strategist_address`. `skip_sim` is optional and defaults to `false`.

```bash
curl -X POST https://api.veda.tech/v1/swap/limit/ord_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/cancel \
  -H "Authorization: Bearer $VEDA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "chain": "ethereum",
    "vault_address": "0x1111111111111111111111111111111111111111",
    "boring_swapper": "0x2222222222222222222222222222222222222222",
    "strategist_address": "0x8888888888888888888888888888888888888888",
    "skip_sim": false
  }'
```

The response `data` contains `chain`, `chain_id`, `vault_address`, `boring_swapper`, `adapter`, `protocol`, `order_id`, `order_type: "limit"`, `transaction_request`, and `simulation`.

### Example response

Calldata and simulation URLs are illustrative placeholders.

```json
{
  "data": {
    "chain": "ethereum",
    "chain_id": 1,
    "vault_address": "0x1111111111111111111111111111111111111111",
    "boring_swapper": "0x2222222222222222222222222222222222222222",
    "adapter": "0x6666666666666666666666666666666666666666",
    "protocol": "cowswap",
    "order_id": "ord_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "transaction_request": {
      "to": "0x7777777777777777777777777777777777777777",
      "data": "0x1234abcd...",
      "value": "0",
      "gas_limit": "310000"
    },
    "simulation": {
      "success": true,
      "simulation_url": "https://www.tdly.co/shared/simulation/example",
      "gas_used": 297421,
      "skipped": false
    },
    "order_type": "limit"
  }
}
```

### Replace

```
POST /v1/swap/limit/{order_id}/replace
```

Replacement builds the cancellation and new-order transaction data. The body requires `chain`, `vault_address`, `boring_swapper`, `strategist_address`, and `new_order`. `skip_sim` is optional and defaults to `false`. The `new_order` object requires `adapter`, `token_in`, `token_out`, `amount_in`, `amount_out`, and `valid_to`; `protocol_parameters` is optional:

```bash
curl -X POST https://api.veda.tech/v1/swap/limit/ord_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/replace \
  -H "Authorization: Bearer $VEDA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "chain": "ethereum",
    "boring_swapper": "0x2222222222222222222222222222222222222222",
    "strategist_address": "0x8888888888888888888888888888888888888888",
    "new_order": {
      "adapter": "0x6666666666666666666666666666666666666666",
      "token_in": "0x3333333333333333333333333333333333333333",
      "token_out": "0x4444444444444444444444444444444444444444",
      "amount_in": "1000",
      "amount_out": "1020",
      "valid_to": 1790942400,
      "protocol_parameters": {
        "partially_fillable": true
      }
    },
    "skip_sim": false,
    "vault_address": "0x1111111111111111111111111111111111111111"
  }'
```

The response `data` includes `chain`, `chain_id`, `vault_address`, `boring_swapper`, `adapter`, `protocol`, the new `order_id`, `replaced_order_id`, `order_type: "limit"`, `platform_fee`, `transaction_request`, and `simulation`.

### Example response

Calldata and simulation URLs are illustrative placeholders.

```json
{
  "data": {
    "chain": "ethereum",
    "chain_id": 1,
    "vault_address": "0x1111111111111111111111111111111111111111",
    "boring_swapper": "0x2222222222222222222222222222222222222222",
    "adapter": "0x6666666666666666666666666666666666666666",
    "protocol": "cowswap",
    "order_id": "ord_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    "platform_fee": {
      "token": "0x3333333333333333333333333333333333333333",
      "amount": "0.5"
    },
    "transaction_request": {
      "to": "0x7777777777777777777777777777777777777777",
      "data": "0x1234abcd...",
      "value": "0",
      "gas_limit": "310000"
    },
    "simulation": {
      "success": true,
      "simulation_url": "https://www.tdly.co/shared/simulation/example",
      "gas_used": 297421,
      "skipped": false
    },
    "order_type": "limit",
    "replaced_order_id": "ord_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  }
}
```

***

## Response headers

`X-Request-ID` identifies a request for log correlation and support. It is separate from quote, transaction, and order identifiers.

## Error handling

The Swap API uses these common statuses:

| HTTP  | Meaning                                                   |
| ----- | --------------------------------------------------------- |
| `401` | Missing or malformed authorization header                 |
| `403` | API key is invalid, revoked, expired, or lacks permission |
| `422` | Request validation failed                                 |
| `429` | Rate limit exceeded; inspect `retry_after` when present   |
| `503` | Swap API is unavailable                                   |

Validation errors use a `detail` array with the invalid field location, message, and error type. Entries may also include `input` and `ctx`:

```json
{
  "detail": [
    {
      "loc": [
        "body",
        "amount"
      ],
      "msg": "Field required",
      "type": "missing"
    }
  ]
}
```

Do not retry validation or authorization errors unchanged. Retry transient `5xx` responses with bounded exponential backoff, and request a fresh quote if the original `quote_id` has expired.
