wss://therundown.io/api/v2/ws carries multiple logical channels — markets, scores, plays, stats, and live — over one connection. Instead of encoding filters in the connection URL, you send JSON subscribe messages after connecting, and you can add or remove subscriptions at any time without reconnecting.
WebSocket access is enabled on real-time API tiers (Ultra and above) by default. The
plays, stats, and live channels additionally require the live game state entitlement, which is also included from Ultra up — see Rate Limits. Each plan also caps concurrent connections and subscriptions per connection — see the connection limits table.When to Use the Multiplexed Endpoint
Connect
Authenticate with thekey query parameter, the same as the dedicated markets stream:
Subscribe to a Channel
Send asubscribe action with a client-chosen id, the channel, and optional params filters:
The server acknowledges each successful subscription:
Channels and Filters
Handle Incoming Messages
Channel data arrives wrapped in a delta envelope tagged with your subscriptionid. Route messages on the top-level type field, then on id:
Heartbeats arrive in the same format as on the dedicated markets stream —
{"meta": {"type": "heartbeat"}, ...} — and are not tagged with a subscription id. For the full payload field reference per channel, see the WebSocket reference; play-by-play payloads are documented under Play messages, and box-score deltas under Game stats messages.
Request a Snapshot
Deltas only tell you what changed — to start from current state, add"snapshot": true to params when subscribing. Snapshot requests need a bounded scope: event_ids, or sport_ids plus date (plays snapshots require event_ids):
subscribed ack, the server sends one or more snapshot frames followed by snapshot_complete; live deltas begin after that. An active subscription can also request a fresh snapshot at any time without reconnecting:
Unsubscribe
Send anunsubscribe action with the subscription’s id:
id first, then unsubscribe the old one — you may briefly receive duplicate messages while both are active, so deduplicate during the overlap.
Handle Errors
Errors are returned as{"type": "error", "id": "...", "code": "...", "message": "..."}:
buffer_overflow is a connection-level error and can omit id. When possible, the server sends it immediately before closing with reason buffer_overflow:reconnect_and_catchup.
Complete Client Example
A client that subscribes to NBA market prices and scores on one connection, routes messages by subscriptionid, and resubscribes automatically after a reconnect:
Streaming Play-by-Play
On an Ultra plan or higher, add aplays subscription to receive live play-by-play for in-progress games:
meta.type of "play" in the inner payload — description, period, running score, and (as attribution rolls out) the players involved. For the full play payload, see Play messages in the reference.
To load the plays that happened before you subscribed, either add "snapshot": true to the subscription (plays snapshots return up to 500 current plays), or fetch the timeline from the REST endpoint GET /api/v2/events/{eventID}/plays and apply streamed plays on top.
If you want scores, live_game_state, plays, and game stats together for the same games, subscribe to the live channel instead of pairing separate subscriptions — it delivers all in-game updates on one subscription (score/status deltas arrive with meta.type of "score", plays with "play", and stats with "game_stats"), which also conserves your plan’s subscription slots.
Streaming Live Game Stats
For supported live games, thestats channel pushes changed team and player box-score rows inline at play latency — typically within a few seconds of the corresponding play-by-play update (live game data overall trails the on-field action by roughly 15–20 seconds, in line with the typical broadcast delay). Connect to the multiplexed /api/v2/ws endpoint and subscribe by sport, event, or both:
stats_ids, team IDs, player IDs, market_ids, and affiliate_ids cannot filter this channel. Each incoming delta has inner meta.type of "game_stats"; its team_stats[] and player_stats[] groups contain only rows that changed.
Use this bootstrap and recovery flow:
- Subscribe (or resubscribe) to
statsand temporarily buffer its delta frames. - Fetch the current team box from
GET /api/v2/events/{eventID}/statsand the current player box fromGET /api/v2/events/{eventID}/players/stats. - Replace your local baseline with those REST responses, then apply the buffered deltas in order.
- For every later frame, upsert each nested row by its team or player ID plus
stat_id. Keepvalueas a string and do not replace an entire group with one delta. When normalizing the REST baseline, player rows use the group’splayer.idand the nestedstat.id; WebSocket player rows additionally carryplayer_idandstat_id. - If both stat arrays are absent and
completeistrue, mark the cached box score complete; repeated completion markers are idempotent. If both arrays are absent andcompleteis nottrue, the frame is an invalidation fallback, so refetch both REST resources. - If you detect a
sub_sequencegap or reconnect afterbuffer_overflow, refetch both REST resources. Neithersub_sequencenordelta_last_idis a stats replay cursor.
stats billing category. A zero-row completion marker or invalidation fallback costs one stats data point. See Game stats messages for the full envelope, completion and fallback timestamp variants, and field reference.
Best Practices
Resubscribe on every reconnect
Resubscribe on every reconnect
Subscriptions live only as long as the connection. After any disconnect, re-send all subscribe messages as soon as the new connection opens — put the subscribe logic in your
onopen handler so it runs on every connect, as in the example above.Re-sync state after a reconnect
Re-sync state after a reconnect
You may have missed updates while disconnected. For snapshot-capable channels, resubscribe with
"snapshot": true so current state arrives before deltas resume, or fetch current state from the REST API (or the delta endpoints). A buffer_overflow:reconnect_and_catchup close always requires this recovery. For stats, fetch both REST game-stat resources and then merge new deltas; snapshots and replay are unsupported. Snapshot frames are metered like the equivalent REST reads.Filter every subscription
Filter every subscription
An unfiltered subscription can stream more data than you need. Every non-market subscription has its own 1,024-message outbound queue; if a live frame cannot be queued, the connection closes so you can recover instead of continuing with a silent gap. Scope each subscription with
sport_ids and event_ids; market subscriptions also support market_ids and affiliate_ids.Use meaningful subscription ids
Use meaningful subscription ids
The
id is how you route incoming messages, so name subscriptions after what they carry (nba-markets, mlb-plays, nba-stats) rather than opaque strings. Keep a map of active ids to handlers and you can add or remove feeds without touching your message loop.Consolidate subscriptions where filters allow
Consolidate subscriptions where filters allow
Plans cap subscriptions per connection — from 3 on Ultra to 50 on Enterprise (error code
subscription_limit; see the connection limits table). One markets subscription filtered to two sports uses one slot; two single-sport subscriptions use two. The live channel can replace separate scores, plays, and stats subscriptions with a single slot. Split subscriptions only when you need to route or manage the feeds independently.Next Steps
WebSocket Reference
Full protocol details and payload field tables
WebSocket Streaming
The dedicated markets stream, heartbeats, and reconnection patterns
Play-by-Play REST Endpoint
Backfill the play timeline before streaming
Game Stats
Bootstrap team and player box scores before streaming deltas
Efficient Polling
REST delta endpoints as a fallback or backfill