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.
Always pass offset=300 to align the date boundary with US Central Time. Without this, games that tip off late at night may show up under the next day’s date.
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",
        "offset": "300",             # Central Time
    }
)

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

Step 3: Discover Available Markets

Not every sport or event has the same markets. Before building your odds columns, check which markets actually have data. There are two ways to do this:

By sport and date

Returns all markets with active pricing for a sport on a given date, keyed by sport ID. Use hide_closed_markets=1 to exclude markets that have been taken off the board.
curl "https://therundown.io/api/v2/sports/4/markets/2026-02-12?key=YOUR_API_KEY&offset=300"

By event ID

Returns only the markets available for a specific event. Useful when building a detail view for a single game.
curl "https://therundown.io/api/v2/events/EVENT_ID/markets?key=YOUR_API_KEY"
Use the event_id value returned by GET /api/v2/sports/{sportID}/events/{date} when calling per-event V2 endpoints. Each market object includes:
FieldDescription
idNumeric market ID — pass these as market_ids when fetching event odds
nameHuman-readable name (e.g., “Moneyline”, “Player Points”)
propositiontrue for player prop markets, false for game-level markets
period_idPeriod this market applies to (full game, first half, etc.)
live_variant_idIf set, the corresponding live/in-play market ID
descriptionLonger description of the market
The three core markets for an odds screen are 1 (Moneyline), 2 (Spread), and 3 (Total). See Market IDs for the full list including player props and live markets.

Step 4: Parse Markets for Display

Each event contains a markets array. Index it by market_id to pull moneyline (1), spread (2), and total (3) for each sportsbook.
MARKET_MONEYLINE = 1
MARKET_SPREAD = 2
MARKET_TOTAL = 3


def get_price(participant, affiliate_id):
    """Extract the price from a participant's first line for a given book."""
    for line in participant.get("lines", []):
        p = line.get("prices", {}).get(affiliate_id, {}).get("price")
        if p == 0.0001:
            return None, line.get("value")  # Off the board
        return p, line.get("value")
    return None, None


def fmt(price):
    """Format American odds for display."""
    if price is None:
        return "N/A"
    return f"+{int(price)}" if price > 0 else str(int(price))


# Build a lookup: market_id -> market object
for event in events:
    away = event["teams"][0]["name"]
    home = event["teams"][1]["name"]
    markets = {m["market_id"]: m for m in event.get("markets", [])}

    # Moneyline
    ml = markets.get(MARKET_MONEYLINE, {})
    ml_prices = {}
    for p in ml.get("participants", []):
        price, _ = get_price(p, "19")
        ml_prices[p["name"]] = fmt(price)

    # Spread
    sp = markets.get(MARKET_SPREAD, {})
    sp_prices = {}
    for p in sp.get("participants", []):
        price, value = get_price(p, "19")
        sp_prices[p["name"]] = f"{value} ({fmt(price)})"

    # Total
    tot = markets.get(MARKET_TOTAL, {})
    tot_prices = {}
    for p in tot.get("participants", []):
        price, value = get_price(p, "19")
        side = "O" if p.get("type") == "TYPE_OVER" else "U"
        tot_prices[side] = f"{value} {fmt(price)}"

    print(f"\n{away} @ {home}")
    print(f"  ML:     {ml_prices.get(away, 'N/A')} / {ml_prices.get(home, 'N/A')}")
    print(f"  Spread: {sp_prices.get(away, 'N/A')} / {sp_prices.get(home, 'N/A')}")
    print(f"  Total:  {tot_prices.get('O', 'N/A')} / {tot_prices.get('U', 'N/A')}")

Step 5: 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:
            msg = json.loads(message)

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

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


asyncio.run(listen_for_updates())

Step 6: Handle Price Updates in the UI

When a WebSocket message arrives, merge the individual price update into your local state. Each message contains a single price change — find the matching event, market, participant, and affiliate, then update the price.
# In-memory state: event_id -> full event dict from the REST API
event_state = {e["event_id"]: e for e in events}


def apply_price_update(update):
    """Merge a single WebSocket price update into the local event state."""
    event = event_state.get(update["event_id"])
    if not event:
        return

    market_id = update["market_id"]
    aff_id = str(update["affiliate_id"])
    participant_id = update["normalized_market_participant_id"]
    new_price = float(update["price"])

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

        for participant in market.get("participants", []):
            if participant["id"] != participant_id:
                continue

            for line in participant.get("lines", []):
                prices = line.setdefault("prices", {})
                prices[aff_id] = {
                    "price": new_price,
                    "is_main_line": update["is_main_line"],
                    "updated_at": update["updated_at"],
                }
                print(f"Updated: market={market_id} participant={participant_id} aff={aff_id} price={new_price}")
                return

Tips for Production

If the WebSocket disconnects, use GET /api/v2/delta to fetch event deltas or GET /api/v2/markets/delta to fetch market price deltas 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

WebSocket Streaming

Deep dive into WebSocket configuration

Efficient Polling

Delta endpoints and cache strategies for when WebSocket isn’t available

Player Props

Add player prop markets to your screen

Historical Odds

Track line movement over time

Data Model

How events, markets, lines, and prices relate

Sportsbook IDs

Full list of affiliate IDs