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

# Quote endpoints

The Exchange API exposes four quote endpoints. They all wrap Kumbaya's smart-order-router and **all require a partner API key**. They differ in **which tokens they cover** and the **request/response shape**.

| Endpoint               | Method | Auth                      | Token allowlist | Use when                                            |
| ---------------------- | ------ | ------------------------- | --------------- | --------------------------------------------------- |
| `/api/v1/quote`        | `GET`  | Partner key (`x-api-key`) | Off             | One-shot quote with calldata and full route details |
| `/api/v1/quote`        | `POST` | Partner key (`x-api-key`) | **Enforced**    | Partner integrations, transaction-ready response    |
| `/api/v1/quote/open`   | `POST` | Partner key (`x-api-key`) | Off             | Quotes for any token pair (allowlist off)           |
| `/api/v1/quote/tokens` | `GET`  | Partner key (`x-api-key`) | -               | Returns the partner allowlist                       |

> **All quote endpoints are partner-gated.** Every one requires a valid `x-api-key`; without it they return `401 { "error": "Invalid API key" }`. "Open" refers to the token **allowlist** being off, not authentication. To get a key, see [Authentication](/developers/apis/exchange-api/authentication.md) or email **<support@kumbaya.xyz>**.

## GET `/api/v1/quote`

Quote with full route details. Returned calldata targets the chosen router. Requires an `x-api-key` header.

### Query parameters

| Param              | Type    | Required                     | Notes                                                        |
| ------------------ | ------- | ---------------------------- | ------------------------------------------------------------ |
| `chainId`          | string  | yes                          | e.g. `4326`                                                  |
| `tokenInAddress`   | address | yes                          |                                                              |
| `tokenInDecimals`  | int     | no, default `18`             |                                                              |
| `tokenOutAddress`  | address | yes                          |                                                              |
| `tokenOutDecimals` | int     | no, default `18`             |                                                              |
| `amount`           | string  | yes                          | Base units of the input token (or output if `type=exactOut`) |
| `type`             | string  | no, default `exactIn`        | `exactIn` or `exactOut`                                      |
| `slippageBps`      | string  | no, default `50`             | basis points (50 = 0.5%)                                     |
| `recipient`        | address | yes                          | Where the swap output should land                            |
| `routerType`       | enum    | no, default `swap-router-02` | `swap-router-02` or `universal`                              |
| `useRouterBalance` | boolean | no, default `false`          | `universal` only - see [POST notes](#post-apiv1quote)        |

### Response (`GetQuoteResponse`)

```jsonc
{
  "quote": "0.000000107555379638",
  "quoteGasAdjusted": "-0.000000087444620362",
  "gasUseEstimate": "195000",
  "gasUseEstimateUSD": "0.000585",
  "methodParameters": {
    "to": "0xE5BbEF8De2DB447a7432A47EBa58924d94eE470e",   // SwapRouter02 or UniversalRouter
    "value": "0x00",
    "calldata": "0x3593564c..."
  },
  "route": { /* full route shape - pools, sub-routes, amounts */ }
}
```

The `methodParameters` block is ready to submit as a transaction.

## POST `/api/v1/quote`

Partner endpoint. Enforces a per-chain token allowlist; an off-list token returns HTTP 400 with `{ "error": "Token not permitted by allowlist", "details": { "addresses": [...] } }`. Pull the live allowlist via `GET /api/v1/quote/tokens?chainId=…`.

### Request body (`QuoteRequest`)

```jsonc
{
  "fromToken":         "0x4200000000000000000000000000000000000006",
  "toToken":           "0x021ee124cF23D302A7f725AE7a01B77A8ce9782B",
  "fromAmount":        "1000000000000000000",          // base units
  "slippage":          0.005,                          // decimal fraction (NOT bps)
  "recipient":         "0xfd3964c84a62692c347235edcf6477bb87de1e9a",
  "routerType":        "swap-router-02",               // or "universal"
  "useRouterBalance":  false                           // universal only
}
```

* **`slippage`** here is a **decimal fraction** (e.g. `0.005` for 0.5%) - not basis points like the GET endpoint.
* `useRouterBalance: true` is for aggregator integrations that pre-funded `UniversalRouter` and want it to swap from its own balance instead of pulling from `msg.sender` via Permit2.

### Header

```
x-api-key: <YOUR_PARTNER_KEY>
```

### Response (`PartnerQuoteResponse`)

```jsonc
{
  "fromAmount":      "1000000000000000000",
  "toAmount":        "107555379638",
  "toAmountMin":     "107017602740",        // post-slippage floor
  "slippage":        0.005,
  "approveToAddress": "0x7E6c4Ada91e432efe5F01FbCb3492Bd3eb7ccD2E",
  "gasEstimation":   "195000",
  "transaction": {
    "to":       "0xE5BbEF8De2DB447a7432A47EBa58924d94eE470e",
    "value":    "0x00",
    "callData": "0x3593564c..."             // ⚠ note camelCase 'D'
  }
}
```

> ⚠️ The transaction object uses **`callData`** (capital D) here, while the GET response uses `calldata` inside `methodParameters`. They're different schemas; don't mix them up.

## POST `/api/v1/quote/open`

Same body and response shape as `POST /api/v1/quote`, and the same `x-api-key` requirement, but with the token allowlist **off** - it quotes any pair and returns `meta.allowlistApplied: false`.

## GET `/api/v1/quote/tokens`

Returns the partner allowlist for a chain.

### Query

| Param     | Required | Notes       |
| --------- | -------- | ----------- |
| `chainId` | yes      | e.g. `4326` |

### Response (`TokensResponse`)

```jsonc
{
  "chainId": 4326,
  "tokens": [
    {
      "address":  "0x4200000000000000000000000000000000000006",
      "symbol":   "WETH",
      "name":     "Wrapped Ether",
      "decimals": 18,
      "logoURI":  "https://assets.kumbaya.xyz/tokens/weth.png"
    }
  ]
}
```

`x-api-key` header required.

## Error codes

All quote endpoints share a `QuoteErrorReason` enum:

| `reason`                          | HTTP | Meaning                                               |
| --------------------------------- | ---- | ----------------------------------------------------- |
| `NO_ROUTE`                        | 404  | No liquidity pools connect the pair                   |
| `INSUFFICIENT_LIQUIDITY`          | 500  | Route exists but insufficient liquidity for this size |
| `NO_POOLS_AVAILABLE`              | 500  | No pools connect the input/output                     |
| `ROUTE_MISSING_METHOD_PARAMETERS` | 500  | Route found but couldn't generate calldata            |
| `INVALID_TOKEN_ADDRESS`           | 400  | Malformed address                                     |
| `TOKEN_NOT_FOUND`                 | 500  | Address isn't a deployed ERC-20                       |
| `TOKEN_DECIMALS_FETCH_FAILED`     | 500  | Couldn't read `decimals()`                            |
| `INVALID_AMOUNT`                  | 400  | Malformed amount                                      |
| `AMOUNT_TOO_SMALL`                | 400  | Below minimum trade threshold                         |
| `CHAIN_NOT_CONFIGURED`            | 400  | Valid chain ID but not enabled on this server         |
| `CHAIN_NOT_SUPPORTED`             | 400  | Unrecognized chain ID                                 |
| `RPC_ERROR`                       | 500  | Network/RPC issue                                     |
| `QUOTE_TIMEOUT`                   | 500  | Request timed out                                     |
| `ROUTE_BUILD_FAILED`              | 500  | Unexpected route-build error                          |

Error response shape:

```jsonc
{
  "error":   "Human-readable message",
  "reason":  "NO_ROUTE",
  "details": { "tokenIn": "0x...", "tokenOut": "0x...", "amountRaw": "..." }
}
```

## Picking GET vs POST

Both require a partner key. The difference is shape and allowlist:

* **GET `/api/v1/quote`** - query params, `slippageBps` (basis points), returns full `route` details plus a `methodParameters` block (`calldata`, lowercase). No allowlist. Good for a one-shot quote where you want the route.
* **POST `/api/v1/quote`** - JSON body, `slippage` as a decimal fraction, enforced allowlist, returns a transaction-ready `transaction` object (`callData`, capital D). The production path for partner integrations.
* **POST `/api/v1/quote/open`** - same as POST but with the allowlist off (any pair).
