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

# Fills & Executions

> Sequenced execution sync with /executions, and fill history with /fills.

A **fill** is one trade between one of your orders and another participant's order. Every fill on your account appears in two places:

| | `GET /mm/v1/executions` | `GET /mm/v1/fills` |
| - | - | - |
| **Use for** | **Reconciliation: forward sync for your trading system** | History and backfill |
| Order | Oldest first, by `executionSequence` | Newest first, by creation time |
| Position | Sequence checkpoint (`after` / `nextAfter`) | Opaque `cursor` |
| Gaps, repeats, late arrivals | None | A fill can appear behind one you have already read |
| Older fills (see below) | Not included | Included |

> [!NOTE]
>
> **Build fill processing on `/executions`.** The execution sequence is the durable record of new fills on your account. Use `/fills` to browse history or to backfill fills from before sequencing began. Do not build exactly-once processing on `/fills`.

Fills from a placement, whether it matched immediately or later while resting, are on `/executions` by the time D1 responds to the request that caused them. The [placement response](https://docs.d1.exchange/market-makers/orders.md#response) shows the order's state but not its fills, so take fills from the feed.

## `GET /mm/v1/executions`

**after** `string` (default: 0)

Return executions with `executionSequence` greater than this value. `0` (or leaving it out) starts from the beginning. Must be a plain non-negative integer: no sign, leading zeros, decimals or exponent, and no larger than 9007199254740991.

**limit** `integer` (default: 100)

1–100.

No other parameters are accepted.

```bash theme={null}
curl "$D1_API_URL/mm/v1/executions?after=147&limit=100" \
  -H "Authorization: Bearer $D1_MM_API_KEY"
```

```json theme={null}
{
  "executions": [
    {
      "executionSequence": "148",
      "fillId": "<fill-id>",
      "orderId": "<order-id>",
      "instrumentId": "<instrument-id>",
      "role": "MAKER",
      "side": "SELL",
      "price": { "units": "110", "decimals": 0 },
      "quantity": { "units": "2", "decimals": 0 },
      "premium": { "units": "220000000", "decimals": 6 },
      "executionIndex": "0",
      "createdAtMs": "1800000000123"
    }
  ],
  "nextAfter": "148",
  "hasMore": false,
  "limit": 100,
  "serverTimeMs": "1800000000456"
}
```

### Execution fields

| Field | Description |
| - | - |
| `executionSequence` | Your account's execution counter: 1, 2, 3, … across all markets and both sides of the book. |
| `fillId` | Unique fill identifier. Use it as your idempotency key. |
| `orderId` | **Your** order that was filled. |
| `instrumentId` | The market. |
| `role` | `MAKER` or `TAKER`: your role in this fill. |
| `side` | `BUY` or `SELL`: your side. |
| `price` | Execution price, per the market's `premiumUnit`. |
| `quantity` | Quantity filled, in contracts. |
| `premium` | Total premium for the fill, in USDC atoms (`decimals: 6`). |
| `executionIndex` | Position of this fill within the matching event that produced it. |
| `createdAtMs` | When the fill was created. |

Every field except `executionSequence` is exactly the same as the matching `/fills` record. The response never includes your counterparty's account, order or sequence.

## Sync semantics

* **Strictly ascending.** Every execution on a page has `executionSequence > after`, in ascending order.
* **No gaps, no repeats, no late arrivals.** D1 assigns sequence numbers one after another, in the same transaction that creates the fill. A new fill always gets a higher number than every fill you can already see, so a lower number never shows up after you have read a higher one.
* **`nextAfter`** is the checkpoint to send as `after` next time: the last sequence on the page, or your `after` unchanged if the page is empty. It never goes backwards.
* **`hasMore: true`** means more executions are ready now, so request again straight away. **`hasMore: false`** means you are caught up, so poll again later.
* **One stream per account.** Sequences are specific to your account. Both of your active API keys read the same stream.
* **Reads are safe to retry.** Reading the feed changes nothing. Repeating a request with the same `after` returns the same executions, plus any new ones if the earlier page was not full.

## Checkpointing and reconnects

Your checkpoint is the last `executionSequence` you have **fully processed and stored durably**. It is the only state you need to resume.

```text theme={null}
durable checkpoint = 147

GET /mm/v1/executions?after=147&limit=100
  ← 148, 149, 150          nextAfter = "150", hasMore = false

process 148, 149, 150 in order
        │  all succeeded
        ▼
persist checkpoint = 150
        │
        ▼
next request (or after a restart):  GET /mm/v1/executions?after=150
```

Rules:

1. **Process in order.** Apply executions in ascending `executionSequence`.
2. **Persist `nextAfter` only after the whole page has been processed.** If processing fails partway through, do not advance the checkpoint. Retry from the last durable value.
3. **Be idempotent on `fillId`.** If your process crashes after applying executions but before saving the checkpoint, you will receive those executions again on restart. Ignore any `fillId` you have already applied. A good approach is to store the checkpoint in the same transaction as the fills it covers.
4. **On errors, keep the checkpoint.** Never skip ahead.
   * `429`: wait the `Retry-After` seconds, then retry the same `after`.
   * `500`: back off and retry the same `after`.
   * `401` / `403`: stop and alert. Retrying will not fix a revoked key, a suspended account or a missing scope. See [Errors](https://docs.d1.exchange/market-makers/errors.md#how-to-handle-errors).

> [!WARNING]
>
> Never set the checkpoint from `serverTimeMs`, `createdAtMs`, or the highest sequence you have *seen*. Use only the `nextAfter` of a page you have finished processing.

### Reference consumer loop

```ts theme={null}
type Exact = { units: string; decimals: number };
type Execution = {
  executionSequence: string; fillId: string; orderId: string; instrumentId: string;
  role: "MAKER" | "TAKER"; side: "BUY" | "SELL";
  price: Exact; quantity: Exact; premium: Exact;
  executionIndex: string; createdAtMs: string;
};
type ExecutionPage = { executions: Execution[]; nextAfter: string; hasMore: boolean };

const BASE = process.env.D1_API_URL!;
const KEY = process.env.D1_MM_API_KEY!;
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
const jitter = (ms: number) => ms + Math.floor(Math.random() * 250);

// Your storage. applyPageAtomically should record every fill (skipping fillIds
// you already have) and save the new checkpoint in one transaction.
declare function loadCheckpoint(): Promise<string>; // "0" on first run
declare function applyPageAtomically(executions: Execution[], nextAfter: string): Promise<void>;

async function syncExecutions(): Promise<never> {
  let after = await loadCheckpoint();
  let failures = 0;
  for (;;) {
    let res: Response;
    try {
      res = await fetch(`${BASE}/mm/v1/executions?after=${after}&limit=100`, {
        headers: { Authorization: `Bearer ${KEY}` },
      });
    } catch {
      // Network error: reads are safe to retry from the same checkpoint.
      await sleep(jitter(Math.min(30_000, 500 * 2 ** failures++)));
      continue;
    }

    if (res.status === 429) {
      await sleep(jitter(Number(res.headers.get("Retry-After") ?? "1") * 1000));
      continue;
    }
    if (res.status >= 500) {
      // Back off and retry from the same checkpoint.
      await sleep(jitter(Math.min(30_000, 500 * 2 ** failures++)));
      continue;
    }
    if (!res.ok) {
      // 4xx: a client or credential problem. Alert, don't spin.
      throw new Error(`executions: ${res.status} ${(await res.json()).error}`);
    }
    failures = 0;

    const page = (await res.json()) as ExecutionPage;
    if (page.executions.length > 0) {
      await applyPageAtomically(page.executions, page.nextAfter); // throws => checkpoint not advanced
      after = page.nextAfter;
    }
    if (!page.hasMore) await sleep(1_000); // caught up; choose a poll interval within your rate limit
  }
}
```

## Older fills without a sequence

Fills created before D1 started numbering executions have no `executionSequence` and will never get one. `/executions` starts at your first numbered fill, so it is complete only from that point on.

If your account traded before then, load those earlier fills once from `/fills`, then use `/executions` for everything after.

## Fills (history and backfill)

`GET /mm/v1/fills`: fill history, newest first.

**limit** `integer` (default: 50)

1–100.

**cursor** `string`

`nextCursor` from the previous page. See [Pagination](https://docs.d1.exchange/market-makers/market-data.md#pagination).

```bash theme={null}
curl "$D1_API_URL/mm/v1/fills?limit=100" \
  -H "Authorization: Bearer $D1_MM_API_KEY"
```

Returns `{ fills, nextCursor, limit, serverTimeMs }`. Each fill has the same fields as an execution, except `executionSequence`: `fillId`, `orderId`, `instrumentId`, `role`, `side`, `price`, `quantity`, `premium`, `executionIndex` and `createdAtMs`.

Fills are sorted by creation time, which is not the order in which they were committed. A fill can therefore appear behind one you have already paged past. That is fine for browsing history, but it is why live sync should use `/executions`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.