> ## Documentation Index
> Fetch the complete documentation index at: https://docs.therundown.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Multiplexed 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); a `plays`, `stats`, or `live` subscribe from a
  non-entitled key is rejected with error code `forbidden`.
- `stats` — changed team and player box-score rows, delivered inline with
  `meta.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 including `live_game_state`, play-by-play deltas,
  and game-stat deltas, on one subscription. **Requires an Ultra plan or higher**;
  `live_game_state` and `game_state` are accepted aliases.

**Subscribe** (client → server):
```json
{
  "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`:
```json
{
  "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](/api-reference/v2/websocket) for full protocol details.




## OpenAPI

````yaml get /api/v2/ws
openapi: 3.1.0
info:
  title: TheRundown Sports API
  version: 2.0.0
  description: >
    **Resolve the event first.** Ambiguous date, team, player, or timezone? The
    agent asks instead of guessing.


    **Every price carries evidence.** Event, market, affiliate ID, line, and the
    price update time. A fetch time is not freshness.


    **Missing stays missing.** No remembered odds, no synthetic prices, no
    filled gaps.


    **IDs come from the API.** Sports, markets, and affiliates are discovered at
    runtime. Retired affiliates stay out.


    Real-time and historical sports betting data, odds, lines, and statistics
    across major North American and international sports leagues.


    ## Authentication

    All endpoints (except `/sports` and `/affiliates`) require authentication.
    Send your API key from a private server-side environment variable in the
    `X-TheRundown-Key` header.

    The query-key security scheme is retained for compatibility with existing
    integrations and deprecated for new integrations; do not put keys in URLs,
    prompts, browser bundles, or public code.

    ## Off-the-Board Sentinel Value

    The value **0.0001** indicates a line is "off the board" — the sportsbook
    has temporarily removed pricing (e.g., pending injury news). This is NOT an
    error. Display as "Off Board" or "N/A" in your UI.


    ## Rate Limiting

    Requests are rate-limited per API key tier. Check response headers for
    current limits.


    ## Data Updates

    - Live odds update in real-time during games

    - Use delta endpoints for efficient polling of changes

    - WebSocket connections available for streaming updates


    ## V1 vs V2

    V2 endpoints use market-based data structures (market_id, participants, line
    prices). V1 endpoints use legacy line-based structures (moneyline, spread,
    total objects). V2 is recommended for new integrations.
  contact:
    name: TheRundown API Support
    url: https://therundown.io
    email: support@therundown.io
  termsOfService: https://therundown.io/terms
servers:
  - url: https://therundown.io
    description: Production
security:
  - ApiKeyHeader: []
tags:
  - name: V2 Sports
    description: Sport listings, dates, and teams (V2)
  - name: V2 Events
    description: Events with market-based odds (V2)
  - name: V2 Markets
    description: Market definitions, odds, deltas, and history (V2)
  - name: V2 Futures
    description: >-
      Futures/outright competition events — championship and tournament-winner
      boards (V2, early access)
  - name: V2 Teams
    description: Team data, players, and stats (V2)
  - name: V2 Players
    description: Player data (V2)
  - name: V2 Stats
    description: Team and player statistics (V2)
  - name: V2 WebSocket
    description: Real-time streaming via WebSocket (V2)
  - name: V2 Reference
    description: Reference data — affiliates, sportsbooks, season types (V2)
  - name: V1 Events
    description: Events with line-based odds (V1 legacy)
  - name: V1 Lines
    description: Moneyline, spread, total, best-line endpoints (V1 legacy)
  - name: V1 Sports
    description: Sport listings, dates, events, schedules (V1 legacy)
  - name: V1 Delta
    description: Delta/change feeds (V1 legacy)
  - name: V1 Reference
    description: Reference data (V1 legacy)
  - name: V1 WebSocket
    description: Real-time streaming via WebSocket (V1 legacy)
externalDocs:
  description: Build with AI guide
  url: https://therundown.io/build-with-ai
paths:
  /api/v2/ws:
    get:
      tags:
        - V2 WebSocket
      summary: >-
        Multiplexed WebSocket — subscribe to markets, scores, plays, stats, and
        live channels
      description: >
        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); a `plays`, `stats`, or `live` subscribe from a
          non-entitled key is rejected with error code `forbidden`.
        - `stats` — changed team and player box-score rows, delivered inline
        with
          `meta.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 including `live_game_state`, play-by-play
        deltas,
          and game-stat deltas, on one subscription. **Requires an Ultra plan or higher**;
          `live_game_state` and `game_state` are accepted aliases.

        **Subscribe** (client → server):

        ```json

        {
          "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`:

        ```json

        {
          "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](/api-reference/v2/websocket) for full protocol
        details.
      operationId: v2WebSocketMultiplexed
      responses:
        '101':
          description: WebSocket upgrade successful
components:
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-TheRundown-Key
      description: Recommended API key request header for new integrations

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.