Overview
TheRundown provides a V2 WebSocket endpoint for streaming real-time data without polling:
WebSocket connections do not count against your REST API rate limit — but each plan has a hard cap on concurrent connections (and on subscriptions per connection on the multiplexed endpoint). Opening a connection beyond the cap is rejected at upgrade time with a
429 (WebSocket connection limit reached).
WebSocket access requires a real-time API tier (Ultra and above). The
plays, stats, and live channels additionally require the live game state entitlement, which is also included from Ultra up — see Rate Limits.Connection
Connect using a standard WebSocket client. Authentication is via thekey query parameter:
Markets WebSocket
GET /api/v2/ws/markets
Streams V2 market price changes in real time. Every time a sportsbook updates a price on any tracked market, you receive a message.
Filter Parameters
All filters are optional. If none are specified, you receive all messages across all sports and markets.Example Connection URLs
Message Format
Each message is a JSON object with two top-level fields:meta (message metadata) and data (the update payload). Unlike the REST API’s nested structure, WebSocket messages deliver one price update per message in a flat format — each message represents a single price change for one participant, one market, and one sportsbook.
Example: Market Price Update
affiliate_id: 26), so it carries liquidity_usd. A price from a traditional sportsbook affiliate omits the field entirely instead of sending null or 0.
Meta Fields
Data Fields
liquidity_usd rides along on whatever price update was already being sent — a liquidity-only change never produces its own message, so you won’t see a delta for it in isolation. It appears the same way on snapshot frames ("snapshot": true) as it does on live deltas, since snapshots reflect the same current-price data as the REST endpoints. See What is liquidity_usd on Kalshi and Polymarket prices? for how each exchange computes it and how fresh it is.Multiplexed WebSocket Channels
The multiplexed endpoint at/api/v2/ws carries multiple logical channels over a single connection. Instead of query-parameter filters, you send JSON subscribe messages after connecting:
The
plays, stats, and live channels require the live game state entitlement (Ultra plan or higher) — the same entitlement as the REST plays endpoint. The futures channel requires the futures entitlement (Ultra plan or higher for API keys; Pro or higher for web sessions), matching the REST futures endpoints. The markets and scores channels are available to any WebSocket-entitled key.Subscribing
Send a subscribe action with a client-chosenid, the channel, and optional filters:
Filters supported in
params: sport_ids, event_ids (plus market_ids, affiliate_ids, and main_line on the markets and futures channels). The stats channel does not support stats_ids, team, or player filters; market_ids and affiliate_ids do not apply to it.
"main_line": true filters the live stream (and, combined with "snapshot": true, the snapshot) to main-line rows only: every price move of the current main and every close of the main. A re-designation onto a line whose price also moved arrives immediately; a designation-only change (price unchanged) surfaces on that line’s next price update — pair with a periodic REST refresh (main_line=true) for the authoritative current main. Alternate-line updates are not delivered and not metered — for a per-book best-line tracker this typically cuts delivered volume by 60–75% on spread/total markets.
Snapshots
Add"snapshot": true to params to receive the current state before live deltas begin. The server sends the subscribed ack, one or more snapshot frames, then snapshot_complete; deltas start after that. Each snapshot covers your subscription scope only — markets snapshots use the same filters and market defaults as the REST events endpoints (each frame carries scope.requested_market_ids / scope.returned_market_ids), live snapshots include the current score/status and live game state, and plays snapshots return up to 500 current plays with a cursor holding the oldest/newest play sequence. futures snapshots replay the REST futures endpoints — one frame per sport with the full board including settlement state. Snapshot requests need a bounded scope: event_ids, or sport_ids plus date (play snapshots require event_ids; futures snapshots require positive sport_ids with no date, and event_ids scoping there requires exactly one sport). An existing subscription can request a fresh snapshot at any time without reconnecting:
Play messages
Each play arrives wrapped in a delta envelope tagged with your subscriptionid:
{"action": "unsubscribe", "id": "p1"}.
Game stats messages
Subscribe on the multiplexed endpoint, not the dedicated/api/v2/ws/markets feed, and scope the subscription by sport, event, or both:
Each changed row across
team_stats[].stats and player_stats[].stats consumes one data point in the stats category, including when the frame arrives through the live channel. A valid game_stats frame with no nested rows — either a completion marker or an invalidation fallback — consumes one stats data point. If one inbound frame matches overlapping stats and live subscriptions on the same connection, it is delivered to both subscriptions but billed once on that connection.
The outer sub_sequence can help detect a gap while the subscription is live, but neither it nor delta_last_id can replay game-stat changes. delta_last_id is the markets-delta cursor even when it appears on a stats frame. Recovery is always a REST refetch followed by continued streaming.
Error codes
Errors are returned as{"type": "error", "id": "...", "code": "...", "message": "..."}:
Heartbeat
The WebSocket endpoint sends a heartbeat message every 15 seconds to keep the connection alive:Message Queue
On the multiplexed endpoint, every non-market subscription has its own 1024-message outbound queue. Themarkets channel is sized separately for its higher message volume. If a subscription or fan-out queue cannot accept a live frame, the server closes the connection rather than let it continue with a silent gap. When possible, it first sends:
buffer_overflow:reconnect_and_catchup. Reconnect, re-send every subscription, and restore current state before applying new deltas. For stats, fetch both event game-stat REST resources and merge newly buffered deltas because snapshots and replay are unsupported.
Keep handlers fast and offload heavy work asynchronously. Narrow each subscription with the supported sport, event, market, and affiliate filters to reduce queue pressure.
Client Examples
If WebSocket is not an option for your architecture, use the REST delta endpoints to poll for changes efficiently. For the full list of market types you can filter on, see Market IDs.
Best Practices
Filter to reduce volume
Filter to reduce volume
The unfiltered market feed can be very high volume. Always apply
sport_ids, market_ids, or event_ids filters to receive only the data you need. On the multiplexed endpoint, queue overflow closes the connection and requires a reconnect plus state recovery.Implement automatic reconnection
Implement automatic reconnection
WebSocket connections can drop due to network issues, server deployments, idle timeouts, or multiplexed queue overflow. Always implement reconnection logic with exponential backoff (e.g., 1s, 2s, 4s, 8s, max 30s). After
buffer_overflow:reconnect_and_catchup, restore current state from REST before applying new deltas.Use heartbeats for health monitoring
Use heartbeats for health monitoring
If you have not received any message (including heartbeats) for 30+ seconds, assume the connection is dead and reconnect. Do not wait for the WebSocket
close event, as it may not fire reliably in all network conditions.Process messages asynchronously
Process messages asynchronously
Keep your message handler fast. Parse the JSON and push work to a queue or separate processing thread. On the multiplexed endpoint, a slow handler can fill a subscription’s outbound queue, causing the server to close the connection rather than continue after a silent gap.