# API Reference — Orders

After you pay, one payment becomes **one order per recipient**. These endpoints track them.

- [`GET /api/order`](https://docs.drip.tools/api/orders.md#get-api-order) — a single order, or all orders of one payment.
- [`GET /api/orders`](https://docs.drip.tools/api/orders.md#get-api-orders) — a payer's history.
- [`POST /api/orders/status`](https://docs.drip.tools/api/orders.md#post-api-orders-status) — bulk status by order id.
- [`GET /api/orders/load`](https://docs.drip.tools/api/orders.md#get-api-orders-load) — dispense-queue position / ETA.
- [`GET /api/orders/stream`](https://docs.drip.tools/api/orders.md#get-api-orders-stream) — Server-Sent Events when your orders change.

Order status values: `processing` → `completed` (or `failed`); `pending` is reserved and not
produced by the current flow. See
[Order lifecycle](https://docs.drip.tools/reference/lifecycle.md).

## GET /api/order

Two modes — pass **one** of `id` or `from_tx_hash`.

### By payment: `?from_tx_hash=<ref>`

The most common tracking call — one payment can emit several orders (multisend). `ref` is:

| FROM chain | What to pass |
| --- | --- |
| EVM | The payment transaction hash (`0x…`). |
| Solana | The transaction signature (base58). |
| TON | The **`quoteId`** — your wallet does not learn the treasury-side transaction hash. Resolved once the payment is honoured (afterwards the order's `from.txHash`, 64 hex characters, works too). A **refused** payment never resolves here — find its refund in the payer's history, `GET /api/orders?address=<UQ…>&view=batches`. |

**cURL**
```bash
curl 'https://drip.tools/api/order?from_tx_hash=0x<yourFromTx>'
```

**Response**
```json
{
  "data": [
    {
      "status": "completed",
      "id": "abc123…",
      "from": {
        "chainId": "8453", "displayName": "Base", "symbol": "ETH",
        "amount": "0.00051755", "address": "0xpayer",
        "tokenAddress": "0x0000000000000000000000000000000000000000",
        "txHash": "0x<from tx>", "ts": 1784120925,
        "explorer": "https://basescan.org", "family": null, "cluster": null
      },
      "to": {
        "chainId": "421614", "displayName": "Arbitrum Sepolia", "symbol": "ETH",
        "amount": "5.00000000", "address": "0xrecipient",
        "tokenAddress": "0x0000000000000000000000000000000000000000",
        "txHash": "0x<payout tx>", "ts": 1784120990,
        "explorer": "https://sepolia.arbiscan.io", "family": null, "cluster": null
      }
    }
  ],
  "refund": null
}
```

- `data` is one [order object](https://docs.drip.tools/api/orders.md#order-object) per recipient (empty array until the payment is
  indexed — keep polling).
- `refund` is `null` unless some/all of the payment is being returned (a payment that could not
  become an order, or the failed slices of a partial batch) — see [refund object](https://docs.drip.tools/api/orders.md#refund-object).

### Paginated recipients: `&paginate=1`

For large batches, ask for an aggregate **summary** plus a keyset page of recipients instead of
the whole array:

**cURL**
```bash
curl 'https://drip.tools/api/order?from_tx_hash=0x<yourFromTx>&paginate=1&limit=100'
```

| Parameter | Description |
| --- | --- |
| `limit` | Page size, default 100, max 1000. `limit=all` returns the whole remainder in one response; `limit=0` returns the summary only. |
| `cursor` | `next_cursor` from the previous page. |

**Response**
```json
{
  "summary": {
    "recipients": 250,
    "statusCounts": { "completed": 240, "processing": 10, "failed": 0 },
    "fromTotal": "0.12500000",
    "toTotal": "1250.00000000",
    "completedTs": null,
    "lastPayout": null
  },
  "data": [ /* order objects, in recipient order */ ],
  "pagination": { "has_next_page": true, "next_cursor": "184233" },
  "refund": null
}
```

`completedTs` is filled once nothing is left `processing`; `lastPayout` (`{ txHash, ts }` of the
last payout) additionally needs at least one `completed` order — an all-failed batch has
`completedTs` but `lastPayout: null`. Until the payment is indexed, `summary.recipients` is `0`
and `fromTotal` / `toTotal` are `null`.

### By id: `?id=<order_id>`

Returns a single [order object](https://docs.drip.tools/api/orders.md#order-object), or `null` (with status 200) if the order isn't
indexed yet — keep polling. Order ids are **case-sensitive**.

**cURL**
```bash
curl 'https://drip.tools/api/order?id=abc123…'
```

Passing neither parameter → `400 Provide order id or from_tx_hash`.

### Order object

| Field | Meaning |
| --- | --- |
| `status` | `processing` \| `completed` \| `failed` (`pending` is reserved, never produced today). |
| `id` | The order id (use with `/order?id=` and `/orders/status`). |
| `from` | The payment side: `chainId`, `displayName`, `symbol`, `amount` (human), `address` (payer), `tokenAddress`, `txHash`, `ts`, plus `explorer` / `family` / `cluster` for building links. |
| `to` | The payout side: same fields for the recipient; `txHash` is the payout tx (`null` until dispensed), `ts` the completion time. |

Identity forms on the `from` side follow the chain: an EVM payer is `0x…` and `txHash` is the
transaction hash; a Solana payer is base58 and `txHash` the transaction signature
(`family: "solana"`, `cluster: "mainnet-beta"`); a TON payer is the `UQ…` address and `txHash`
the treasury-side transaction hash (64 hex characters, `family: "ton"`).

### Refund object

| Field | Meaning |
| --- | --- |
| `status` | `refund_pending` (recorded, not yet returned) \| `refunded` (return tx sent). |
| `reason` | `unknown_quote` \| `unattributed` \| `ambiguous_memo` \| `expired` \| `bad_signature` \| `failed_order` — see [Refund reasons](https://docs.drip.tools/reference/errors.md#refund-reasons). |
| `from` | The returned amount + payer + original payment tx. |
| `refundTxHash` | The return transaction, or `null`. |
| `refundedAt` | Epoch seconds, or `null`. |

## GET /api/orders

A payer's order history, keyset-paginated.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `address` | string | yes | The payer identity, or up to 10 identities comma-separated (`0x…`, Solana base58, TON address). EVM addresses match case-insensitively; Solana and TON identities must be exact (`UQ…` form for TON). |
| `view` | string | no | `batches` = one row per payment (multisend collapsed). Otherwise one row per order. |
| `limit` | number | no | 1–100, default 20. |
| `cursor` | string | no | `next_cursor` from a prior response — the next (older) page. |
| `since_cursor` | string | no | Poll for rows newer than this cursor. |

`view=batches` also accepts filters (resend them with every page): `status`
(`processing`\|`partial`\|`completed`\|`failed`\|`refund`), `to_chain_id`, and `q` (prefix search
over order ids / FROM tx hashes, at least 2 characters).

**Response**

```json
{
  "data": [ /* order or batch rows */ ],
  "pagination": {
    "has_next_page": true,
    "next_cursor": "eyJ0cyI6…",
    "latest_item_cursor": "eyJ0cyI6…"
  }
}
```

A **batch row** (`view=batches`) aggregates a payment:

**Response**
```json
{
  "status": "partial",
  "statusCounts": { "completed": 3, "processing": 0, "failed": 1 },
  "refund": { "status": "refund_pending", "reason": "failed_order" },
  "recipients": 4,
  "id": "firstOrderId",
  "from": { "chainId": "8453", "displayName": "Base", "symbol": "ETH", "amount": "0.16", "address": "0x<payer>", "txHash": "0x…", "ts": 1784120925 },
  "to":   { "chainId": "421614", "displayName": "Arbitrum Sepolia", "symbol": "ETH", "amount": "16.0", "address": null, "txHash": null, "ts": null }
}
```

Aggregate `status` is `processing` \| `completed` \| `failed` \| `partial` (some paid, some failed).
Amounts are decimal strings whose trailing zeros vary (a batch row's `from.amount` is padded to
18 places, other amounts to 8) — parse them as decimals, never compare them as strings.

Payments that could **not** become an order (unknown or missing quote, expired) appear in the
unfiltered batches view as rows with `status: "refund_pending"` / `"refunded"`, `isRefund: true`,
`reason`, `recipients: 0`, `id: null` (no order id — look the payment up by its FROM tx hash) and
`to: null`; `status=refund` lists only those.

**Errors:** `400 Incorrect address`; `400 status must be one of: processing, partial, completed, failed, refund`.

## POST /api/orders/status

Lightweight bulk status for order ids you already hold.

**cURL**
```bash
curl -X POST 'https://drip.tools/api/orders/status' \
  -H 'Content-Type: application/json' \
  -d '{ "ids": ["orderId1", "orderId2"] }'
```

**Response**
```json
{
  "data": [
    { "status": "completed", "id": "orderId1", "to": { "txHash": "0x<payout>", "ts": 1784120990 } }
  ]
}
```

Missing ids are simply omitted. Empty `ids: []` → `{ "data": [] }`. Non-array `ids` →
`400 ids must be an array and is required`. More than 500 ids → `400 too many ids (max 500)`.

## GET /api/orders/load

Dispense-queue depth and drain rate, for estimating how long a payout will take.

**Query parameters**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `ref` | string | no | An `order_id` or a FROM tx hash (or the TON treasury-side hash) — adds *your* position in line. |

**Response**

```json
{
  "updatedAt": 1784120100,
  "ratePerSec": 25,
  "minEtaSec": 10,
  "chains": {
    "421614": { "depth": 42, "rate": 28.5, "measured": true, "stalled": false, "minEtaSec": null }
  },
  "ref": {
    "chainId": "421614",
    "depth": 17,
    "statusCounts": { "processing": 2, "completed": 15, "failed": 0 }
  }
}
```

- `chains[toChainId].depth` — recipients still awaiting dispense on that TO chain; `rate` — measured
  drain rate (recipients/sec). Rough ETA ≈ `max(minEtaSec, depth / rate)`. An idle chain is
  simply absent from `chains`.
- `ref.depth` — recipients ahead of (and including) your batch on its TO chain; only shrinks as the
  queue reaches you. `ref` is `null` if the ref isn't indexed yet — fall back to the chain-wide depth.

## GET /api/orders/stream

A [Server-Sent Events](https://developer.mozilla.org/docs/Web/API/Server-sent_events) feed that
fires whenever an order or refund belonging to the given payer identities changes. It is a
**notification only** — re-fetch with `GET /api/order` or `GET /api/orders` when it fires.

**cURL**
```bash
curl -N 'https://drip.tools/api/orders/stream?address=0xpayer'
```

**Event stream**
```txt
: connected

event: orders-changed
data: {"txs":["0x<from tx>"]}

: ping
```

- `address` — same as [`GET /api/orders`](https://docs.drip.tools/api/orders.md#get-api-orders): one or more identities, comma-separated.
- `orders-changed` — `data.txs` lists the FROM tx hashes whose orders changed. Re-fetch those.
- A `: ping` comment every 25 s keeps the connection alive. The stream is not rate-limited; a
  malformed `address` gets `400 Incorrect address`, and if it answers `503`, fall back to polling.
