Skip to main content
This guide walks through building a complete odds screen from scratch — fetching sports, loading events, parsing market data, connecting a WebSocket for live updates, and handling price changes in your UI.

Step 1: Fetch the Sports List

Start by loading the list of available sports. This endpoint is public and does not require authentication.
import requests

BASE_URL = "https://therundown.io/api/v2"
API_KEY = "YOUR_API_KEY"

sports_response = requests.get(f"{BASE_URL}/sports")
sports = sports_response.json()["sports"]

for sport in sports:
    print(f"{sport['sport_id']}: {sport['sport_name']}")
Use the sport list to populate a sport selector in your UI. Common sport IDs:
SportID
NFL2
MLB3
NBA4
NCAAB5
NHL6

Step 2: Fetch Events for a Sport and Date

Once the user selects a sport, fetch events for that sport on a given date. Include market_ids=1,2,3 for moneyline, spread, and total. Use main_line=true to get only the primary line for each market.
from datetime import date

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

response = requests.get(
    f"{BASE_URL}/sports/{sport_id}/events/{today}",
    params={
        "key": API_KEY,
        "market_ids": "1,2,3",
        "affiliate_ids": "19,23,22",  # DraftKings, FanDuel, BetMGM
        "main_line": "true",
    }
)

events = response.json()["events"]
print(f"Found {len(events)} events")

Step 3: Parse Markets for Display

Each event contains a markets array. To build an odds screen, you need to extract moneyline, spread, and total data and organize it by team and sportsbook.
def parse_event_odds(event, target_affiliate="19"):
    """
    Parse an event into a display-friendly structure.
    Returns a dict with team names and their odds for each market.
    """
    away_team = event["teams"][0]["name"]
    home_team = event["teams"][1]["name"]

    result = {
        "event_id": event["event_id"],
        "away": away_team,
        "home": home_team,
        "moneyline": {"away": None, "home": None},
        "spread": {"away": None, "home": None, "away_line": None, "home_line": None},
        "total": {"over": None, "under": None, "line": None},
    }

    for market in event.get("markets", []):
        mid = market["market_id"]

        for participant in market["participants"]:
            name = participant["name"]
            is_away = name == away_team

            for line in participant["lines"]:
                price_obj = line["prices"].get(target_affiliate, {})
                price = price_obj.get("price")

                if price == 0.0001:
                    price = None  # Off the board

                if mid == 1:  # Moneyline
                    if is_away:
                        result["moneyline"]["away"] = price
                    else:
                        result["moneyline"]["home"] = price

                elif mid == 2:  # Spread
                    if is_away:
                        result["spread"]["away"] = price
                        result["spread"]["away_line"] = line.get("line")
                    else:
                        result["spread"]["home"] = price
                        result["spread"]["home_line"] = line.get("line")

                elif mid == 3:  # Total
                    result["total"]["line"] = line.get("line")
                    # Over is typically the first participant, Under the second
                    if participant.get("participant_type") == "TYPE_OVER" or "Over" in name:
                        result["total"]["over"] = price
                    else:
                        result["total"]["under"] = price

    return result


# Parse all events
for event in events:
    odds = parse_event_odds(event, target_affiliate="19")
    print(f"\n{odds['away']} @ {odds['home']}")
    print(f"  ML:     {odds['moneyline']['away']} / {odds['moneyline']['home']}")
    print(f"  Spread: {odds['spread']['away_line']} ({odds['spread']['away']}) / "
          f"{odds['spread']['home_line']} ({odds['spread']['home']})")
    print(f"  Total:  O/U {odds['total']['line']}  "
          f"({odds['total']['over']} / {odds['total']['under']})")

Step 4: Connect WebSocket for Real-Time Updates

Once your initial data is loaded, connect the V2 Markets WebSocket to receive live price updates. Filter by sport to reduce traffic.
import asyncio
import json
import websockets

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


async def listen_for_updates():
    async with websockets.connect(WS_URL) as ws:
        async for message in ws:
            data = json.loads(message)

            # Skip heartbeat messages
            if data.get("meta", {}).get("type") == "heartbeat":
                continue

            event_id = data.get("event_id")
            market_id = data.get("market_id")
            print(f"Update: event={event_id} market={market_id}")
            print(json.dumps(data, indent=2)[:500])


asyncio.run(listen_for_updates())

Step 5: Handle Price Updates in the UI

When a WebSocket message arrives, merge the update into your local state. A common pattern is to maintain a map of events keyed by event_id, then update the specific market/participant/line that changed.
# In-memory state: event_id -> parsed odds dict
odds_state = {}

# Initial load
for event in events:
    parsed = parse_event_odds(event, target_affiliate="19")
    odds_state[event["event_id"]] = parsed


def apply_update(update, target_affiliate="19"):
    """Apply a WebSocket market update to the local state."""
    event_id = update.get("event_id")
    if event_id not in odds_state:
        return  # Unknown event, skip

    market_id = update.get("market_id")
    current = odds_state[event_id]

    for participant in update.get("participants", []):
        name = participant.get("name", "")
        is_away = name == current["away"]

        for line in participant.get("lines", []):
            price_obj = line.get("prices", {}).get(target_affiliate, {})
            price = price_obj.get("price")

            if price == 0.0001:
                price = None

            if market_id == 1:
                key = "away" if is_away else "home"
                old_price = current["moneyline"][key]
                current["moneyline"][key] = price
                if old_price != price:
                    print(f"ML update: {name} {old_price} -> {price}")

            elif market_id == 2:
                key = "away" if is_away else "home"
                old_price = current["spread"][key]
                current["spread"][key] = price
                current["spread"][f"{key}_line"] = line.get("line")
                if old_price != price:
                    print(f"Spread update: {name} {old_price} -> {price}")

            elif market_id == 3:
                current["total"]["line"] = line.get("line")
                if "Over" in name or participant.get("participant_type") == "TYPE_OVER":
                    old_price = current["total"]["over"]
                    current["total"]["over"] = price
                else:
                    old_price = current["total"]["under"]
                    current["total"]["under"] = price
                if old_price != price:
                    print(f"Total update: {name} {old_price} -> {price}")

Tips for Production

If the WebSocket disconnects, use GET /api/v2/sports/{sportID}/events/delta to fetch only events that have changed since your last request. This is much more efficient than refetching the full event list.
When a price moves, briefly highlight the cell green (price improved for the bettor) or red (price worsened). This visual cue helps users notice live movement.
Always check for 0.0001 before displaying a price. Show “Off Board” or “N/A” instead. See Sentinel Values for details.
The sports and affiliates endpoints return reference data that rarely changes. Cache these responses and refresh once per day to avoid unnecessary API calls.
Use affiliate_ids, market_ids, and main_line=true to reduce payload size. Only request the data your UI actually displays.

Next Steps