> ## 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.

# Building a Reliable MM Client

> Practical rules for market-making bots: retries, timeouts, restarts and reconciliation.

A market-making client has to stay correct through timeouts, restarts and lost connections. These rules cover the cases that matter.

## Rules

1. **Persist every `clientOrderId` before you send it.** Store the ID with the order terms first. A placement you cannot match back to its ID after a crash is a placement you cannot recover.
2. **Retry placements with the same `clientOrderId` and the same terms.** It is always safe. D1 creates at most one order per ID and returns the existing order on a retry. See [clientOrderId and idempotency](https://docs.d1.exchange/market-makers/orders.md#clientorderid-and-idempotency).
3. **Never create a second intent while the first is unknown.** After a timeout or `5xx`, do not re-send the same order under a new ID. Mark the old ID as "possibly live" until you have seen its result.
4. **A timeout is not a failure.** The order may already exist, or may still be created after you stopped waiting. Not finding it straight away does not prove it will never exist.
5. **Persist your execution checkpoint.** Store `nextAfter` only after you have applied the whole page, ideally in the same transaction as the fills. Make fill handling idempotent on `fillId`. See [Checkpointing](https://docs.d1.exchange/market-makers/executions.md#checkpointing-and-reconnects).
6. **Reconcile after every reconnect.** Catch up on `/executions` until `hasMore` is `false`, then compare `GET /mm/v1/orders?state=resting` with what you think is resting.
7. **Respect `429` and `Retry-After`.** Wait at least `Retry-After` seconds, add jitter, and retry the same request. See [Rate Limits](https://docs.d1.exchange/market-makers/rate-limits.md).
8. **Back off with jitter on errors.** On `5xx` and network errors, use bounded exponential backoff with random jitter, so that many workers do not retry in lockstep.
9. **Cancel risk when placement becomes unavailable.** On `TRADING_DISABLED`, `DEALER_SUSPENDED`, a stale price feed or an unexpected state, stop placing and call `POST /mm/v1/orders/cancel-all`. Cancellation keeps working when placement does not.
10. **Handle partial fills.** An order can fill in several pieces, both when placed and later while resting. Book each execution as it arrives; do not wait for `FILLED`.
11. **Reconcile on a schedule.** Every few minutes, check open orders, positions and balances against your own state. If they disagree, trust D1, find the missing executions, and alert.
12. **Stop on credential errors.** `401` and `403` (other than `TRADING_DISABLED`) will not fix themselves. Stop, alert a person, and do not retry in a loop.

## Startup and reconnect

```text theme={null}
GET /mm/v1/status                       key valid? trading.available? limits?
GET /mm/v1/executions?after=<saved>     repeat until hasMore = false; apply fills
GET /mm/v1/orders?state=resting         rebuild your view of resting orders
for each "possibly live" clientOrderId:
    resend the identical POST /mm/v1/orders  → 200/201 gives the order
GET /mm/v1/positions, /balances         check against your books
start quoting
```

If you want to start from a clean book, call `POST /mm/v1/orders/cancel-all` after catching up on executions and before you start quoting.

## Placing with an unknown outcome

```text theme={null}
id = new clientOrderId; persist(id, terms, state = "sending")

POST /mm/v1/orders {id, terms}
  201 / 200            → persist(id, order); done
  400 / 404 / 409 / 422 → refused; nothing was created; persist(id, "refused")
  429                  → wait Retry-After + jitter; resend the same request
  5xx / timeout / network error
                       → persist(id, "possibly live")
                         resend the same request with backoff and jitter
                         meanwhile, reconcile with /orders and /executions
```

A refusal answers only that attempt. If an earlier attempt with the same ID is still in flight, it can still succeed, so keep reconciling an ID that ever had an unknown result.

## Failure cheat sheet

| What happened | What to do |
| - | - |
| Placement timed out | Same ID, same terms, retry. Reconcile. Never a new ID for the same intent. |
| `429 RATE_LIMITED` | Wait `Retry-After` seconds plus jitter, then retry the same request. |
| `500 INTERNAL` on a placement | Treat as unknown. Retry with the same ID. |
| `500 INTERNAL` on a read | Back off and retry. Keep your checkpoint. |
| `403 TRADING_DISABLED` | Stop placing, cancel if needed, poll `/status`. |
| `403 DEALER_SUSPENDED` | Stop placing, cancel, contact D1. |
| `401` | Stop. Check the key or switch to your other key. |
| `409 IDEMPOTENCY_CONFLICT` | A bug in how you generate or reuse IDs. Do not retry. |
| Process crashed | On restart, run the startup sequence above before quoting. |


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