(market, channel) pair on a connection. You create them
with SUBSCRIBE, name the market with a MarketStream, and the server hands
back a subscriptionId that keys every frame that follows.
MarketStream
How you reference a market on the wire. It’s a discriminated union on venue.
You build it from venue-native identifiers you already have, or that you get
from the Markets API or the venue’s
List Markets.
How many entries a market takes
Kalshi works differently: one ticker, one book, one subscribable side.
Where the other venues give you an entry per outcome and you pick either or
both, a Kalshi ticker addresses a single YES-priced orderbook and there is no
second entry to subscribe to.
side accepts only "YES"; a subscribe naming
"no" is refused with UNKNOWN_MARKET rather
than returning a NO feed. You read the NO side by deriving it locally. See
below.side: "YES" and an omitted side name the same subscription. Sending both
forms on one connection does not create two: the second returns the existing
subscriptionId, under the idempotency rule described in
SUBSCRIBE.Deriving the NO side of a Kalshi market
The book we publish is YES-priced. The NO side is that same book read from the other end, which you compute locally. Two transforms, and you need both: applying1 - price without swapping the sides produces a plausible book that
is wrong:
- Price becomes
1 - price. - Sides swap. Our
asksare your NO bids; ourbidsare your NO asks. - Size carries across unchanged. One contract is one contract read from
either end: buying NO at
0.47and selling YES at0.53move the same single contract, so only the price coordinate and the bid/ask label flip.sizeis in shares, not notional, so it never scales by price. - Re-sort afterwards. Our
bidsare ordered best first (descendingprice) and ourasksbest first (ascendingprice), and1 - pricereverses both orderings.
SNAPSHOT for { "venue": "KALSHI", "ticker": "KXNBA-25-LAL" }:
Three checks to assert against your own implementation:
- YES best ask
0.53+ NO best bid0.47=1.00 - YES best bid
0.52+ NO best ask0.48=1.00 - The spread is preserved:
0.01in both views.
DELTA frames derive the same way, side included:
size: 0 still deletes the level.
Channels
A subscription is one
(market, channel) pair. Subscribing to one market on
both channels counts as two subscriptions against the
per-connection cap.
SUBSCRIBE
For each market in the batch you get back a
SUBSCRIBED ack or an ERROR
(for example, UNKNOWN_MARKET), so a market the server cannot resolve never
sinks the rest of the batch. SUBSCRIBED is sent before the upstream venue feed
has confirmed the market. If the feed then refuses it or does not confirm it in
time, or a book’s first snapshot never arrives, a later ERROR carrying the
subscriptionId ends the subscription, so one market can get a SUBSCRIBED
followed by an ERROR. See ERROR. A frame that fails
validation is different: the whole frame is rejected before any market is read,
so a malformed entry takes its batch with it.
subscriptionId to whatever local state you keep for
that market. Every subsequent frame for this subscription is keyed by that id,
and it’s also the handle you pass to UNSUBSCRIBE.
snapshotPending: true tells you a SNAPSHOT is on its way (it is always
false on the trade channel, since the tape has no snapshot).
Subscribing is idempotent per (connection, market, channel): a repeat returns
the existing subscriptionId rather than creating a second one.
UNSUBSCRIBE
subscriptionId(s) from prior SUBSCRIBED acks. Each id
this connection still holds gets an UNSUBSCRIBED reply, carrying
subscriptionId, market, and channel. An id the connection does not hold,
because it is unknown or the subscription was already removed (by an earlier
UNSUBSCRIBE, an ERROR that ended it, or a
CLOSED status), is ignored with no reply. A frame
that names only such ids gets no reply at all.
LIST
SUBSCRIPTIONS frame: one atomic snapshot of every active
subscription on this connection.