Skip to main content
GET /v1/stream pushes events as they are recorded, as Server-Sent Events over one keyed HTTP connection. Every data event has an id; reconnect with the last id you saw and the stream replays what you missed, in order, then continues live. Authenticate as for any request. A browser EventSource cannot send the key header, so connect from a server.

Topics

topics is required: a comma-separated list of what to receive.

Filters

relations_truncated is sent to every relations stream whatever the filters. An unknown topic, a malformed market or a cursor that is not an id is 400 invalid_parameter.

The connection

The response opens with a retry line and a ready event:
Streams are not permanent. Each one ends itself after 45 to 55 minutes (the exact moment is in ready, jittered so a deploy’s reconnects do not all return at once), and sooner when the server restarts or you fall a full buffer behind. Each ending is announced:
Reconnect after the retry interval, 3 seconds, backing off on errors.

Ids and resuming

Every data event carries an id. An id is a decimal outbox position, or position-index for one of the many events a single graph run expands into:
Ids are opaque and ordered; compare them only by keeping the latest. To resume, send the last id you processed as the Last-Event-ID header (what every SSE client library does on reconnect) or as cursor, with the same filters: events after it are replayed in order, then the stream continues live with nothing missed or repeated. Replay is served from the outbox, so a burst of 40,000 replayed quotes takes a few seconds. A cursor from the last hour always resumes. An older cursor, or one from the future, gets a reset event after ready, and the stream continues from now:
On reset, rebuild your state from the REST endpoints (/v1/opportunities, /v1/markets/{venue}/{id}/quotes, and so on), then carry on with the ids that follow.

Event shapes

Every event’s data is one JSON object. Prices and money are decimal strings in USD; times are RFC 3339 in UTC. recorded_at on every data event is when Routeur wrote it, which is the order events are delivered in.

quote

yes_ask_usd and no_ask_usd are each omitted when that side has no ask, as here where nothing is offered on No. observed_at is when the crawl read the venue.

trade

book

The top of the book in Yes terms, as on GET /v1/markets/{venue}/{id}/book: yes_bid_usd/yes_bid_quantity and yes_ask_usd/yes_ask_quantity (each pair omitted when that side is empty), mid_usd, microprice_usd, spread_usd, the USD within 5¢ of each side’s best bid, imbalance, Kalshi’s seq, the venue’s venue_ts, and observed_at, when the change was received. Deeper changes that leave the top alone are not streamed.

relation

One per relation the run proved for the first time, cross-venue ones first, stated from left. proof_path is where to open the proof trail. A run that finds more than 10,000 new relations, which happens when a new graph version relates everything anew, sends one relations_truncated event instead, with graph_run, new_relations, limit and a message pointing at /v1/cross-venue and /v1/markets/{venue}/{id}/relations.

lead

The same fields as a lead on GET /v1/opportunities (see Leads), including depth where the run sized it against the books. Every lead of the run is sent, not only new ones, so a lead that persists arrives once an hour; key on the pair and legs to tell a repeat from a change.
The quote, trade, book, rule_change, ready, reset and reconnect samples on this page were captured from the stream. No graph run finished during the capture, so the relation sample is an observed proof as the stream carries it, and the lead sample is the reference example from the API spec.

rule_change

scope is contract, series, event, market or document; rulebook_id names the rulebook for the last four. from_hash and to_hash are rules hashes, rulebook content hashes or document hashes by scope. rules_path is where to read the versions and diffs.

error

Sent when the key’s daily quota runs out while the stream is open; the stream then ends without a reconnect. Reconnect after the quota resets at midnight UTC.

Limits and usage

Opening a stream counts as one request against the per-minute limit and the daily quota, and an open stream counts one more request per minute against the daily quota while it is open, so a stream held all day costs about 1,440 requests. Replayed events are not counted individually. A key over its concurrent streams gets 429 too_many_streams with Retry-After: 60; close a stream first. When no stream capacity is free on the instance reached, or it is shutting down, the answer is 503 stream_unavailable with Retry-After: 5.

A minimal resumable client

Both clients below keep the last id, reconnect on every ending with it, resync on reset, and treat 45 seconds of silence as a dead connection. Neither depends on an SSE library.
Keep the filters identical across reconnects. A cursor is a position in one shared log, not in your filtered view, so resuming with different filters is valid but delivers the new filter’s events from that position, not the old filter’s.
  • Order books: what a book event is the top of.
  • Rules: what a rule_change points at.
  • Leads and Relations: what lead and relation events carry.
  • Rate limits: the per-minute limits and daily quotas that streams count against.
  • Webhooks: to be called when a lead becomes executable, without holding a connection.