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

# Python SDK

> Use TheRundown API from Python with the requests library and websockets for real-time data.

<Note>
  An official Python SDK is coming soon. In the meantime, this guide shows how to use TheRundown API directly with the `requests` library for REST calls and `websockets` for real-time streaming.
</Note>

## Installation

```bash theme={null}
pip install requests websockets
```

## Configuration

```python theme={null}
import os

API_KEY = os.environ.get("THERUNDOWN_API_KEY", "YOUR_API_KEY")
BASE_URL = "https://therundown.io/api/v2"
WS_URL = "wss://therundown.io/api/v2/ws/markets"

HEADERS = {
    "X-TheRundown-Key": API_KEY,
}
```

## Getting Sports

```python theme={null}
import requests

response = requests.get(f"{BASE_URL}/sports", headers=HEADERS)
response.raise_for_status()

sports = response.json()["sports"]
for sport in sports:
    print(f"{sport['sport_id']}: {sport['sport_name']}")
```

## Getting Events with Odds

```python theme={null}
import requests
from datetime import date

sport_id = 4  # NBA
today = date.today().isoformat()

response = requests.get(
    f"{BASE_URL}/sports/{sport_id}/events/{today}",
    headers=HEADERS,
    params={
        "market_ids": "1,2,3",      # Moneyline, Spread, Total
        "affiliate_ids": "19,23",    # DraftKings, FanDuel
        "main_line": "true",
    }
)
response.raise_for_status()
data = response.json()

for event in data["events"]:
    away = event["teams"][0]["name"]
    home = event["teams"][1]["name"]
    print(f"\n{away} @ {home}")

    for market in event.get("markets", []):
        print(f"  {market['name']}:")
        for participant in market["participants"]:
            for line in participant["lines"]:
                for aff_id, price in line["prices"].items():
                    p = price["price"]
                    display = "N/A" if p == 0.0001 else f"{p:+d}" if p > 0 else str(int(p))
                    line_val = f" ({line['value']})" if line.get("value") else ""
                    print(f"    {participant['name']}{line_val}: {display} @ {aff_id}")
```

## Filtering by Affiliate

```python theme={null}
# Only DraftKings lines
response = requests.get(
    f"{BASE_URL}/sports/4/events/{date.today()}",
    headers=HEADERS,
    params={
        "market_ids": "1,2,3",
        "affiliate_ids": "19",  # DraftKings only
        "main_line": "true",
    }
)
```

## Getting Player Props

```python theme={null}
# NBA player points, rebounds, assists, 3PT
response = requests.get(
    f"{BASE_URL}/sports/4/events/{date.today()}",
    headers=HEADERS,
    params={
        "market_ids": "29,35,38,39",  # Points, Rebounds, 3PT, Assists
        "affiliate_ids": "19",
    }
)
response.raise_for_status()

for event in response.json()["events"]:
    print(f"\n{event['teams'][0]['name']} @ {event['teams'][1]['name']}")
    for market in event.get("markets", []):
        print(f"  {market['name']}:")
        for participant in market["participants"]:
            for line in participant["lines"]:
                price = line["prices"].get("19", {}).get("price", "N/A")
                print(f"    {participant['name']}: {line.get('value')} ({price})")
```

## Historical Odds

```python theme={null}
event_id = "abc123"

# Full market history
response = requests.get(
    f"{BASE_URL}/events/{event_id}/markets/history",
    headers=HEADERS,
    params={"affiliate_ids": "19"},
)
history = response.json()["history"]

for entry in history:
    print(f"{entry['updated_at']}: line={entry['line']} price={entry['price']} ({entry['change_type']})")

# Opening lines
openers = requests.get(
    f"{BASE_URL}/events/{event_id}/openers",
    headers=HEADERS,
    params={"market_ids": "1,2,3"},
).json()

# Closing lines
closing = requests.get(
    f"{BASE_URL}/events/{event_id}/closing",
    headers=HEADERS,
    params={"market_ids": "1,2,3"},
).json()
```

## WebSocket Streaming

```python theme={null}
import asyncio
import json
import websockets


async def stream_odds(sport_ids="4", market_ids="1,2,3"):
    url = (
        f"{WS_URL}?key={API_KEY}"
        f"&sport_ids={sport_ids}"
        f"&market_ids={market_ids}"
    )

    async with websockets.connect(url) as ws:
        print("Connected to WebSocket")

        async for message in ws:
            msg = json.loads(message)

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

            d = msg["data"]
            print(
                f"Update: event={d['event_id']} market={d['market_id']}"
                f" aff={d['affiliate_id']} price={d['price']}"
            )


asyncio.run(stream_odds())
```

## WebSocket with Reconnection

```python theme={null}
import asyncio
import json
import random
import websockets


async def stream_with_reconnect(sport_ids="4", market_ids="1,2,3"):
    url = (
        f"{WS_URL}?key={API_KEY}"
        f"&sport_ids={sport_ids}"
        f"&market_ids={market_ids}"
    )

    reconnect_delay = 1.0
    max_delay = 30.0

    while True:
        try:
            async with websockets.connect(url) as ws:
                print("WebSocket connected")
                reconnect_delay = 1.0

                async for message in ws:
                    msg = json.loads(message)
                    if msg.get("meta", {}).get("type") == "heartbeat":
                        continue

                    # Process update
                    d = msg["data"]
                    print(f"Update: event={d['event_id']} market={d['market_id']} price={d['price']}")

        except (websockets.ConnectionClosed, ConnectionError) as e:
            jitter = random.uniform(0, 1)
            delay = min(reconnect_delay + jitter, max_delay)
            print(f"Disconnected ({e}). Reconnecting in {delay:.1f}s...")
            await asyncio.sleep(delay)
            reconnect_delay = min(reconnect_delay * 2, max_delay)


asyncio.run(stream_with_reconnect())
```

## Error Handling

```python theme={null}
import requests
import time


def api_get(path, params=None, max_retries=3):
    """Make an API request with retry logic for rate limits."""
    for attempt in range(max_retries):
        response = requests.get(
            f"{BASE_URL}{path}",
            headers=HEADERS,
            params=params,
        )

        if response.status_code == 200:
            return response.json()

        if response.status_code == 429:
            wait = (2 ** attempt) + random.uniform(0, 1)
            print(f"Rate limited. Retrying in {wait:.1f}s...")
            time.sleep(wait)
            continue

        response.raise_for_status()

    raise Exception("Max retries exceeded")


# Usage
data = api_get(
    f"/sports/4/events/{date.today()}",
    params={"market_ids": "1,2,3"},
)
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Getting Live Odds" icon="signal" href="/guides/getting-live-odds">
    Detailed guide on fetching odds
  </Card>

  <Card title="WebSocket Streaming" icon="bolt" href="/guides/websocket-streaming">
    Real-time data streaming guide
  </Card>

  <Card title="Authentication" icon="key" href="/authentication">
    All authentication methods
  </Card>

  <Card title="Rate Limits" icon="gauge" href="/rate-limits">
    Rate limit details and best practices
  </Card>
</CardGroup>
