> ## 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.

# WebSocket

> Real-time streaming of market price updates via WebSocket

## Overview

TheRundown provides a V2 WebSocket endpoint for streaming real-time data without polling:

| Endpoint             | Description                                                |
| -------------------- | ---------------------------------------------------------- |
| `/api/v2/ws/markets` | Streams market price changes (odds updates) as they happen |

WebSocket connections do **not** count against your REST API rate limit.

***

## Connection

Connect using a standard WebSocket client. Authentication is via the `key` query parameter:

```
wss://therundown.io/api/v2/ws/markets?key=YOUR_API_KEY
```

<Warning>
  Use `wss://` (secure WebSocket), not `ws://`. Connections on `ws://` will be rejected.
</Warning>

***

## 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.

| Parameter       | Description                                    |
| --------------- | ---------------------------------------------- |
| `affiliate_ids` | Comma-separated sportsbook IDs (e.g., `19,23`) |
| `sport_ids`     | Comma-separated sport IDs (e.g., `4,6`)        |
| `event_ids`     | Comma-separated event IDs                      |
| `market_ids`    | Comma-separated market IDs (e.g., `1,2,3`)     |

### Example Connection URLs

```bash theme={null}
# All NBA market updates
wss://therundown.io/api/v2/ws/markets?key=YOUR_API_KEY&sport_ids=4

# DraftKings + FanDuel moneyline updates only
wss://therundown.io/api/v2/ws/markets?key=YOUR_API_KEY&affiliate_ids=19,23&market_ids=1

# Updates for a specific event
wss://therundown.io/api/v2/ws/markets?key=YOUR_API_KEY&event_ids=EVENT_ID
```

### 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

```json theme={null}
{
  "meta": {
    "type": "market_price",
    "version": "v2",
    "timestamp": 1772495104
  },
  "data": {
    "id": 193600383,
    "event_id": "9b9d0cf6007fdaeb15c3a1888dcfd5df",
    "affiliate_id": 26,
    "market_participant_id": 19402291,
    "market_id": 3,
    "line": "1.5",
    "price": "-117",
    "previous_price": "-122.0000",
    "price_delta": 5,
    "is_main_line": true,
    "normalized_market_participant_id": 10,
    "normalized_market_participant_type": 3,
    "sport_id": 7,
    "updated_at": "2026-03-02T23:44:44Z"
  }
}
```

#### Meta Fields

| Field       | Type   | Description                                                      |
| ----------- | ------ | ---------------------------------------------------------------- |
| `type`      | string | `"market_price"` for price updates, `"heartbeat"` for keep-alive |
| `version`   | string | API version (`"v2"`)                                             |
| `timestamp` | number | Unix epoch timestamp when the message was sent                   |

#### Data Fields

| Field                                | Type    | Description                                                                      |
| ------------------------------------ | ------- | -------------------------------------------------------------------------------- |
| `id`                                 | number  | Unique price record ID                                                           |
| `event_id`                           | string  | Canonical V2 event ID                                                            |
| `affiliate_id`                       | number  | Sportsbook ID (see [Sportsbook IDs](/reference/sportsbooks))                     |
| `market_id`                          | number  | Market type (see [Market IDs](/reference/markets))                               |
| `market_participant_id`              | number  | Sportsbook-specific participant ID for this market entry                         |
| `normalized_market_participant_id`   | number  | Canonical participant ID — maps to `participant.id` in REST API responses        |
| `normalized_market_participant_type` | number  | Participant type identifier                                                      |
| `line`                               | string  | Line value (e.g., `"-4.5"` for spread, `"224.5"` for total, `"0"` for moneyline) |
| `price`                              | string  | Current American odds (e.g., `"-117"`, `"+150"`)                                 |
| `previous_price`                     | string  | Price before this update                                                         |
| `price_delta`                        | number  | Numeric change from previous price                                               |
| `is_main_line`                       | boolean | Whether this is the primary line                                                 |
| `sport_id`                           | number  | Sport ID                                                                         |
| `updated_at`                         | string  | ISO 8601 timestamp of the price change                                           |

***

## Heartbeat

The WebSocket endpoint sends a heartbeat message every **15 seconds** to keep the connection alive:

```json theme={null}
{
  "meta": {
    "type": "heartbeat"
  },
  "data": {
    "now": "2026-02-27T01:15:00Z"
  }
}
```

Your client should detect heartbeats and use them to confirm the connection is healthy. If you stop receiving heartbeats, the connection may have dropped -- reconnect.

***

## Message Queue

Each client has a **1024-message buffer**. If your client cannot consume messages fast enough, the server drops messages rather than blocking. This means:

* If your processing is slow, you may miss updates
* Design your client to process messages quickly and offload heavy work asynchronously
* For high-volume feeds, consider filtering to specific sports or events to reduce message volume

***

## Client Examples

<CodeGroup>
  ```javascript JavaScript (Browser / Node.js) theme={null}
  const ws = new WebSocket(
    "wss://therundown.io/api/v2/ws/markets?key=YOUR_API_KEY&sport_ids=4&market_ids=1,2,3"
  );

  ws.onopen = () => {
    console.log("Connected to TheRundown WebSocket");
  };

  ws.onmessage = (event) => {
    const msg = JSON.parse(event.data);

    // Skip heartbeats
    if (msg.meta?.type === "heartbeat") return;

    const d = msg.data;
    console.log(
      `Event ${d.event_id} | market=${d.market_id} aff=${d.affiliate_id}`
    );
    console.log(
      `  line=${d.line} price=${d.price} (was ${d.previous_price}, delta=${d.price_delta})`
    );
  };

  ws.onerror = (error) => {
    console.error("WebSocket error:", error);
  };

  ws.onclose = (event) => {
    console.log(`Disconnected: code=${event.code} reason=${event.reason}`);
    // Implement reconnection logic here
  };
  ```

  ```python Python (websockets library) theme={null}
  import asyncio
  import json
  import websockets

  API_KEY = "YOUR_API_KEY"
  URL = f"wss://therundown.io/api/v2/ws/markets?key={API_KEY}&sport_ids=4"

  async def listen():
      async with websockets.connect(URL) as ws:
          print("Connected to TheRundown WebSocket")
          async for message in ws:
              msg = json.loads(message)

              if msg.get("meta", {}).get("type") == "heartbeat":
                  continue

              d = msg["data"]
              print(
                  f"Event {d['event_id']} | market={d['market_id']} aff={d['affiliate_id']}"
              )
              print(
                  f"  line={d['line']} price={d['price']}"
                  f" (was {d['previous_price']}, delta={d['price_delta']})"
              )

  asyncio.run(listen())
  ```

  ```javascript Node.js (ws library) with Reconnection theme={null}
  const WebSocket = require("ws");

  const API_KEY = "YOUR_API_KEY";
  const URL = `wss://therundown.io/api/v2/ws/markets?key=${API_KEY}&sport_ids=4`;

  function connect() {
    const ws = new WebSocket(URL);

    ws.on("open", () => console.log("Connected"));

    ws.on("message", (raw) => {
      const msg = JSON.parse(raw);
      if (msg.meta?.type === "heartbeat") return;
      const d = msg.data;
      console.log(`Update: event=${d.event_id} market=${d.market_id} price=${d.price}`);
    });

    ws.on("close", () => {
      console.log("Disconnected, reconnecting in 3s...");
      setTimeout(connect, 3000);
    });

    ws.on("error", (err) => {
      console.error("WebSocket error:", err.message);
      ws.close();
    });
  }

  connect();
  ```
</CodeGroup>

***

<Info>
  If WebSocket is not an option for your architecture, use the [REST delta endpoints](/guides/efficient-polling) to poll for changes efficiently. For the full list of market types you can filter on, see [Market IDs](/reference/markets).
</Info>

## Best Practices

<AccordionGroup>
  <Accordion title="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. This keeps your 1024-message buffer from overflowing.
  </Accordion>

  <Accordion title="Implement automatic reconnection">
    WebSocket connections can drop due to network issues, server deployments, or idle timeouts. Always implement reconnection logic with exponential backoff (e.g., 1s, 2s, 4s, 8s, max 30s).
  </Accordion>

  <Accordion title="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.
  </Accordion>

  <Accordion title="Process messages asynchronously">
    Keep your message handler fast. Parse the JSON and push work to a queue or separate processing thread. A slow message handler causes the 1024-message buffer to fill up and messages to be dropped.
  </Accordion>
</AccordionGroup>
