curl --request GET \
--url https://therundown.io/api/v2/ws \
--header 'X-TheRundown-Key: <api-key>'import requests
url = "https://therundown.io/api/v2/ws"
headers = {"X-TheRundown-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-TheRundown-Key': '<api-key>'}};
fetch('https://therundown.io/api/v2/ws', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://therundown.io/api/v2/ws",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-TheRundown-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://therundown.io/api/v2/ws"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-TheRundown-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://therundown.io/api/v2/ws")
.header("X-TheRundown-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://therundown.io/api/v2/ws")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-TheRundown-Key"] = '<api-key>'
response = http.request(request)
puts response.read_bodyMultiplexed WebSocket — subscribe to markets, scores, plays, stats, and live channels
Establishes a single WebSocket connection carrying multiple logical channels. Instead of query-parameter filters, send JSON subscribe messages after connecting.
Connection: wss://therundown.io/api/v2/ws
Authenticate the WebSocket upgrade with X-TheRundown-Key: $THERUNDOWN_API_KEY
from a server-side client. Native browser WebSocket clients cannot set custom
headers; use an authenticated backend relay for browser consumers. WebSocket
access requires an Ultra plan or higher.
Channels:
markets— market price updates (same payloads as/api/v2/ws/markets)scores— score and event-status updates (no live game state fields)plays— play-by-play deltas for live games. Requires an Ultra plan or higher (the live game state entitlement); aplays,stats, orlivesubscribe from a non-entitled key is rejected with error codeforbidden.stats— changed team and player box-score rows, delivered inline withmeta.type=game_stats. Requires an Ultra plan or higher. This is a delta-only channel with no snapshot, replay, or resume; bootstrap and recover from both event game-stat REST endpoints. Each changed stat row is one stats data point.live— score/status deltas includinglive_game_state, play-by-play deltas, and game-stat deltas, on one subscription. Requires an Ultra plan or higher;live_game_stateandgame_stateare accepted aliases.
Subscribe (client → server):
{
"action": "subscribe",
"id": "p1",
"channel": "plays",
"params": { "sport_ids": [3], "event_ids": ["<eventID>"] }
}
The server acknowledges with {"type":"subscribed","id":"p1","sequence":N,"message":"subscribed to plays"}.
Supported params filters: sport_ids, event_ids (plus market_ids and
affiliate_ids on the markets channel). stats has no stat, team, or player
filter. Unsubscribe with
{"action":"unsubscribe","id":"p1"}.
Delta messages (server → client) are wrapped in an envelope tagged with your
subscription id:
{
"type": "delta",
"id": "p1",
"sequence": 42,
"sub_sequence": 7,
"delta_last_id": "...",
"data": { "meta": { "type": "play" }, "data": { ... } }
}
A stats delta uses the same outer envelope. Its inner payload has
meta.type=game_stats; data.team_stats[] and data.player_stats[] contain
only changed rows, and each row’s value remains a JSON string. Upsert those
rows into the REST-bootstrapped box score rather than replacing a whole group.
If both row arrays are absent and complete=true, the frame is the terminal
completion marker; mark the cached box complete and treat repeats as idempotent.
If both arrays are absent without complete=true, the frame is an invalidation
fallback: refetch both GET /api/v2/events/{eventID}/stats and
GET /api/v2/events/{eventID}/players/stats. Row deltas and completion markers
use an RFC 3339 updated_at; the fallback uses numeric Unix seconds. Do not use
delta_last_id as a game-stat replay cursor. Each changed nested row is one
stats data point; either zero-row variant costs one stats data point.
Snapshots: add "snapshot": true to params to receive current state
(snapshot frames, then snapshot_complete) before deltas begin; an active
subscription can request a fresh snapshot at any time with
{"action":"snapshot","id":"p1"}. Snapshot requests need a bounded scope and
are metered as data points like the equivalent REST reads. Snapshots are not
supported on stats; a request returns snapshot_error with code
unsupported_snapshot. A live snapshot does not include a game-stat baseline.
Errors: {"type":"error","id":"...","code":"...","message":"..."} with codes
forbidden (plays/stats/live without Ultra+), invalid_channel, missing_id,
duplicate_id, and subscription_limit (plan’s concurrent subscription cap
reached). A slow multiplexed client may receive the connection-level error code
buffer_overflow immediately before the connection closes with reason
buffer_overflow:reconnect_and_catchup.
Queue and recovery: each non-market subscription has its own 1024-message
outbound queue; market subscriptions are sized separately. If a live frame
cannot be queued, the server closes the connection rather than continue with a
silent gap. Reconnect, re-send subscriptions, and recover current state before
applying new deltas. For stats, refetch both event game-stat REST resources.
Concurrent connection and subscription limits vary by tier. See the WebSocket reference for full protocol details.
curl --request GET \
--url https://therundown.io/api/v2/ws \
--header 'X-TheRundown-Key: <api-key>'import requests
url = "https://therundown.io/api/v2/ws"
headers = {"X-TheRundown-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-TheRundown-Key': '<api-key>'}};
fetch('https://therundown.io/api/v2/ws', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://therundown.io/api/v2/ws",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-TheRundown-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://therundown.io/api/v2/ws"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-TheRundown-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://therundown.io/api/v2/ws")
.header("X-TheRundown-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://therundown.io/api/v2/ws")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-TheRundown-Key"] = '<api-key>'
response = http.request(request)
puts response.read_bodyAuthorizations
Recommended API key request header for new integrations
Response
WebSocket upgrade successful