Skip to main content
TheRundown V2 API uses a market-based data model that is more flexible and extensible than V1’s flat line structure. This guide covers the key differences and provides a mapping to help you migrate.

Important Notes Before You Start

Use teams_normalized, not teams. V1 event responses contain both a teams array and a teams_normalized array. The teams array uses internal book-specific IDs that are inconsistent across sportsbooks and seasons. Always use teams_normalized — these are canonical, stable team IDs that work across all endpoints. The team_id values in teams_normalized are the same IDs used in /teams/{teamID}, stats, and player endpoints.
The V2 delta endpoint is a V1 feature. Despite living at /api/v2/delta and /api/v2/markets/delta, these are currently the only V2-path endpoints available and function as upgraded versions of the V1 delta. They return V2-formatted market data but are accessible to V1 API plans. The rest of the V2 API (events, markets, teams, players, stats) requires a V2 subscription.

Data Model: Lines vs. Markets

The fundamental change in V2 is how odds data is structured.

V1: Flat Lines Model

In V1, each event contains a lines object keyed by affiliate_id. Each affiliate entry has separate moneyline, spread, and total objects.
{
  "event_id": "abc123",
  "lines": {
    "19": {
      "affiliate_id": 19,
      "moneyline": {
        "moneyline_away": 150,
        "moneyline_home": -170
      },
      "spread": {
        "point_spread_away": 3.5,
        "point_spread_away_money": -110,
        "point_spread_home": -3.5,
        "point_spread_home_money": -110
      },
      "total": {
        "total_over": 220.5,
        "total_over_money": -110,
        "total_under": 220.5,
        "total_under_money": -110
      }
    },
    "23": {
      "affiliate_id": 23,
      "moneyline": {
        "moneyline_away": 145,
        "moneyline_home": -165
      }
    }
  }
}

V2: Markets Model

In V2, each event contains a markets array. Each market has participants with lines and prices organized by sportsbook.
{
  "event_id": "abc123",
  "markets": [
    {
      "market_id": 1,
      "name": "Moneyline",
      "period_id": 0,
      "participants": [
        {
          "name": "Team A",
          "lines": [
            {
              "is_main": true,
              "prices": {
                "19": { "price": 150, "affiliate_id": 19 },
                "23": { "price": 145, "affiliate_id": 23 }
              }
            }
          ]
        },
        {
          "name": "Team B",
          "lines": [
            {
              "is_main": true,
              "prices": {
                "19": { "price": -170, "affiliate_id": 19 },
                "23": { "price": -165, "affiliate_id": 23 }
              }
            }
          ]
        }
      ]
    }
  ]
}

Key Structural Differences

AspectV1V2
Data accessevent.lines[affiliate_id].moneylineevent.markets[].participants[].lines[].prices[affiliate_id]
Sportsbook groupingTop level (by affiliate)Nested inside lines (prices map)
Market typesFixed: moneyline, spread, totalExtensible: any market ID
Player propsNot supportedFull support via market IDs
Alternate linesLimitedFull support, is_main flag
Period dataline_periods objectperiod_id on each market

Period Data

V1: line_periods

V1 uses a nested line_periods object for half/quarter data:
{
  "lines": {
    "19": {
      "moneyline": { "moneyline_away": 150, "moneyline_home": -170 },
      "line_periods": {
        "period_first_half": {
          "moneyline": { "moneyline_away": 130, "moneyline_home": -150 },
          "spread": { "point_spread_away": 2.0, "point_spread_away_money": -110 }
        },
        "period_first_quarter": {
          "moneyline": { "moneyline_away": 180, "moneyline_home": -210 }
        }
      }
    }
  }
}

V2: period_id

V2 includes period data as separate market entries distinguished by period_id:
{
  "markets": [
    {
      "market_id": 1,
      "name": "Moneyline",
      "period_id": 0,
      "participants": [...]
    },
    {
      "market_id": 1,
      "name": "Moneyline",
      "period_id": 1,
      "participants": [...]
    }
  ]
}
See Period IDs for the complete list of period identifiers.

Delta Endpoints

There are four delta endpoints available. The V2-path delta endpoints (/api/v2/delta and /api/v2/markets/delta) are available to all API plans — they were built as upgrades to the V1 delta and happen to live under the /v2/ path.
EndpointFormatDescription
GET /api/v1/deltaV1 linesReturns full V1 events that changed. Use for legacy integrations.
GET /api/v1/deltaV2V2 marketsReturns V2-formatted data via the V1 path. Useful during migration.
GET /api/v2/deltaV2 eventsReturns changed events with V2 market data.
GET /api/v2/markets/deltaV2 markets onlyReturns only changed market/price data — most granular and efficient.
The V2 market delta is the most efficient option — it returns individual price changes rather than entire event objects, significantly reducing bandwidth.

Delta Polling Pattern

  1. Initial fetch: Pass last_id=0 (for markets delta) or a zeroed UUID for event deltas
  2. Store the cursor: Each response includes a last_id or delta_last_id value
  3. Subsequent polls: Pass the stored cursor to get only changes since your last poll
  4. Replace, don’t merge: Each delta contains the full updated object — replace your cached version entirely
# Most efficient: V2 markets delta
curl "https://therundown.io/api/v2/markets/delta?\
key=YOUR_API_KEY&last_id=0&sport_id=4&market_ids=1,2,3"

WebSocket Endpoints

V2 has dedicated WebSocket endpoints for different data types, while V1 uses a single endpoint.
VersionEndpointDescription
V1wss://therundown.io/api/v1/wsSingle stream, all line updates
V2wss://therundown.io/api/v2/ws/marketsMarket/price updates
V2 WebSockets support more granular filtering (by market_ids, event_ids, affiliate_ids).

Endpoint Mapping Table

Use this table to find the V2 equivalent of each V1 endpoint.
V1 EndpointV2 EquivalentNotes
GET /api/v1/sportsGET /api/v2/sportsSame response format
GET /api/v1/affiliatesGET /api/v2/affiliatesSame response format
GET /api/v1/sports/{id}/events/{date}GET /api/v2/sports/{id}/events/{date}Response uses markets model
GET /api/v1/events/{id}GET /api/v2/events/{id}Response uses markets model
GET /api/v1/sports/{id}/events/deltaGET /api/v2/markets/deltaV2 returns market-level changes
GET /api/v1/events/{id}/linesGET /api/v2/events/{id}/marketsMarkets replace lines
wss://therundown.io/api/v1/wswss://therundown.io/api/v2/ws/marketsV2 supports more filters
N/AGET /api/v2/events/{id}/markets/historyNew in V2
N/AGET /api/v2/events/{id}/openersNew in V2

Migration Code Example

Here is a side-by-side comparison of extracting odds data in V1 vs. V2.
import requests

API_KEY = "YOUR_API_KEY"

# V1: Fetch events
response = requests.get(
    "https://therundown.io/api/v1/sports/4/events/2026-02-12",
    params={"key": API_KEY}
)
data = response.json()

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

    lines = event.get("lines", {})

    # Access DraftKings lines (affiliate 19)
    dk = lines.get("19", {})
    if dk:
        ml = dk.get("moneyline", {})
        spread = dk.get("spread", {})
        total = dk.get("total", {})

        print(f"  ML: {ml.get('moneyline_away')} / {ml.get('moneyline_home')}")
        print(f"  Spread: {spread.get('point_spread_away')} ({spread.get('point_spread_away_money')})")
        print(f"  Total: O {total.get('total_over')} ({total.get('total_over_money')})")

    # Access first half lines
    periods = dk.get("line_periods", {})
    first_half = periods.get("period_first_half", {})
    if first_half:
        fh_ml = first_half.get("moneyline", {})
        print(f"  1H ML: {fh_ml.get('moneyline_away')} / {fh_ml.get('moneyline_home')}")

V2 Advantages

V2 supports player props (points, rebounds, assists, combos), team totals, and other market types that V1 cannot represent. New markets are added as new market IDs without API changes.
V2 returns all available lines (main and alternates) with an is_main flag to distinguish the primary line. Use main_line=true to filter to main lines only.
V2 delta and WebSocket endpoints deliver market-level updates rather than full event objects, reducing bandwidth and processing overhead.
V2 adds price history and opening lines endpoints that are not available in V1.

Common Gotchas

The 0.0001 sentinel value

A price of 0.0001 means the sportsbook has taken that line off the board — it is not an error. Display it as “Off Board” or “N/A” and never use it in calculations. See Sentinel Values for details.

V1 team IDs are unreliable

V1 events contain both teams and teams_normalized arrays. The teams array uses internal book-specific IDs that vary by sportsbook and can change between seasons. Always use teams_normalized for stable, canonical team identifiers. In V2, the teams array already uses normalized IDs.

Null vs. empty vs. zero

The API may return null, empty strings, empty arrays, or 0 depending on the field and state. Your parsing code should handle all of these:
  • A missing score object means the game hasn’t started
  • An empty markets array means no odds are available yet
  • A 0 value for game_clock can mean the period hasn’t started or has ended — check event_status for context

line_value_is_participant

This flag on market definitions tells you how to interpret the value field on lines:
  • true (moneyline) — the participant IS the bet. The line value is irrelevant (typically "0").
  • false (spread, total) — the line value is the number (e.g., "-3.5" for spread, "224.5" for total).

Next Steps