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

# Data Model

> How events, markets, participants, lines, and prices relate to each other in the V2 API.

The V2 API organizes sports data in a nested hierarchy. Understanding this structure is essential for parsing event responses, building odds screens, and processing delta updates.

## Hierarchy Overview

```
Event
├── score                    # Live score, game clock, status
├── teams[]                  # Away team (index 0), Home team (index 1)
├── schedule                 # Season info, event name
└── markets[]                # Array of market types
      ├── market_id          # e.g., 1 = Moneyline, 2 = Spread, 3 = Total
      ├── period_id          # 0 = full game, 1 = first half, etc.
      └── participants[]     # Teams, players, or result types
            ├── id           # Participant identifier
            ├── type         # TYPE_TEAM, TYPE_PLAYER, or TYPE_RESULT
            └── lines[]      # Available lines for this participant
                  ├── id     # 32-character hex line identifier
                  ├── value  # Line value (e.g., "-3.5") or empty for moneyline
                  └── prices # Map of affiliate_id → price object
                        {affiliate_id}:
                          ├── id            # Numeric string price identifier
                          ├── price         # American odds (e.g., -110, +150)
                          ├── is_main_line  # true if this is the consensus line
                          └── updated_at    # ISO 8601 timestamp
```

## Event Object

Each event represents a single game or match. Events are the top-level objects returned by `/api/v2/sports/{sportID}/events/{date}`.

| Field | Type | Description |
| - | - | - |
| `event_id` | string | Canonical event identifier string. Use this value in V2 path params, filters, delta consumers, and cache keys. |
| `sport_id` | integer | Sport identifier. See [Sport IDs](/reference/sports). |
| `event_uuid` | string | Compatibility identifier retained for older integrations. Do not assume it matches `event_id`. |
| `event_date` | string | Scheduled start time in ISO 8601 UTC |
| `rotation_number_away` | integer | Away team rotation number (not used for soccer) |
| `rotation_number_home` | integer | Home team rotation number (not used for soccer) |
| `score` | object | Live score and game status. See [Score Object](#score-object) below. |
| `teams` | array | Two-element array: `[away_team, home_team]`. See [Team Object](#team-object) below. |
| `schedule` | object | Season metadata: `season_type`, `season_year`, `event_name`, `league_name` |
| `markets` | array | Array of [Market objects](#market-object) with odds data |
| `affiliate_source_ids` | object | Map of affiliate ID (string) → that sportsbook's own identifier for this event, when available. See [Source identifiers](/reference/sportsbooks#source-identifiers-mapping-to-a-books-own-data). |

<Note>
  For V2 REST endpoints and WebSocket filters, pass the `event_id` value returned in event payloads. Do not substitute `event_uuid`.
</Note>

### Score Object

| Field | Type | Description |
| - | - | - |
| `event_status` | string | Current status (e.g., `STATUS_SCHEDULED`, `STATUS_IN_PROGRESS`, `STATUS_FINAL`). See [Event Statuses](/reference/event-statuses). |
| `score_away` | integer | Away team score |
| `score_home` | integer | Home team score |
| `score_away_by_period` | array | Away team's per-period scores, not running totals; index `0` is the first period. Overtime periods are appended. Read this array by its own length. Its sum is not guaranteed to equal `score_away`; verify before using it. |
| `score_home_by_period` | array | Home team's per-period scores, not running totals; index `0` is the first period. Overtime periods are appended. Read this array by its own length. Its sum is not guaranteed to equal `score_home`; verify before using it. |
| `venue_name` | string | Arena or stadium name. May be an empty string when unavailable. |
| `venue_location` | string | City and state. May be an empty string when unavailable. |
| `game_clock` | integer | Game clock in seconds |
| `display_clock` | string | Formatted clock display (e.g., "4:32") |
| `game_period` | integer | Current period number |
| `broadcast` | string | TV broadcast network |
| `event_status_detail` | string | Human-readable status (e.g., "3rd Quarter - 4:32") |
| `updated_at` | string | ISO 8601 timestamp of the last score update |

<Note>
  Score metadata can be incomplete. Text fields such as `venue_name`, `venue_location`, `broadcast`, and `display_clock` may be empty when unavailable. Period arrays can also be empty. Use `score.updated_at` to judge freshness.
</Note>

Regulation NFL and NBA finals have four period entries per side and `game_period` of `4`. Each overtime adds another entry and advances `game_period`: one overtime gives five entries and `Final/OT`; two give six entries and `Final/2OT`. Regulation finals use `event_status_detail` of `Final`. Exhibition exceptions are described below.

Read each side by its own array length. In baseball, the home array is one entry shorter when the home team does not bat in the bottom of the ninth or the last extra inning. Do not pad a missing entry with zero or assume both arrays have the same length.

<Note>
  The final scores (`score_home` and `score_away`) and period arrays are independent fields. In the twelve months reviewed on September 8, 2026, every NFL and NBA event that reached a final state had period sums matching its final scores. `game_period` matched the array lengths in all but one exhibition game. These are observations, not enforced guarantees: verify completeness, each side's sum, and the period count before deriving a result, and handle mismatches without guessing.

  All-Star and preseason games can be final with an empty period array or `game_period` of `0` or `1`. Regular-season and playoff games were consistent in that review, but still need validation. Exclude preseason using the [season-specific sport IDs](/reference/sports#season-specific-sports), including NBA Preseason (`23`) and NFL Preseason (`25`).
</Note>

See [Scores and Results](/guides/scores-and-results) for worked examples, half scores, and checks before locking a result.

### Team Object

| Field | Type | Description |
| - | - | - |
| `team_id` | integer | Normalized team identifier (stable across seasons and endpoints) |
| `name` | string | Full team name (e.g., "Cleveland Cavaliers") |
| `mascot` | string | Team mascot (e.g., "Cavaliers") |
| `abbreviation` | string | Short abbreviation (e.g., "CLE") |
| `record` | string | Current season record (e.g., "42-14") |
| `is_away` | boolean | `true` if this is the away team |
| `is_home` | boolean | `true` if this is the home team |

## Market Object

Each market represents a type of bet (moneyline, spread, total, player prop, etc.). Markets are nested inside events.

| Field | Type | Description |
| - | - | - |
| `id` | integer | Instance identifier for this market on this event |
| `market_id` | integer | Canonical market type ID. See [Market IDs](/reference/markets). |
| `period_id` | integer | Period this market applies to. `0` = full game. See [Period IDs](/reference/periods). |
| `name` | string | Display name (e.g., "Moneyline", "Total Over/Under") |
| `market_description` | string | Human-readable description |
| `participants` | array | Array of [Participant objects](#participant-object) |

## Participant Object

Participants are the entities you can bet on within a market — teams, players, or result types (Over/Under).

| Field | Type | Description |
| - | - | - |
| `id` | integer | Participant identifier (team ID, player ID, or `0`/`1` for result types) |
| `type` | string | `TYPE_TEAM`, `TYPE_PLAYER`, or `TYPE_RESULT` |
| `name` | string | Display name (e.g., "Cleveland Cavaliers", "Donovan Mitchell", "Over") |
| `lines` | array | Array of [Line objects](#line-object) |

<Note>
  **`id` is the stable, joinable identifier — join on `id`, not `name`.** What `id` points to depends on `type`:

  * **`TYPE_TEAM`** — `id` is the normalized team ID. It is stable across seasons and endpoints, and matches `event.teams[].team_id`. Fetch the full team at `GET /api/v2/teams/{team_id}`.
  * **`TYPE_PLAYER`** — `id` is the player ID. Fetch the full player (team, names, position) at `GET /api/v2/players/{player_id}`.
  * **`TYPE_RESULT`** — `id` is a small outcome index (e.g. `0`/`1` for Over/Under) and is **not** a team or player resource key.

  Because every distinct team and player has a distinct `id`, joining on `id` resolves shared-name collisions that joining on `name` cannot. To look up an `id` from a name once, use the roster at `GET /api/v2/teams/{team_id}/players` or the team list at `GET /api/v2/sports/{sportID}/teams`.
</Note>

## Line Object

A line represents a specific betting line for a participant. For spreads and totals, the `value` contains the line number. For moneylines, `value` is an empty string.

| Field | Type | Description |
| - | - | - |
| `id` | string | 32-character hex identifier for this line |
| `value` | string | Line value: `"-3.5"` for spread, `"224.5"` for total, `""` for moneyline |
| `prices` | object | Map of affiliate ID → [Price object](#price-object). Keyed by sportsbook. |

<Note>
  `line_value_is_participant` tells you where the meaningful selection detail lives. When it is `true`, the participant carries the selection and the line `value` may be a placeholder or label. When it is `false`, display the line `value` when present; it may be a number, threshold, method, round, or other outcome qualifier.
</Note>

## Price Object

A price is the odds offered by a single sportsbook for a specific line.

| Field | Type | Description |
| - | - | - |
| `id` | string | Numeric string identifier for this price |
| `price` | number | American odds (e.g., `-110`, `+150`). A value of `0.0001` means the line is off the board. See [Sentinel Values](/reference/sentinel-values). |
| `is_main_line` | boolean | `true` if this is the primary/consensus line. Use `main_line=true` query param to filter to main lines only. |
| `updated_at` | string | ISO 8601 timestamp of the last price update |
| `source_id` | string | The sportsbook's own identifier for this price/selection, when available (omitted otherwise). For Polymarket it encodes the event slug, Gamma market ID, and CLOB token ID — see [Source identifiers](/reference/sportsbooks#source-identifiers-mapping-to-a-books-own-data). |

Fields that appear in delta/history responses:

| Field | Type | Description |
| - | - | - |
| `price_delta` | number | Difference from the previous price (present in some delta responses) |
| `closed_at` | string | Optional ISO 8601 timestamp when a closing time was recorded; omitted when none is recorded. |

Chart responses use the optional `c` field for the same closing-time semantics. A missing closing timestamp does not by itself show that a line is currently open.

Fields present on selected affiliates:

| Field | Type | Description |
| - | - | - |
| `liquidity_usd` | number | USD reading for this price. On Kalshi (affiliate 25) and Polymarket (affiliate 26) this is approximate resting-order-book depth. On Pinnacle (affiliate 3) this is the book's max stake. How each venue computes it, and how often it refreshes, differs. See the note below. |

<Note>
  `liquidity_usd` is **omitted** entirely, never `null` and never a fabricated `0`, when a current reading is not available. Treat a missing field as "unknown," not as zero. See [What is `liquidity_usd` on a price object?](/faq) in the FAQ for what the number means on each affiliate and why it is not on every price.
</Note>

## Traversing the Data

### Reading a moneyline price

```
event.markets[0].participants[0].lines[0].prices["19"].price
```

Gives you the DraftKings (affiliate 19) moneyline price for the first participant (away team).

### Reading a spread value and price

```
event.markets[1].participants[0].lines[0].value    → "-3.5"
event.markets[1].participants[0].lines[0].prices["19"].price → -110
```

### Iterating all prices for an event

```python theme={null}
for market in event["markets"]:
    for participant in market["participants"]:
        for line in participant["lines"]:
            for affiliate_id, price_obj in line["prices"].items():
                print(f"{market['name']} | {participant['name']} | "
                      f"{line['value']} | {affiliate_id}: {price_obj['price']}")
```

## Delta Responses

Delta endpoints return a different shape. Instead of the nested event → market → participant → line → price hierarchy, they return **flat change records**:

| Field | Description |
| - | - |
| `event_id` | Which event changed |
| `market_id` | Which market |
| `participant_id` | Which participant |
| `affiliate_id` | Which sportsbook |
| `line` | Line value |
| `price` | New price |
| `previous_price` | Previous price (empty string if new) |
| `change_type` | `"new"`, `"price_change"`, or `"line_change"` |

Use the `event_id`, `market_id`, `participant_id`, and `affiliate_id` to locate the correct entry in your local cache and replace the price. See the [Efficient Polling guide](/guides/efficient-polling) for the full update pattern.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.