# API Reference — Discovery

The Drip Tools REST API is served at:

```
https://drip.tools/api
```

## Conventions

- **Transport:** JSON over HTTPS. `Content-Type: application/json` for POST bodies. CORS is open.
- **No auth.** Endpoints are public. Requests are rate-limited per IP, `POST /api/quote` more
  tightly — see [Rate limits](https://docs.drip.tools/api/rate-limits.md).
- **Amounts.** All token amounts in quotes (`toAmounts` in, `fromTotal` / `fromAmounts` /
  `toAmounts` out) are **base-unit integer strings** — no decimals, no scientific notation. Convert
  with the token's `decimals`. Order-history amounts (`from.amount`, `to.amount`) are instead
  human-readable decimal strings.
- **Native token sentinel.** A token `address` of `0x0000000000000000000000000000000000000000`
  means the chain's native coin (ETH, BNB, POL, SOL, TON, BTC, …) — on every chain family.
- **Identities are strings, per family.** EVM addresses are `0x…` and compared case-insensitively;
  quote fields (`sender`, `fromToken`, `vault`) come back lowercased, order objects return the
  checksummed form — never compare EVM strings byte for byte. Solana keys are base58 and
  **case-sensitive**. A TON *payer* (`sender`) is accepted in any form and normalized to the
  non-bounceable `UQ…` form; TON *recipients* are kept exactly as sent. Never change the case of a
  non-EVM identity.
- **`chainId` types vary by endpoint.** `GET /chains`, `GET /quotes`, and order objects return
  `chainId` as a **string**; `POST /quote` and `GET /prices` use a **number**. Coerce defensively.
- **Timestamps** are epoch **seconds**.
- **Machine-readable.** The whole API is described in [`/openapi.json`](https://docs.drip.tools/openapi.json)
  (OpenAPI 3.1, validated against live responses by the docs checks). The docs themselves are
  available as markdown: [`/llms.txt`](https://docs.drip.tools/llms.txt) (index),
  [`/llms-full.txt`](https://docs.drip.tools/llms-full.txt) (everything in one file), or any page
  URL with `.md` appended. See [For agents](https://docs.drip.tools/guide/agents.md).
- **Warming up.** For about a minute after a backend restart, `GET /api/quotes`, `GET /api/prices`
  and `POST /api/quote` can answer `503 { "error": "WARMING_UP", "retryAfterSec": 5, "waitingFor": [...] }`
  with a `Retry-After` header. Wait and retry; it is not a rate limit.

## GET /api/chains

Lists every supported chain, its tokens, and where to pay. This is your entry point: FROM chains
(mainnet) expose `contracts.depository` — the depository contract on EVM, the treasury account on
Solana and TON. TO chains (testnet) carry no contract you interact with — payouts are sent to your
recipients by our relayers.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `unwrap` | `'true'` | no | Return one flat array `[...testnet, ...mainnet]` instead of `{ testnet, mainnet }`. |

**Response** — `{ testnet: [...], mainnet: [...] }`, or a flat array with `unwrap=true`. Each chain:

**Response**
```json
{
  "chainId": "42161",
  "name": "arbitrum",
  "displayName": "Arbitrum",
  "explorer": "https://arbiscan.io",
  "isTestnet": false,
  "isEnabled": true,
  "rpc": ["https://arbitrum.drpc.org", "https://arbitrum-one-rpc.publicnode.com"],
  "tokens": [
    {
      "symbol": "USDC",
      "displayName": "USD Coin",
      "decimals": 6,
      "address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
      "priceFeed": "0x50834F3163758fcC1Df9973b6e91f0F0F0434aD3"
    }
  ],
  "contracts": { "depository": "0x52361c9412DceC6A83980fb065D0276ec001fACE" }
}
```

A non-EVM FROM chain looks like this:

**Response**
```json
{
  "chainId": "1399811149",
  "name": "solana_mainnet",
  "displayName": "Solana",
  "explorer": "https://explorer.solana.com",
  "family": "solana",
  "cluster": "mainnet-beta",
  "isTestnet": false,
  "isEnabled": true,
  "rpc": ["https://…"],
  "tokens": [
    { "symbol": "SOL",  "displayName": "SOL",  "decimals": 9, "address": "0x0000000000000000000000000000000000000000",
      "priceFeed": { "chainId": 1, "address": "0x4ffC43a60e009B551865A93d232E33Fce9f01507" } },
    { "symbol": "USDC", "displayName": "USDC", "decimals": 6, "address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "priceFeed": { "chainId": 1, "address": "0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6" } }
  ],
  "contracts": { "depository": "94BkmkTF6ZFASfLfqEv4HV61GcWj3dykKZP67C2HTQnU" }
}
```

The TON FROM chain (`1667471768`, `family: "ton"`, token `GRAM`) has the same shape; its
`contracts.depository` is the treasury wallet `UQCpSpH8mxHJ02uj2QEcyujXf9a1XIUY_ROgpcmPnz_Cp0VK`,
given in the non-bounceable `UQ…` form.

| Field | Meaning |
| --- | --- |
| `chainId` | Chain id, **as a string**. Non-EVM chains use synthetic ids (Solana mainnet `1399811149`, TON mainnet `1667471768`). |
| `name` | Internal slug (`arbitrum`, `solana_mainnet`, …). Use `displayName` for labels. |
| `displayName` | Human-readable chain name. |
| `isTestnet` | `false` = FROM (mainnet) side; `true` = TO (testnet) side. |
| `isEnabled` | Disabled chains are still listed but not swappable. |
| `isPaused` | Present, and `true`, only while the chain is temporarily paused: still listed, but `POST /api/quote` refuses it. Comes with `pausedReason` (free text or `null`). |
| `family` | Present only on non-EVM chains: `solana` \| `ton` \| `bitcoin`. Absent = EVM. |
| `cluster` | Solana only: `mainnet-beta` (FROM), `testnet` \| `devnet` (TO). |
| `network` | Bitcoin only: `testnet` \| `testnet4` \| `signet`. |
| `explorer` | Block-explorer base URL for the chain. |
| `rpc` | `string[]` — ordered public RPC endpoints: `rpc[0]` is the primary, the rest are fallbacks to try in order. |
| `tokens[]` | `symbol`, `displayName`, `decimals`, `address` (`0x0…0` = native; a base58 mint on Solana), `priceFeed` (informational: the oracle feed the price comes from — a feed address, `{ chainId, address }` when the feed lives on another chain, or `0x` on TO tokens, which are not priced). Bitcoin testnet tokens add `quoteDecimals` (how many decimal places a TO amount may have) and `minTo` (per-recipient minimum, in whole tokens). |
| `contracts.depository` | **FROM chains** — the pay target: the depository contract (EVM) or the treasury account (Solana `base58`, TON `UQ…`). Absent → swaps not enabled on that chain. |

## GET /api/quotes (rates & liquidity)

The listed USD rate of each TO token and how much of it can be dispensed right now. Use it to
estimate cost and check availability before quoting.

> **Warning: Not the quote endpoint**
>
> `GET /api/quotes` (plural) is a read-only price list. The swap itself starts with
> [`POST /api/quote`](https://docs.drip.tools/api/quote.md) (singular), which prices and signs your order.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `chainId` | string/number | no* | TO chain id. |
| `symbol` | string | no* | TO token symbol. |

\* With **neither**, returns the full array of enabled TO tokens. With **both**, returns the single
matching object. With only one → `400 Missing chainId or symbol`. Both but no match →
`404 No quote found for symbol <symbol>`.

**Response**

```json
{
  "chainName": "Arbitrum Sepolia",
  "chainId": "421614",
  "symbol": "ETH",
  "address": "0x0000000000000000000000000000000000000000",
  "decimals": 18,
  "price": 0.25,
  "balance": "12513664307780914212000",
  "available": true
}
```

| Field | Meaning |
| --- | --- |
| `price` | USD rate of the TO token (a plain number) — the one `POST /api/quote` prices with. |
| `balance` | What can be dispensed right now, **base units**. Orders exceeding this are rejected `NO_LIQUIDITY`. |
| `available` | `false` when that token can't be dispensed right now (`NO_TO_PRICE` at quote time). |

The response carries a few more bookkeeping fields; the ones above are all you need.

`503 WARMING_UP` while the first balance pass after a restart is running.

## OpenAPI specification

Every endpoint on these pages is also described in an [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0)
document at [`https://docs.drip.tools/openapi.json`](https://docs.drip.tools/openapi.json): query
parameters, request and response schemas (conditional fields are optional), every error body with
its `code` and HTTP status, and the rate-limit headers. Import it into Postman, Insomnia or Swagger
UI, or generate a typed client from it. It is checked against the live API, like the field tables
here. The docs themselves are available as markdown — see [For agents](https://docs.drip.tools/guide/agents.md).

## Next

- Price your swap and lock the amounts: [POST /api/quote](https://docs.drip.tools/api/quote.md).
- Live FROM-token prices: [Prices](https://docs.drip.tools/api/prices.md).
- Track the resulting orders: [Orders](https://docs.drip.tools/api/orders.md).
