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

> ## Agent Instructions
> Prices are US dollars per contract that pays $1: 0.46 means 46 cents. Spread is the Yes ask plus the No ask minus $1. Liquidity grades are deep, tradeable and thin; lists hide thin markets unless include_thin=true. Relations are proven from contract terms, never inferred from wording; a lead marked informational is never executable. Every figure carries a graph_run; read /agents/reading-order first, then /concepts/relations and /concepts/liquidity.

# Order books

> What Routeur records from both venues' order books, how it is kept, and how to read a book now, at a past moment, or over time.

Routeur records the order books of Kalshi and Polymarket: every level change as it arrives on the venue's own feed, a full copy of each book at intervals, and the top of the book for every second in which it changed. Three endpoints read that record: the book now or at a past moment, the top of book over time, and the health of each venue's feed.

## What a book is here

A prediction market has two outcomes, Yes and No, and a contract on either pays \$1 (Kalshi) or 1 USDC (Polymarket) if it wins. A bid to buy Yes at 41¢ is the same order as an offer to sell No at 59¢, and the venues match them that way. So a book is described completely by its **resting bids per side**: the Yes bids and the No bids. A Yes ask at price *p* is a No bid at 1 − *p*, and vice versa. Everything Routeur stores is bids; asks are derived.

* On Kalshi, the feed publishes exactly this: `yes` and `no` bid ladders.
* On Polymarket, each outcome token has its own book, and the exchange mirrors every bid on one token as an ask on the other. Routeur subscribes to both tokens and records each token's **bids** (Yes-token bids as Yes bids, No-token bids as No bids), so every order is stored once. When only one of a market's tokens is known, that token's asks stand in for the other side's bids.

Prices are dollars per contract, exact to six decimals. Quantities are contracts on Kalshi and outcome shares on Polymarket. USD amounts are price × quantity.

## Which markets are recorded how

The collector sorts listed markets into two tiers and re-reads the sorting every 5 minutes:

| Tier   | Which markets                                                                                                                                                                                    | How                                                                                                                                                                                                         |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `full` | Every market graded `tradeable` or `deep` (see [Liquidity](/concepts/liquidity)); every market in a lead of the latest finished graph run; every market of an event someone follows on the site. | Subscribed on the venue feed: a full snapshot at subscription, then every level change; a full snapshot again every 15 minutes (staggered per market), after every sequence gap, and after every reconnect. |
| `rest` | Every other listed market.                                                                                                                                                                       | Polled over REST, round-robin, at a fixed budget of 5 requests a second per venue; 5 levels per side per poll. One pass over the tier is `rest_cycle_seconds` in `GET /v1/books/health`.                    |

A polled book is only as fresh as its last poll. With over 100,000 polled markets per venue, a cycle takes hours; the `polled` issue on a book says so, and `as_of` says when it was read.

## What is kept, and for how long

| Series       | One row per                                                                                             | Kept hot | Archive                                                                                                          |
| ------------ | ------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
| Level events | Resting-bid level change (price, size after the change, delta, `seq`, `venue_ts`, `received_at`)        | 7 days   | Not exported until the data-rights review clears raw redistribution; the day is dropped at the end of retention. |
| Snapshots    | Full book the collector held: on subscribe, on resync, every 15 minutes, or per poll of a `rest` market | 30 days  | Nightly Parquet.                                                                                                 |
| Top of book  | Market and second in which the top changed; the last state in the second wins                           | 30 days  | Nightly Parquet.                                                                                                 |

The export job runs at 02:30 UTC, writes each day past retention as Zstandard-compressed Parquet under `books/{dataset}/venue={venue}/date={YYYY-MM-DD}/`, with a manifest listing every object's rows, bytes and SHA-256, and drops the hot partition only after both venues exported cleanly. BigQuery external tables `books.snapshots` and `books.top` read the objects in place, partitioned by venue and date; a partition filter is required. The archive is not served through the API.

## Three clocks

Three timestamps appear on books, and they are never mixed:

| Field                                   | Clock           | Meaning                                                                                                                                    |
| --------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `venue_ts`                              | The venue's     | The venue's own stamp on the message, where it sends one: Kalshi stamps each delta but not its snapshots; Polymarket stamps every message. |
| `as_of`, `snapshot_at`, `at`, `book_at` | The collector's | When the message was received or the row was formed, in `us-east4`. The top-of-book series truncates `at` to the second.                   |
| `seq`                                   | Kalshi's        | A per-subscription sequence number. It orders Kalshi messages exactly; Polymarket has none, and its messages are ordered by receipt.       |

Feed latency, receipt minus `venue_ts`, is reported per venue as `latency_ms_p50` and `latency_ms_p99` in `GET /v1/books/health`. It includes the venue's own publishing delay and the network path. When the observed p50 was 19 ms on Kalshi and 50 ms on Polymarket, that was the whole delay from the venue's stamp to Routeur's receipt.

## The book now, or at a moment

```bash theme={null}
curl "https://api.routeur.app/v1/markets/kalshi/KXMLBTOTAL-26SEP171235MILPIT-9/book?depth=3" \
  -H "X-API-Key: $ROUTEUR_API_KEY"
```

| Parameter | Default | Meaning                                                            |
| --------- | ------- | ------------------------------------------------------------------ |
| `depth`   | `10`    | Levels per side to return, 1 to 100.                               |
| `at`      | now     | RFC 3339. The book as it stood at that moment, up to 90 days back. |

The book is rebuilt from the latest snapshot taken at or before the moment, plus every level event received since. Without `at`, `source` is `live`; with it, `rebuilt`.

```json theme={null}
{
  "data": {
    "venue": "kalshi",
    "market_id": "KXMLBTOTAL-26SEP171235MILPIT-9",
    "source": "live",
    "tier": "full",
    "as_of": "2026-09-17T12:42:01.052454Z",
    "venue_ts": "2026-09-17T12:42:01.033Z",
    "seq": 659801,
    "snapshot_at": "2026-09-17T12:39:43.014521Z",
    "events_applied": 5,
    "depth": 3,
    "yes": {
      "bids": [
        { "price_usd": "0.54", "quantity": "551", "notional_usd": "297.54" },
        { "price_usd": "0.53", "quantity": "31315.3", "notional_usd": "16597.109" },
        { "price_usd": "0.52", "quantity": "43327.05", "notional_usd": "22530.066" }
      ],
      "asks": [
        { "price_usd": "0.55", "quantity": "38282.18", "notional_usd": "21055.199" },
        { "price_usd": "0.56", "quantity": "34235.11", "notional_usd": "19171.6616" },
        { "price_usd": "0.57", "quantity": "36523.75", "notional_usd": "20818.5375" }
      ]
    },
    "no": {
      "bids": [
        { "price_usd": "0.45", "quantity": "38282.18", "notional_usd": "17226.981" },
        { "price_usd": "0.44", "quantity": "34235.11", "notional_usd": "15063.4484" },
        { "price_usd": "0.43", "quantity": "36523.75", "notional_usd": "15705.2125" }
      ],
      "asks": [
        { "price_usd": "0.46", "quantity": "551", "notional_usd": "253.46" },
        { "price_usd": "0.47", "quantity": "31315.3", "notional_usd": "14718.191" },
        { "price_usd": "0.48", "quantity": "43327.05", "notional_usd": "20796.984" }
      ]
    },
    "top": {
      "yes_bid_usd": "0.54", "yes_bid_quantity": "551",
      "yes_ask_usd": "0.55", "yes_ask_quantity": "38282.18",
      "no_bid_usd": "0.45", "no_ask_usd": "0.46",
      "mid_usd": "0.545", "microprice_usd": "0.540141", "spread_usd": "0.01",
      "yes_depth_1c_usd": "16894.649", "yes_depth_2c_usd": "39424.715", "yes_depth_5c_usd": "56704.1692",
      "no_depth_1c_usd": "32290.4294", "no_depth_2c_usd": "47995.6419", "no_depth_5c_usd": "72750.0188",
      "imbalance": "-0.12395"
    },
    "feed": { "connected": true, "last_message_at": "2026-09-17T12:42:42.242697Z", "stale_seconds": 45 },
    "issues": [],
    "liquidity": "tradeable"
  }
}
```

The Yes asks are the No bids at the complementary price: 38,282 contracts bid for No at 45¢ are 38,282 contracts offered as Yes at 55¢. The liquidity fields of the market follow (trimmed above); see [Liquidity](/concepts/liquidity).

| Field                           | Meaning                                                                                                                                                                            |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `source`                        | `live`: the latest recorded book. `rebuilt`: the book at the `at` asked for.                                                                                                       |
| `tier`                          | `full` or `rest` (polled).                                                                                                                                                         |
| `as_of`                         | When the last change in this book was received. For a polled book, when it was polled.                                                                                             |
| `venue_ts`, `seq`               | The venue's stamp and sequence number on that last change, where it has them.                                                                                                      |
| `snapshot_at`, `events_applied` | The snapshot the book was rebuilt from, and how many level changes were applied on top of it.                                                                                      |
| `yes`, `no`                     | Each outcome's `bids` and `asks`, best first, to `depth` levels. Each level has `price_usd`, `quantity` and `notional_usd`.                                                        |
| `top`                           | The top of book in Yes terms (below).                                                                                                                                              |
| `feed.connected`                | Whether the venue feed is up right now (reported within the last 2 minutes).                                                                                                       |
| `feed.stale_seconds`            | Seconds since this book last changed; `0` for a rebuilt book.                                                                                                                      |
| `issues`                        | `polled`, `stale` (the feed is down and this is the last book recorded) or `truncated` (more than 250,000 events since the last snapshot; the book is as of the last one applied). |

### The top of book

| Field                                                      | Meaning                                                                                                                                                                                                             |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `yes_bid_usd`, `yes_bid_quantity`                          | Best Yes bid and the quantity resting at it. Absent when nothing rests on the Yes side.                                                                                                                             |
| `yes_ask_usd`, `yes_ask_quantity`                          | Best Yes ask, which is 1 − the best No bid, and the quantity at that No bid.                                                                                                                                        |
| `no_bid_usd`, `no_ask_usd`                                 | The same two prices seen from the No side.                                                                                                                                                                          |
| `mid_usd`                                                  | (Yes bid + Yes ask) / 2. Absent unless both sides have a bid.                                                                                                                                                       |
| `microprice_usd`                                           | The bid and ask weighed by the size resting at the *other*: (Yes bid × Yes-ask size + Yes ask × Yes-bid size) / (both sizes). It leans toward the thinner side, where the next trade is likelier to push the price. |
| `spread_usd`                                               | Yes ask − Yes bid.                                                                                                                                                                                                  |
| `yes_depth_1c_usd`, `yes_depth_2c_usd`, `yes_depth_5c_usd` | USD resting on the Yes side within 1, 2 and 5 cents of the best Yes bid. A level exactly that far away counts, so `*_depth_1c_usd` on a 1¢-tick market covers the best two levels.                                  |
| `no_depth_1c_usd`, `no_depth_2c_usd`, `no_depth_5c_usd`    | The same for the No side: money waiting to buy No, which is money waiting to sell Yes.                                                                                                                              |
| `imbalance`                                                | (`yes_depth_5c_usd` − `no_depth_5c_usd`) / (their sum), from −1 to +1. Absent when both are zero.                                                                                                                   |

### At a past moment

```bash theme={null}
curl "https://api.routeur.app/v1/markets/kalshi/KXMLBTOTAL-26SEP171235MILPIT-9/book?depth=1&at=2026-09-17T12:12:41Z" \
  -H "X-API-Key: $ROUTEUR_API_KEY"
```

```json theme={null}
{
  "source": "rebuilt",
  "tier": "full",
  "as_of": "2026-09-17T12:12:41.169311Z",
  "venue_ts": "2026-09-17T12:12:41.15Z",
  "seq": 633062,
  "snapshot_at": "2026-09-17T12:09:37.579685Z",
  "events_applied": 41,
  "feed": { "connected": true, "last_message_at": "2026-09-17T12:42:42.242697Z", "stale_seconds": 0 }
}
```

`at` accepts up to 90 days back, but the rebuild is exact only while the level events are hot (7 days). Between 7 and 30 days back the book is the latest snapshot at or before the moment with no events on top, so `events_applied` is `0` and `as_of` is the snapshot's time; for a `full` market that is at most 15 minutes before the moment asked. Further back than the snapshots reach, the answer is `404 book_not_found`.

## The top of book over time

```bash theme={null}
curl "https://api.routeur.app/v1/markets/kalshi/KXMLBTOTAL-26SEP171235MILPIT-9/book/history?step=15m" \
  -H "X-API-Key: $ROUTEUR_API_KEY"
```

| Parameter | Default              | Meaning                                 |
| --------- | -------------------- | --------------------------------------- |
| `from`    | 24 hours before `to` | Start of the window, RFC 3339.          |
| `to`      | now                  | End of the window.                      |
| `step`    | `1m`                 | `1s`, `10s`, `1m`, `5m`, `15m` or `1h`. |

One point per step in which the top changed, each the last state in its step, oldest first. The series is recorded per second of change and kept hot for 30 days.

```json theme={null}
{
  "data": [
    { "at": "2026-09-17T11:30:00Z", "yes_bid_usd": "0.53", "yes_ask_usd": "0.54", "mid_usd": "0.535", "microprice_usd": "0.538304", "spread_usd": "0.01", "yes_depth_5c_usd": "44361.673", "no_depth_5c_usd": "46974.1121", "imbalance": "-0.028602", "changes": 2 },
    { "at": "2026-09-17T11:45:00Z", "yes_bid_usd": "0.53", "yes_ask_usd": "0.54", "mid_usd": "0.535", "microprice_usd": "0.538776", "spread_usd": "0.01", "yes_depth_5c_usd": "52289.7096", "no_depth_5c_usd": "59491.4296", "imbalance": "-0.064426", "changes": 65 },
    { "at": "2026-09-17T12:00:00Z", "yes_bid_usd": "0.54", "yes_ask_usd": "0.55", "mid_usd": "0.545", "microprice_usd": "0.540224", "spread_usd": "0.01", "yes_depth_5c_usd": "53086.8053", "no_depth_5c_usd": "60350.8078", "imbalance": "-0.064035", "changes": 70 }
  ],
  "from": "2026-09-16T12:42:49.064685826Z",
  "to": "2026-09-17T12:42:49.064685826Z",
  "step": "15m"
}
```

`at` is the start of the step; `changes` counts the seconds within it in which the top changed. A step with no change has no point, so a quiet market produces a short series: carry the previous point forward when you chart it.

<Warning>
  At most 5,000 points per request, and the window may not be longer than 5,000 steps: a day at `1s` needs 18 requests of 5,000 seconds each, and a window longer than that is rejected as `invalid_parameter` on `from`. At `1m` a window of up to 3 days 11 hours fits in one request.
</Warning>

## Feed health

```bash theme={null}
curl "https://api.routeur.app/v1/books/health" -H "X-API-Key: $ROUTEUR_API_KEY"
```

One entry per venue, from the collector's own report, which it rewrites every 15 seconds:

```json theme={null}
{
  "venue": "kalshi",
  "status": "up",
  "connected": true,
  "started_at": "2026-09-17T12:35:15.695408Z",
  "connected_since": "2026-09-17T12:35:17.801665Z",
  "last_message_at": "2026-09-17T12:38:32.635599Z",
  "stale_seconds": 8,
  "messages_per_minute": 225747,
  "events_per_minute": 262625,
  "full_depth_markets": 4352,
  "rest_markets": 124244,
  "reconnects": 0,
  "gaps": 43,
  "resyncs": 43,
  "dropped": 0,
  "rest_errors": 0,
  "latency_ms_p50": 19,
  "latency_ms_p99": 23,
  "updated_at": "2026-09-17T12:38:32.636721Z"
}
```

| `status`   | Meaning                                                                                             |
| ---------- | --------------------------------------------------------------------------------------------------- |
| `up`       | Connected, reporting, and a message within the last 2 minutes.                                      |
| `degraded` | Connected and reporting, but silent for more than 2 minutes, or the collector reports an `issue`.   |
| `down`     | Not connected, or no report for 2 minutes: a silent collector is down however its last report read. |

| Field                                      | Meaning                                                                                                                                                                                                                |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `started_at`                               | When the collector started. Every counter counts from here, so a restart resets them.                                                                                                                                  |
| `connected_since`                          | When the current feed connection was made; resets on reconnect.                                                                                                                                                        |
| `last_message_at`, `stale_seconds`         | The last feed message of any kind, and how long ago.                                                                                                                                                                   |
| `messages_per_minute`, `events_per_minute` | Feed messages received and level changes recorded, over the last minute.                                                                                                                                               |
| `full_depth_markets`, `rest_markets`       | How many markets are in each tier.                                                                                                                                                                                     |
| `rest_cycle_seconds`                       | How long the last full pass over the polled tier took. Absent until a pass completes after a start.                                                                                                                    |
| `reconnects`                               | Feed connections lost and re-established.                                                                                                                                                                              |
| `gaps`                                     | Kalshi sequence gaps detected. Each one triggers a fresh snapshot.                                                                                                                                                     |
| `resyncs`                                  | Fresh snapshots taken after gaps and reconnects, plus books found to differ from the venue's re-sent copy.                                                                                                             |
| `dropped`                                  | Level changes not recorded because the writer was overloaded (a sustained backlog of 200,000 rows). Affected books are snapshotted afresh once the backlog clears, so rebuilds stay correct; the raw record has holes. |
| `rest_errors`                              | Failed polls.                                                                                                                                                                                                          |
| `latency_ms_p50`, `latency_ms_p99`         | Receipt minus `venue_ts` over the last 512 stamped messages.                                                                                                                                                           |
| `issue`                                    | The last connection or write error; cleared on recovery.                                                                                                                                                               |

## Books on the live stream

The `books` topic on [`/v1/stream`](/concepts/live-stream) pushes a `book` event when a market's top of book changes (best prices or the size at them), at most once a second per market. It carries the top in Yes terms with the 5-cent depths and the imbalance, the venue's `seq` and `venue_ts`, `observed_at` (receipt) and `recorded_at`. Deeper changes that leave the top alone are not streamed; read them from the book. Polled markets appear only when a poll shows their top changed, so once a cycle at most.

## Caveats

* **Polled books are old.** A `rest` market is read once per cycle, 5 levels per side. Check `as_of` and the `polled` issue before treating the book as current.
* **Polymarket has no sequence numbers.** A lost message cannot be detected as it happens; it shows up as a correction when the venue re-sends the book, which it does after each trade. Between a lost message and the next re-send, the book held can be wrong at that level.
* **Kalshi snapshots carry no `venue_ts`.** A book rebuilt from a snapshot with no events since has `venue_ts` absent.
* **Depth windows include the boundary level.** `*_depth_1c_usd` on a 1¢-tick market covers the best two levels, not one.
* **Quantities are rounded to six decimals.** Polymarket shares can carry more.
* **Raw events are not exported.** Until the data-rights review clears redistribution, level events are queryable only for 7 days through `at=` rebuilds; the snapshot and top-of-book series are the durable record.

## Related

* [Liquidity](/concepts/liquidity): the `book_*` fields carried by every market, and the grades that decide the `full` tier.
* [Live stream](/concepts/live-stream): the `books` topic.
* [Leads](/concepts/leads): `depth`, which walks both books of a lead with real fees and minimum sizes.
* [Quotes and trades](/concepts/history): the crawl's asks and the public fills, the other two price records.
