Skip to main content
A subscription is one (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: applying 1 - price without swapping the sides produces a plausible book that is wrong:
  • Price becomes 1 - price.
  • Sides swap. Our asks are your NO bids; our bids are your NO asks.
  • Size carries across unchanged. One contract is one contract read from either end: buying NO at 0.47 and selling YES at 0.53 move the same single contract, so only the price coordinate and the bid/ask label flip. size is in shares, not notional, so it never scales by price.
  • Re-sort afterwards. Our bids are ordered best first (descending price) and our asks best first (ascending price), and 1 - price reverses both orderings.
Given this SNAPSHOT for { "venue": "KALSHI", "ticker": "KXNBA-25-LAL" }:
the NO view is: Three checks to assert against your own implementation:
  • YES best ask 0.53 + NO best bid 0.47 = 1.00
  • YES best bid 0.52 + NO best ask 0.48 = 1.00
  • The spread is preserved: 0.01 in both views.
DELTA frames derive the same way, side included:
The first is what we send; the second is that same change in the NO view. 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.
Cache the mapping from 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

Unsubscribe by the 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

Returns a SUBSCRIPTIONS frame: one atomic snapshot of every active subscription on this connection.