Kalshi Market Data Endpoints and Response Structure

Developers get direct access to prices, orderbooks, and trades without authentication.

Senior API Engineer · · 10 min read
Cover illustration for “Kalshi Market Data Endpoints and Response Structure”
Kalshi API Integration · October 10, 2026 · 10 min read · 2,248 words

Kalshi's public market data API gives developers direct access to prediction market state, meaning prices, orderbook depth, and completed trades, without requiring an API key. Three endpoints form the core of this reference: GET /markets (along with GET /markets/{ticker}), GET /markets/{ticker}/orderbook, and GET /markets/trades. A developer who knows what each field in these three responses actually represents can build a parsing layer that survives contact with real market conditions, rather than one that breaks the first time a field goes empty or a status flips.

How Kalshi organizes markets: series, events, and market tickers

Understanding the three-tier structure underneath a Kalshi market response is what makes each field in it make sense: series, event, and market ticker. Kalshi groups tradable contracts into a series, which names a recurring contract family; an event, which groups related markets within that series; and a market ticker, which identifies one specific tradable binary proposition. That series contains dated events, one for each day in question, and each event contains one or more markets, such as a threshold temperature contract that resolves yes or no. A market response carries both its own ticker and the event_ticker of its parent, so you can reconstruct the full hierarchy from market-level data alone, without a separate lookup. Storing the series, event, and market tickers alongside the human-readable title matters for a practical reason: a join built only on title text can silently merge two different expiration windows when a contract rolls forward or a new threshold gets added to the series.

The market response object's fields

A market response object groups into five functional categories: identification, price, volume and open interest, status, and settlement, plus a set of fields that have been deprecated. Identification starts with ticker, the stable unique identifier that should anchor any lookup logic, since title is a human-readable resolution question meant for display and not for keying records. event_ticker links the market back to its parent event, which is what makes the series and event hierarchy reconstructable from market data alone, as described above.

Price fields in the current API surface are denominated in dollars and represented as fixed-point strings. yes_bid_dollars and yes_ask_dollars give the best resting bid and ask on the YES side; no_bid_dollars and no_ask_dollars give the same for the NO side. last_price records the most recent traded price, which works as a useful mid-market reference when the bid-ask spread is wide [0]. A normalization layer built on top of these fields should never manufacture liquidity on the opposite side just because the complementary price can be derived arithmetically.

Volume and open interest fields describe how much a market has traded and how much remains open. volume_fp gives the lifetime contract count as a fixed-point string, while volume_24h narrows that to the trailing 24-hour window, which is useful for screening markets by liquidity before committing size to them. open_interest reports the total unsettled contracts outstanding.

Status and settlement fields govern whether a market can still be traded and how it resolves. status takes the value open, closed, or determined, and governs whether new orders can be placed. result stays empty while a market is open and takes the value yes, no, or a scalar once the market has been determined. expiration_time gives an ISO 8601 timestamp marking when the market closes to new orders. settlement_bounds_type takes the value default or floor, and markets of type floor carry an additional field, settlement_floor_dollars, the lowest value the YES side can settle at. This field touches more of the API surface than market endpoints alone: it appears on GET /markets, GET /markets/{ticker}, GET /historical/markets, GET /historical/markets/{ticker}, GET /events, GET /events/multivariate, the nested markets under GET /events/{event_ticker}, and POST /multivariate_event_collections/{collection_ticker}. fractional_trading_enabled previously appeared consistently across event and market data, but it has since been deprecated and removed, because all markets now support fractional trading without condition.

Two deprecations change existing code's behavior and deserve direct attention. tick_size was deprecated on January 5, 2026, and removed on May 7, 2026, replaced by price_level_structure and price_ranges[].step for determining a market's valid tick sizes. liquidity and liquidity_dollars are deprecated and now return 0 on GET /markets, GET /markets/{ticker}, GET /events, GET /events/{ticker}, and GET /events/multivariate. Code built against the old schema keeps running, just on numbers that no longer mean what they used to.

The orderbook response object: structure, ordering, and the fixed-point format

The orderbook response wraps all depth-of-market data inside a single top-level key, orderbook_fp, and the ordering of the arrays inside it determines how a caller finds the best bid and ask. yes_dollars holds an array of two-element string arrays, each in the form [price_dollars, count_fp], representing resting YES bids sorted in ascending price order. That means the highest YES bid, the best bid, sits as the last element in the array. no_dollars follows the identical structure and sort order for NO bids, so the highest NO bid is also the last element [0]. Both price and count arrive as strings rather than floats, specifically to eliminate floating-point representation error, and any caller should parse them as exact decimals rather than casting them to native floating-point types.

Reading the book correctly follows directly from that ordering. As with the market object, available size and quoted sides represent specific resting orders, and nothing in this structure should be used to infer liquidity that isn't actually resting on the book.

Kalshi added a batched orderbook endpoint in March 2026, GET /markets/orderbooks?tickers=..., which accepts up to 100 tickers in a single call and cuts down on round-trips for any integration tracking many markets at once. An empty side returns "0.00", and both fields are simply omitted when market stats aren't available. These two fields belong to the Margin exchange's market response, not to the Predictions exchange's orderbook endpoint, so if you build against /markets/{ticker}/orderbook, you shouldn't expect to find them there.

The trades endpoint: response fields, filters, and pagination

GET /markets/trades returns completed transactions between users and, like the market and orderbook endpoints, requires no authentication. Each trade carries its own identity, execution detail, and taker-side classification. trade_id uniquely identifies the execution, and ticker names the market in which it occurred. count_fp gives the number of contracts exchanged as a fixed-point string, for example "10.00". yes_price_dollars and no_price_dollars give the YES-side and NO-side execution prices as fixed-point strings, such as "0.5600" for each. taker_outcome_side records which outcome the taker was buying, yes or no, and taker_book_side records whether the taker was the bid or ask aggressor. The response also still includes taker_side, yes or no, but that field is deprecated: it is a legacy alias for taker_outcome_side and taker_book_side combined, and new integrations should avoid it and confirm the exact semantics of the two replacement fields against the live reference. created_time gives an ISO 8601 timestamp of execution, for example "2023-11-07T05:31:56Z". is_block_trade is a boolean, and block trades are included by default, though they can be isolated or excluded with a query parameter.

The response envelope itself has two required fields: trades, the array of trade objects, and cursor, a string. Ignoring cursor breaks pagination outright, since an empty cursor is the only signal that no further pages remain, and a caller that doesn't track it has no reliable way to know when to stop. On the Margin exchange, GET /trade-api/v2/margin/trades now accepts an optional ticker parameter, and omitting it returns trades across all margin markets, with the same cursor pagination, timestamp filters, and page limits applying as on the Predictions endpoint.

Real-time market data via WebSocket: public and private channels

WebSocket channels carry the same underlying market data as the REST endpoints above, as a stream of incremental events. Which channels are public and which are private determines what a given integration actually needs to build around them. Three channels are public, broadcast to all users without any additional channel-level authentication beyond the authenticated connection itself: ticker, which emits market price and volume updates; trade, which emits completed trade events; and multivariate, which emits updates for multivariate event collections. Five channels are private, scoped to the authenticated account: orderbook_delta, which emits incremental orderbook changes; fill, which confirms executions; market_positions, which tracks position updates; communications, which carries account-level messages; order_group_updates, which tracks changes to order groups; and user_orders, which tracks order status changes. orderbook_delta sits in the private category because its responses include per-user enrichment such as client_order_id alongside the public book data.

As of October 1, 2026, Kalshi Predictions WebSocket messages include a new field, sending_ts_ms, the server-side send timestamp in milliseconds.

A practical split emerges from these two transport layers. REST handles discovery, snapshots, order placement, cancellation, positions, and fill reconciliation, the operations where getting a single read or write accurately correct matters most. WebSocket handles live orderbook deltas, trade events, market-status changes, and fill notifications, the stream of updates where latency matters and a snapshot would be stale before it printed. After a disconnect, or after a sequence gap appears in the delta stream, the only safe recovery is to rebuild from a fresh REST snapshot before processing any further deltas. Applying an incremental update to a base state that's already stale produces an orderbook that looks complete and is wrong.

How Kalshi's API schema has changed

Kalshi's API changes at a pace that turns any fixed assumption, a hardcoded field name, a fixture built from an old response snapshot, a mock that still returns last year's schema, into something silently wrong. The tick_size field documented above is the clearest example: deprecated January 5, 2026, and removed April 2, 2026, with removal completed by May 7. Any integration still reading tick_size after that date doesn't error. It receives nothing, and depending on how the parsing code handles a missing field, that absence can propagate in ways that are hard to trace back to the source.

liquidity and liquidity_dollars follow the same pattern from the other direction: both fields are deprecated and now permanently return 0. On the Margin exchange, market_version arrived on Margin markets on October 8, 2026, and order placement got a corresponding market_version parameter alongside it. The batched orderbook endpoint added in March 2026 isn't a breaking change in the same sense, but an integration still polling individual orderbooks one ticker at a time is leaving efficiency on the table that a one-line change would recover.

None of these changes arrived with a version bump to the endpoint path. A team that stops watching the changelog gets no built-in signal that its response parsing has gone stale, and the specific danger is that a deprecated field returning 0 doesn't throw a parse error. It corrupts downstream logic quietly, in exactly the way liquidity_dollars now does for anyone still reading it as a real number.

Testing Kalshi integrations without hitting live endpoints: stateful simulation vs. static mocks

A static mock that returns a fixed JSON blob can confirm that a parser understands the shape of one response. It cannot confirm that an integration handles the Kalshi API's actual sequential behavior: state mutations across calls, pagination correctness, field deprecations, or drift introduced by a schema update like the ones described above. A handful of specific behaviors fall outside what a stateless mock can ever validate. One is whether a POST to /portfolio/orders placing an order actually causes that ticker to show up in a subsequent call to GET /portfolio/positions. Another is whether cursor-based pagination across GET /markets/trades returns pages that are correctly ordered and don't overlap. Whether a market_lifecycle_v2 WebSocket event correctly moves a market's status from open to determined, with later reads reflecting the final settled result, is a third. Whether code built against liquidity_dollars, now permanently 0, or against tick_size, now absent entirely, behaves correctly once that drift has happened is a fourth, and arguably the one most teams discover only in production.

Stateful simulation closes that gap by carrying the consequences of one call into the next. Fault injection, simulated 429 and 503 responses, latency spikes, covers the rate-limit and resilience paths that a live development environment can't safely trigger without risking an actual account or burning real quota. Running without live credentials also means a CI pipeline can execute the full integration test suite in every environment without provisioning API keys anywhere near it.

If you're building Kalshi integrations, you need a way to test market data parsing and orderbook logic without hitting Kalshi's rate limits or depending on whatever the live market happens to be doing at test time. Rystic builds stateful, locally hosted simulators for APIs including Kalshi, with each simulator maintaining state across calls, shipping with pre-seeded realistic market data, and getting checked against the real API before every release specifically to catch schema drift of the kind described above before it reaches integration code. For a parser working against the market response object, the cost of a small field misinterpretation (floating-point drift in a price complement, a missed status transition, a stale settlement bound) compounds across a trading integration rather than staying isolated to one bad read, and a verified simulator with controllable state lets that parser get exercised against edge cases like settlement floors and status changes without waiting on real market conditions to produce them. The same property that makes a simulator useful for unit tests, deterministic and controllable state running horizontally across containers, makes it applicable to teams building RL environments or AI agents that call Kalshi endpoints as tools, where the simulator serving as a test backend and the simulator serving as an agent evaluation environment are the same piece of infrastructure.

Sources

  1. API Changelog - API Documentation
  2. Quick Start: Market Data - API Documentation
  3. Get Trades - API Documentation

More in Kalshi API Integration