> Full text of the [NBA API guide](https://odds-api.net/docs/nba).

# Build an NBA product with Odds API

Find NBA games, load every available sportsbook market, follow price changes, and read game results. One `event_id` connects each request.

## Integration path

### Find NBA games

Request NBA events and keep each `event_id` for follow-up calls.

### Load the odds

Page through the game snapshot to collect every available market.

### Follow changes

Apply odds deltas and read the result when the game finishes.

```bash
curl -sS -G 'https://api.odds-api.net/v1/events' \
  -H "X-API-Key: $ODDS_API_KEY" \
  --data-urlencode 'sport=basketball' \
  --data-urlencode 'league=NBA'
```

- **Base URL:** https://api.odds-api.net/v1
- **Auth:** `X-API-Key: $ODDS_API_KEY`
- **League filter:** `sport=basketball&league=NBA`

## Build from the available NBA data

Start with `GET /v1/coverage?sport=basketball&league=NBA` to build sportsbook and market filters. The event and odds responses determine what your product can display for a particular game.

## Use the right NBA endpoint

The public API separates game discovery, game records, odds, and results. A stream sends changes after an initial snapshot.

| Need | Endpoint | What it returns |
| --- | --- | --- |
| Current coverage | `GET /v1/coverage?sport=basketball&league=NBA` | NBA league, sportsbook, and approximate market coverage. |
| Games | `GET /v1/events?league=NBA` | Team names, tip-off times, canonical IDs, and pages of games. |
| Game record | `GET /v1/events/{event_id}` | The source game record under `data`. |
| Game result | `GET /v1/events/{event_id}/results` | `pending` or `available`, with the result record when present. |
| All game odds | `GET /v1/events/{event_id}/odds/snapshot` | Current rows across available sportsbooks, markets, and periods. Follow every `next_cursor`. |
| All game odds changes | `GET /v1/events/{event_id}/odds/stream` | SSE changes for that game's odds. WebSocket: `/odds/ws`. |
| Line movement | `GET /v1/events/{event_id}/odds/history` | Retained points for one `selection_key` and time range when history access is enabled. |
| NBA main lines | `GET /v1/odds/main-lines/stream?leagues=NBA` | A league-wide feed of main lines. It is a smaller view than every game market. |

## Find NBA coverage and sportsbooks

Request `GET /v1/coverage?sport=basketball&league=NBA` to build your league and sportsbook filters. The coverage response contains `bookmakers`, `leagues`, and approximate `markets` with an `as_of` timestamp. Use the canonical keys returned by `GET /v1/bookmakers` when filtering odds.

```bash
curl -sS -G 'https://api.odds-api.net/v1/coverage' \
  --data-urlencode 'sport=basketball' \
  --data-urlencode 'league=NBA'
```

The `bookmakers` field on an NBA event shows which sportsbooks are attached to that game. The event odds snapshot shows their current selections. For sportsbook-specific context, see [DraftKings](https://odds-api.net/bookmakers/draftkings), [FanDuel](https://odds-api.net/bookmakers/fanduel), [BetMGM](https://odds-api.net/bookmakers/betmgm), and [Bet365](https://odds-api.net/bookmakers/bet365).

## Discover games and read results

Page `/v1/events` with `sport=basketball` and `league=NBA`. Set `start_from` and `start_to` as Unix seconds when you need live games as well as upcoming ones. Without `start_from`, the endpoint starts at the current time.

```bash
curl -sS -G 'https://api.odds-api.net/v1/events' \
  -H "X-API-Key: $ODDS_API_KEY" \
  --data-urlencode 'sport=basketball' \
  --data-urlencode 'league=NBA' \
  --data-urlencode 'limit=200'
```

Illustrative response. Use the returned IDs and times; these values are examples.

```json
{
  "count": 1,
  "items": [
    {
      "away_team": "New York Knicks",
      "bookmakers": {
        "draftkings": null,
        "fanduel": null
      },
      "event_id": "nba-example-001",
      "home_team": "Boston Celtics",
      "league": "NBA",
      "sport": "basketball",
      "start_time": 1792875600
    }
  ],
  "next_cursor": null
}
```

### Follow the cursor

Pass `next_cursor` back as `cursor` with the same time filters until it is null. Use `event_id` for every follow-up request.

### Game record

```text
GET /v1/events/{event_id}
```

The response contains `event_id` and a `data` object. Read the fields actually supplied for that game; the record can carry more source detail than the event list.

### Result

```text
GET /v1/events/{event_id}/results
```

The result endpoint returns `status` as `pending` or `available`. An available response contains the result record. Poll it for scores; this endpoint is not an odds stream.

A game without a result record returns:

```json
{
  "event_id": "nba-example-001",
  "result": null,
  "status": "pending"
}
```

## Request every available odds market

Leave `bookmakers`, `types`, `market_keys`, and `periods` out of the request. That returns all odds rows available to your account for the chosen game. `price_fields=all` includes the supported price fields; `include_unavailable=true` also returns suspended rows.

```bash
curl -sS -G 'https://api.odds-api.net/v1/events/$EVENT_ID/odds/snapshot' \
  -H "X-API-Key: $ODDS_API_KEY" \
  --data-urlencode 'limit=10000' \
  --data-urlencode 'price_fields=all' \
  --data-urlencode 'include_unavailable=true' \
  --data-urlencode 'include_source=true'
```

Continue with the snapshot's `next_cursor` until it is null. Save the `resume` token from the first page before opening the stream, so changes during later pages can be replayed. Match stream changes by the odds-row `id`.

Illustrative snapshot with a moneyline and a player prop. API prices are decimal: 1.91 is about American -110; 2.05 is about +105.

```json
{
  "as_of_ts_ms": 1792875300000,
  "event_id": "nba-example-001",
  "items": [
    {
      "bet_type": "moneyline",
      "bookmaker": "draftkings",
      "event_id": "nba-example-001",
      "fair_odds": 1.95,
      "id": "line-example-001",
      "is_available": true,
      "market_key": "moneyline",
      "odds": 1.91,
      "odds_no_vig": 1.96,
      "period": "full game",
      "selection_key": "selection-example-home",
      "selection_name": "Boston Celtics",
      "side": "home"
    },
    {
      "bet_type": "player prop",
      "bookmaker": "draftkings",
      "event_id": "nba-example-001",
      "id": "line-example-002",
      "is_available": true,
      "line": "24.5",
      "market_key": "player points",
      "odds": 2.05,
      "period": "full game",
      "player_name": "Example Player",
      "selection_key": "selection-example-points-over",
      "side": "over"
    }
  ],
  "next_cursor": null,
  "resume": "1792875300000-0",
  "ttl_seconds": 120
}
```

## Read NBA market keys

`market_key` identifies the normalized market in an odds row. Use `market_keys` in a query only when your product needs a subset. Keep `period`, `line`, `side`, and `player_name` when comparing two prices.

| American market name | API market keys | Meaning |
| --- | --- | --- |
| **Moneyline** | `moneyline` | Straight-up prices for each team to win the game. |
| **Point spread** | `handicap` | Spread prices for both teams, including alternate lines where supplied. |
| **Game total** | `total` | Over and under prices for the combined game score. |
| **Team total** | `team total` | Over and under prices for each team's score. |
| **Player scoring props** | `player points`<br>`player threes`<br>`player field goals` | Player points, made three-pointers and field-goal lines for covered NBA players. |
| **Player stat props** | `player assists`<br>`player rebounds`<br>`player blocks`<br>`player steals`<br>`player turnovers` | Assists, rebounds, blocks, steals and turnovers for covered NBA players. |
| **Combined player props** | `player pra`<br>`player pr`<br>`player pa`<br>`player ra`<br>`pickem player points` | Points-rebounds-assists combinations and pick'em player points when supplied. |

For example, a point spread uses the API key `handicap`. A game total uses `total`. Player points and player rebounds use their literal prop keys. The [Python player props tutorial](https://odds-api.net/guides/nba-player-props-api-python) shows how to match exact prop selections across sportsbooks.

## Follow NBA odds updates

Open one odds stream for each NBA game you are tracking. Pass the snapshot's `resume` token as `since`, with the same price and availability options used for the snapshot. The stream returns changes, not another full odds list.

```bash
curl -N -G 'https://api.odds-api.net/v1/events/$EVENT_ID/odds/stream' \
  -H "X-API-Key: $ODDS_API_KEY" \
  --data-urlencode "since=$RESUME" \
  --data-urlencode 'catchup=true' \
  --data-urlencode 'price_fields=all' \
  --data-urlencode 'include_unavailable=true' \
  --data-urlencode 'include_source=true'
```

Illustrative payload for an SSE `delta` event. The `odd.id` identifies the row to replace.

```json
{
  "changes": [
    {
      "odd": {
        "bet_type": "moneyline",
        "bookmaker": "draftkings",
        "event_id": "nba-example-001",
        "id": "line-example-001",
        "is_available": true,
        "market_key": "moneyline",
        "odds": 1.95,
        "period": "full game",
        "selection_key": "selection-example-home",
        "selection_name": "Boston Celtics",
        "side": "home"
      },
      "op": "upsert"
    }
  ],
  "event_id": "nba-example-001",
  "resume": "1792875315000-0"
}
```

### Full NBA market feed

Discover games with `/v1/events`, then snapshot and stream each game. Repeat discovery to add newly scheduled games. This covers all markets offered through each event's odds endpoint.

### League-wide main lines

Use `/v1/odds/main-lines/snapshot?leagues=NBA` and `/v1/odds/main-lines/stream?leagues=NBA` for a lighter NBA scoreboard. Main lines are a subset of the per-game markets.

2. **`delta`:** apply each change to the local odds row keyed by `odd.id`. Save the latest `resume` token.
4. **`heartbeat`:** keep the connection open. It has no odds changes.
6. **`resync`:** discard the local odds view, reload every snapshot page, then reopen the stream with the new token.
8. **Disconnect:** reconnect with `since=<last_resume>` and `catchup=true`. Back off after errors.

The WebSocket route is `/v1/events/{event_id}/odds/ws`. It accepts the same query filters and sends JSON envelopes shaped as `{"event":"delta","data":{...}}`. Keep API keys on your server. Game discovery and results still use their REST endpoints.

For a line chart or audit, request `/v1/events/{event_id}/odds/history` with a `selection_key` from the snapshot and bounded ISO 8601 `from_ts` and `to_ts` values. The history stream follows one selection. Both history routes require history access; use the game snapshot and odds stream for current state.

## Understand the NBA data

These fields are the pieces an NBA product needs to join games, compare the same bet, show current prices, and recover a live connection. Optional fields can be absent or null.

### Game identity and timing

| Field | What it means |
| --- | --- |
| `event_id` | The canonical game ID. Use it in the game, result, snapshot, and stream paths. |
| `sport / league` | Use basketball and NBA to identify the league in event and coverage responses. |
| `home_team / away_team` | The teams assigned to the home and away sides of this game. |
| `start_time` | Scheduled tip-off as Unix seconds. Convert it to the viewer's time zone in your app. |
| `bookmakers` | Sportsbooks attached to the event. The event list is a discovery hint; the odds snapshot contains the actual selections. |

### Markets and selections

| Field | What it means |
| --- | --- |
| `id` | An odds-row ID. Use it to update a row when a stream delta arrives; treat its format as opaque. |
| `bookmaker` | The canonical sportsbook key. Keep it when comparing prices across books. |
| `market_key` | The exact normalized market key in an odds row. The market_keys query filter matches this value. |
| `bet_type` | The normalized market family, such as moneyline, handicap, total, or player prop. |
| `period / period_str` | Which game period the bet covers. Keep the period when matching selections. |
| `metric` | The measured stat or scoring quantity when the market supplies one. |
| `line` | The spread, total, or player-stat threshold. It is nullable and may be a string. |
| `side` | The side of a market, such as home, away, over, or under. |
| `player_name` | The player for a prop market, when present. |
| `selection_name` | The display name of the offered selection, when present. |
| `selection_key` | An optional normalized selection identity. Treat it as opaque and also compare period, line, player, and side. |

### Prices and state

| Field | What it means |
| --- | --- |
| `odds` | The sportsbook's decimal price. A value of 1.91 is about American -110; 2.05 is about +105. |
| `odds_no_vig` | A nullable price with the sportsbook margin removed. |
| `fair_odds` | A nullable composite fair-odds estimate. Request price_fields=all to include every supported price field. |
| `is_available` | Whether the row is currently offered. Request include_unavailable=true when your product must track suspensions. |
| `as_of_ts_ms` | Snapshot freshness time in Unix milliseconds. It applies to the snapshot, not the game's tip-off. |
| `ttl_seconds` | The snapshot cache lifetime when supplied. Show age rather than presenting stale odds as current. |
| `next_cursor` | The next page token. Continue until it is null for both event lists and odds snapshots. |
| `resume / since` | Pass the snapshot resume token as the stream's since parameter, then persist the latest delta resume token. |

The API provides sportsbook player-prop lines, including points and rebounds. It does not expose a separate player box-score statistics feed.

## Handle normal production states

| Response or state | What to do |
| --- | --- |
| No NBA events | Keep the selected time window visible. An empty page means no games matched that request; widen the window or wait for another slate. |
| No odds rows | Keep the game record. Odds can be posted later, and a specific sportsbook or prop may have no rows. |
| `status=pending` | The result record is not ready. Poll the game result endpoint separately from the odds stream. |
| Old `as_of_ts_ms` | Show the snapshot age in your product and reload rather than presenting a stale price as current. |
| 401 or 403 | Check the server-side API key and whether the account has event odds access. See [plans](https://odds-api.net/pricing). |
| 429 or stream disconnect | Back off and reconnect with the last `resume` token. Respect rate-limit headers when present. |
| `resync` | Reload all snapshot pages before opening a new stream. The older changes cannot safely be replayed. |

Keep `ODDS_API_KEY` on your server. Use [the API reference](https://odds-api.net/docs) for current response codes and parameter limits, and [status and support](https://odds-api.net/sla-status-support) when an endpoint is unavailable.

## Run the complete NBA example

The Python example pages a rolling NBA schedule, requests game records and results, loads every odds snapshot page, and opens a stream for each game. It writes JSON Lines, so another process can consume the complete records. Set a suitable discovery window and connection budget for your product.

```bash
python -m pip install httpx
export ODDS_API_KEY='your_api_key'
python nba_full_feed.py
```

Save [nba_full_feed.py](https://odds-api.net/static/examples/nba_full_feed.py?v=1790651753) and run it from your server. Set `NBA_PAST_HOURS` and `NBA_FUTURE_HOURS` to change the rolling game window. Set `ODDS_API_BASE_URL` for a different API environment.

### Read the full Python example

```python
"""Discover NBA games, load every available odds row, and follow each game.

Install: python -m pip install httpx
Run:     ODDS_API_KEY=your_key python nba_full_feed.py

The program writes JSON Lines to stdout. Set NBA_PAST_HOURS and
NBA_FUTURE_HOURS to change the rolling discovery window.
"""

from __future__ import annotations

import asyncio
import json
import os
import random
import sys
import time
from typing import Any, AsyncIterator

import httpx


BASE_URL = os.getenv("ODDS_API_BASE_URL", "https://api.odds-api.net/v1").rstrip("/") + "/"
API_KEY = os.getenv("ODDS_API_KEY", "").strip()
PAST_HOURS = int(os.getenv("NBA_PAST_HOURS", "8"))
FUTURE_HOURS = int(os.getenv("NBA_FUTURE_HOURS", "48"))
DISCOVERY_SECONDS = int(os.getenv("NBA_DISCOVERY_SECONDS", "300"))
ODDS_SHAPE = {
    "price_fields": "all",
    "include_unavailable": "true",
    "include_source": "true",
}


def emit(kind: str, **payload: Any) -> None:
    print(json.dumps({"kind": kind, **payload}, separators=(",", ":")), flush=True)


async def get_json(client: httpx.AsyncClient, path: str, params: dict | None = None) -> dict:
    response = await client.get(path, params=params)
    response.raise_for_status()
    return response.json()


async def nba_events(client: httpx.AsyncClient) -> list[dict]:
    now = int(time.time())
    params = {
        "sport": "basketball",
        "league": "NBA",
        "start_from": now - PAST_HOURS * 3600,
        "start_to": now + FUTURE_HOURS * 3600,
        "limit": 1000,
    }
    events: list[dict] = []
    while True:
        page = await get_json(client, "events", params)
        events.extend(page.get("items") or [])
        cursor = page.get("next_cursor")
        if not cursor:
            return events
        params["cursor"] = cursor


async def complete_snapshot(client: httpx.AsyncClient, event_id: str) -> tuple[dict, dict[str, dict]]:
    params = {"limit": 10000, **ODDS_SHAPE}
    rows: dict[str, dict] = {}
    first_page: dict | None = None
    while True:
        page = await get_json(client, f"events/{event_id}/odds/snapshot", params)
        if first_page is None:
            first_page = page
        for odd in page.get("items") or []:
            if odd.get("id"):
                rows[str(odd["id"])] = odd
        cursor = page.get("next_cursor")
        if not cursor:
            break
        params["cursor"] = cursor

    # Start the stream at the first page's resume token. Changes that arrive
    # while later snapshot pages load can then be replayed by the stream.
    return first_page or {}, rows


async def sse_messages(response: httpx.Response) -> AsyncIterator[tuple[str, dict]]:
    event = "message"
    data: list[str] = []
    async for line in response.aiter_lines():
        if line == "":
            if data:
                yield event, json.loads("\n".join(data))
            event, data = "message", []
        elif line.startswith("event:"):
            event = line[6:].strip()
        elif line.startswith("data:"):
            data.append(line[5:].lstrip())
    if data:
        yield event, json.loads("\n".join(data))


def apply_delta(rows: dict[str, dict], payload: dict) -> None:
    for change in payload.get("changes") or []:
        odd = change.get("odd") or {}
        row_id = str(odd.get("id") or "")
        if not row_id:
            continue
        if change.get("op") in {"remove", "delete"}:
            rows.pop(row_id, None)
        elif change.get("op") == "upsert":
            # Keep is_available=false rows so the product can show suspensions.
            rows[row_id] = odd


async def follow_event(client: httpx.AsyncClient, event_id: str) -> None:
    resume = ""
    rows: dict[str, dict] = {}
    needs_snapshot = True
    retry_seconds = 1.0

    while True:
        try:
            if needs_snapshot:
                snapshot, rows = await complete_snapshot(client, event_id)
                resume = str(snapshot.get("resume") or "$")
                emit(
                    "odds_snapshot",
                    event_id=event_id,
                    as_of_ts_ms=snapshot.get("as_of_ts_ms"),
                    ttl_seconds=snapshot.get("ttl_seconds"),
                    resume=resume,
                    items=list(rows.values()),
                )
                needs_snapshot = False

            params = {"since": resume, "catchup": "true", "max_batch": 2000, **ODDS_SHAPE}
            async with client.stream(
                "GET", f"events/{event_id}/odds/stream", params=params,
                headers={"Accept": "text/event-stream"},
            ) as response:
                response.raise_for_status()
                retry_seconds = 1.0
                async for event, payload in sse_messages(response):
                    if event == "delta":
                        apply_delta(rows, payload)
                        resume = str(payload.get("resume") or resume)
                        emit("odds_delta", event_id=event_id, resume=resume, changes=payload.get("changes") or [])
                    elif event == "resync":
                        emit("odds_resync", event_id=event_id, reason=payload.get("reason"))
                        needs_snapshot = True
                        break
                    # A heartbeat contains no price change.

        except asyncio.CancelledError:
            raise
        except (httpx.HTTPError, ValueError, json.JSONDecodeError) as exc:
            emit("stream_error", event_id=event_id, error=str(exc))

        if needs_snapshot:
            continue
        await asyncio.sleep(retry_seconds + random.uniform(0, 0.5))
        retry_seconds = min(retry_seconds * 2, 30.0)


async def game_record(client: httpx.AsyncClient, event_id: str) -> None:
    try:
        detail, result = await asyncio.gather(
            get_json(client, f"events/{event_id}"),
            get_json(client, f"events/{event_id}/results"),
        )
        emit("event", event_id=event_id, data=detail)
        emit("result", event_id=event_id, data=result)
    except httpx.HTTPError as exc:
        emit("record_error", event_id=event_id, error=str(exc))


async def bounded_game_record(client: httpx.AsyncClient, semaphore: asyncio.Semaphore, event_id: str) -> None:
    async with semaphore:
        await game_record(client, event_id)


async def main() -> None:
    if not API_KEY:
        raise SystemExit("Set ODDS_API_KEY before running this example.")
    timeout = httpx.Timeout(connect=10.0, read=None, write=10.0, pool=10.0)
    tasks: dict[str, asyncio.Task] = {}
    record_limit = asyncio.Semaphore(6)
    async with httpx.AsyncClient(
        base_url=BASE_URL,
        headers={"X-API-Key": API_KEY, "Accept": "application/json"},
        timeout=timeout,
    ) as client:
        try:
            while True:
                try:
                    events = await nba_events(client)
                    ids = {str(item["event_id"]) for item in events if item.get("event_id")}
                    emit("event_list", count=len(events), items=events)

                    for event_id in ids:
                        if event_id not in tasks or tasks[event_id].done():
                            tasks[event_id] = asyncio.create_task(follow_event(client, event_id))
                    for event_id in set(tasks) - ids:
                        tasks.pop(event_id).cancel()

                    await asyncio.gather(
                        *(bounded_game_record(client, record_limit, event_id) for event_id in ids)
                    )
                except httpx.HTTPError as exc:
                    emit("discovery_error", error=str(exc))
                await asyncio.sleep(DISCOVERY_SECONDS)
        finally:
            for task in tasks.values():
                task.cancel()
            await asyncio.gather(*tasks.values(), return_exceptions=True)


if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("Stopped.", file=sys.stderr)
```

## NBA endpoint parameters

The parameter lists come from the current public OpenAPI document. The [full API reference](https://odds-api.net/docs) contains response schemas and other sports routes.

### GET Coverage `/v1/coverage`

Returns public bookmaker, sport, league, and recently observed market coverage. Market records are approximate and based on normalized odds lines seen in the configured lookback window, not a guarantee that every market is available for every event at request time.

### Query parameters

- `bookmaker`: string. Canonical bookmaker filter. Use `/bookmakers` or `/coverage` to discover supported keys.

- `sport`: string. Sport filter. Use `/sports` to discover supported values.

- `league`: string. League filter. Use `/leagues?sport=...` to discover supported values.

- `country_code`: string. Comma-separated country code filter, for example `AU` or `AU,UK`.

- `lookback_days`: integer. Number of days of recently observed approximate market coverage to include. Maximum is 90.

### GET Bookmakers `/v1/bookmakers`

Lists active bookmakers accepted by bookmaker filters across odds and betting endpoints. Each item includes the country codes where that bookmaker is available. Pass `country_code=AU` or `country_code=AU,UK` to filter the catalog.

### Query parameters

- `country_code`: string. Comma-separated country code filter, for example `AU` or `AU,UK`.

### GET Search `/v1/events`

Searches sports events by sport, league, team, time window, status, bookmaker coverage, and pagination cursor. For production polling, use bounded time windows, keep filters stable across pages, and pass `next_cursor` back as `cursor` until there is no next cursor. For live discovery start with `/v1/events/live`; `event_states=in_play` returns only events with a fresh live observation and adds a `hint` when started but unconfirmed events were excluded. Every item has a plain `live_status`.

### Query parameters

- `sport`: string. Sport filter. Use `/sports` to discover supported values.

- `league`: string. League filter. Use `/leagues?sport=...` to discover supported values.

- `start_from`: integer. Unix seconds lower bound for event start time. Use bounded windows in production polling.

- `start_to`: integer. Unix seconds upper bound for event start time. Keep windows narrow for hot sync jobs.

- `cursor`: string. Pagination cursor from the previous `next_cursor`. Keep filters identical between pages.

- `limit`: integer. Maximum items to return. Respect the caps returned by `/limits`.

- `include_bookmaker_ids`: boolean. When true, include bookmaker-to-odds-data IDs and preview ID maps on event responses.

- `include_source`: boolean. When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept.

- `include_opportunity_counts`: boolean.

- `not_started_only`: boolean.

- `not_started_buffer_seconds`: integer.

- `event_states`: string. Lifecycle filter. Requesting in_play automatically applies the live lookback window.

- `live_candidates`: boolean. Include already-started events that remain candidates for live play; this is not confirmation of current play.

### GET Event details `/v1/events/{event_id}`

Returns the current event record for a canonical sports event ID.

### Path parameters

- `event_id` (required): string. Canonical event or race identifier from an event list response.

### Query parameters

- `include_links`: boolean. When true, include bookmaker/deep-link fields such as match links and racing links.

- `include_raw_payload`: boolean. When true, include raw stored payload/data objects where the endpoint exposes them.

- `include_source`: boolean. When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept.

- `include_bookmaker_ids`: boolean. When true, include bookmaker-to-odds-data IDs and preview ID maps on event responses.

- `include_debug_ids`: boolean. When true, include internal/subgroup/opposing IDs useful for reconciliation.

### GET Event result `/v1/events/{event_id}/results`

Returns the latest known result for a sports event, or `pending` until settled. Poll every 1-5 minutes after start, then back off once the event is final.

### Path parameters

- `event_id` (required): string. Canonical event or race identifier from an event list response.

### GET Snapshot `/v1/events/{event_id}/odds/snapshot`

Returns the current odds lines for one event. Use filters to narrow bookmakers, market types, market keys, and periods. An explicit `bookmakers` filter returns every matching line for those bookmakers in one response, up to the 25,000-item safety cap. Without a bookmaker filter, pages contain whole bookmakers, so a bookmaker's matching markets are never split across pages. Follow the opaque `next_cursor` until `complete=true`. Cache by request shape. `as_of_ts_ms` is when the API accepted the latest successful event snapshot or authoritative bookmaker subset; use `bookmaker_as_of_ts_ms` for bookmaker-specific freshness and compare it with `target_refresh_interval_seconds`. Respect `ttl_seconds` when present, and persist `resume` when you plan to subscribe to updates. Pass `price_fields=odds,fair` to include nullable composite fair odds alongside bookmaker odds. Exchange orderbooks are excluded from this sportsbook-style odds surface.

### Path parameters

- `event_id` (required): string. Canonical event or race identifier from an event list response.

### Query parameters

- `limit`: integer. Soft item target when `bookmakers` is omitted. Pages contain whole bookmakers and may exceed this target. With an explicit bookmaker filter, all matching lines are returned up to the 25,000-item safety cap.

- `cursor`: string. Opaque bookmaker-page cursor from the previous `next_cursor`. Use only when `bookmakers` is omitted and keep all filters identical between pages.

- `bookmakers`: string. Comma-separated bookmaker allow-list. Explicitly requested bookmakers are returned complete in one response, up to the 25,000-item safety cap. Use `/bookmakers` to discover supported keys.

- `types`: string. Comma-separated market type allow-list for odds filters.

- `market_keys`: string. Comma-separated market key allow-list for odds filters.

- `periods`: string. Comma-separated period allow-list for odds filters.

- `price_fields`: string. `odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields.

- `include_source`: boolean. When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept.

- `include_debug_ids`: boolean. When true, include internal/subgroup/opposing IDs useful for reconciliation.

- `include_unavailable`: boolean. When true, include unavailable or suspended rows where the endpoint supports them.

### GET Stream (SSE) `/v1/events/{event_id}/odds/stream`

Server-Sent Events feed for odds changes on one event. Subscribe after reading the snapshot and pass the snapshot `resume` value as `since` to receive catch-up changes when available. Handle ordered semantic `delta` batches idempotently and persist each batch's `resume`; a `heartbeat` carries current freshness even when prices did not change. Reload the snapshot after `resync`. Exchange orderbook changes are served only from the exchange orderbook stream.

### Path parameters

- `event_id` (required): string. Canonical event or race identifier from an event list response.

### Query parameters

- `bookmakers`: string. Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys.

- `types`: string. Comma-separated market type allow-list for odds filters.

- `market_keys`: string. Comma-separated market key allow-list for odds filters.

- `periods`: string. Comma-separated period allow-list for odds filters.

- `price_fields`: string. `odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields.

- `include_source`: boolean. When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept.

- `include_debug_ids`: boolean. When true, include internal/subgroup/opposing IDs useful for reconciliation.

- `include_unavailable`: boolean. When true, include unavailable or suspended rows where the endpoint supports them.

- `since`: string. Resume token from a previous snapshot or stream message. Pass it after reconnecting.

- `catchup`: boolean. When true, return available missed stream events after `since` before waiting for new events.

- `heartbeat_sec`: integer. Heartbeat interval in seconds for stream liveness. Valid range is 5-120.

- `max_batch`: integer. Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients.

### GET Stream (WebSocket) `/v1/events/{event_id}/odds/ws`

WebSocket feed for odds changes on one event. Messages use the same `delta`, `heartbeat`, and `resync` payloads as the SSE stream. Reconnect with jittered exponential backoff and `since=<last_resume>`.

### Path parameters

- `event_id` (required): string. Canonical event or race identifier from an event list response.

### Query parameters

- `bookmakers`: string. Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys.

- `types`: string. Comma-separated market type allow-list for odds filters.

- `market_keys`: string. Comma-separated market key allow-list for odds filters.

- `periods`: string. Comma-separated period allow-list for odds filters.

- `price_fields`: string. `odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields.

- `include_source`: boolean. When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept.

- `include_debug_ids`: boolean. When true, include internal/subgroup/opposing IDs useful for reconciliation.

- `include_unavailable`: boolean. When true, include unavailable or suspended rows where the endpoint supports them.

- `since`: string. Resume token from a previous snapshot or stream message. Pass it after reconnecting.

- `catchup`: boolean. When true, return available missed stream events after `since` before waiting for new events.

- `heartbeat_sec`: integer. Heartbeat interval in seconds for stream liveness. Valid range is 5-120.

- `max_batch`: integer. Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients.

### GET Snapshot `/v1/events/{event_id}/odds/history`

Returns line movement for a single selection across bookmakers and a time range. Use `selection_key` from an odds snapshot response, bound queries with `from_ts` and `to_ts`, and narrow by bookmaker or market when building charts or backtests.

### Path parameters

- `event_id` (required): string. Canonical event or race identifier from an event list response.

### Query parameters

- `selection_key` (required): string. Stable selection identifier from an odds snapshot line, used for history and line movement.

- `market_group_id`: string. Optional market grouping filter for history queries.

- `bookmakers`: string. Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys.

- `from_ts`: string. ISO8601 UTC start timestamp for a bounded history query.

- `to_ts`: string. ISO8601 UTC end timestamp for a bounded history query.

- `price_type`: string. History price type to return, for example odds.

- `price_fields`: string. `odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields.

- `include_source`: boolean. When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept.

- `include_debug_ids`: boolean. When true, include internal/subgroup/opposing IDs useful for reconciliation.

- `include_unavailable`: boolean. When true, include unavailable or suspended rows where the endpoint supports them.

- `limit_points_per_bookmaker`: integer. Maximum history points per bookmaker. Use this to keep chart/backtest payloads bounded.

### GET Stream (SSE) `/v1/events/{event_id}/odds/history/stream`

Server-Sent Events feed for line movement on one selection. This is useful for charts that should update while an event market is moving.

### Path parameters

- `event_id` (required): string. Canonical event or race identifier from an event list response.

### Query parameters

- `selection_key` (required): string. Stable selection identifier from an odds snapshot line, used for history and line movement.

- `market_group_id`: string. Optional market grouping filter for history queries.

- `bookmakers`: string. Comma-separated bookmaker allow-list. Use `/bookmakers` to discover supported keys.

- `price_type`: string. History price type to return, for example odds.

- `price_fields`: string. `odds`, `odds,novig`, `odds,fair`, or `all`. Compact clients default to `odds`; existing clients default to current full pricing fields.

- `include_source`: boolean. When true, include per-line/per-bookmaker provenance and capture metadata. Top-level freshness fields are always kept.

- `include_debug_ids`: boolean. When true, include internal/subgroup/opposing IDs useful for reconciliation.

- `include_unavailable`: boolean. When true, include unavailable or suspended rows where the endpoint supports them.

- `since`: string. Resume token from a previous snapshot or stream message. Pass it after reconnecting.

- `catchup`: boolean. When true, return available missed stream events after `since` before waiting for new events.

- `heartbeat_sec`: integer. Heartbeat interval in seconds for stream liveness. Valid range is 5-120.

- `max_batch`: integer. Maximum stream messages to read per batch. Default is 500; use smaller batches for low-latency clients.

Need a narrower build? Start with the [NBA product overview](https://odds-api.net/sports/nba) or the [player props tutorial](https://odds-api.net/guides/nba-player-props-api-python).
