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

# Futures & Outrights

> List championship and tournament-winner boards, page through competitions, and keep futures prices fresh with the markets delta feed.

Futures (outrights) are markets on the outcome of an entire competition — who wins the Super Bowl, the Stanley Cup, or this week's PGA Tour event — rather than a single game. TheRundown serves them through a dedicated V2 endpoint, `GET /api/v2/sports/{sportID}/futures`, using the same market → participants → lines → prices shape as game odds.

<Note>
  Futures are in **early access**. The endpoint requires an **Ultra plan or higher** on API keys, and coverage is expanding — see [what's covered today](#sports-coverage). Requests on lower tiers return a `403` with an `upgrade_url` (see [Errors](/errors#403-forbidden)).
</Note>

## Competitions Are Intervals, Not Games

A futures event represents a **competition** — a span of time, not a point on the calendar. That changes three things compared to game events:

* **Two dates instead of one.** `event_date` is when the competition starts (opening kickoff of the NFL season, Thursday's first tee time), and `settle_by` is the settlement horizon — when the market must have graded (Super Bowl Sunday, the tournament's final round). An NBA championship board opens in October and settles in June.
* **A stable `event_id` for the whole competition.** The `event_id` is an opaque hash that stays constant from the day the board opens until settlement. Cache it — it is the public handle for the competition across snapshots and the delta feed.
* **No `teams`, no `score`.** A competition has no away/home sides, so futures events carry no `teams` array and no score block. The human-readable identity lives in `schedule.event_name` (e.g. `"NFL Super Bowl Winner (2026 Season)"`).

Here is a real (trimmed) NFL championship event:

```json theme={null}
{
  "event_id": "a3d1f9f94f220a45cfd944181569cc46",
  "sport_id": 2,
  "event_date": "2026-09-10T00:00:00Z",
  "settle_by": "2027-02-21T00:00:00Z",
  "event_status": "STATUS_SCHEDULED",
  "settlement": {},
  "schedule": {
    "event_name": "NFL Super Bowl Winner (2026 Season)",
    "season_year": 2026
  },
  "markets": [
    {
      "id": 4967891,
      "market_id": 1141,
      "period_id": 0,
      "name": "tournament_winner",
      "market_description": "Tournament Winner",
      "participants": [
        {
          "id": 61,
          "type": "TYPE_TEAM",
          "name": "Buffalo Bills",
          "lines": [
            {
              "id": "8155509548895b87157633c87df81b3e",
              "prices": {
                "22": { "id": "665502535", "price": 1000, "is_main_line": true, "updated_at": "2026-07-29T17:01:46Z" },
                "25": { "id": "665857721", "price": 1074, "is_main_line": true, "updated_at": "2026-07-29T18:30:25Z" }
              }
            }
          ]
        }
      ]
    }
  ]
}
```

<Note>
  Season-long team competitions currently report `event_status: "STATUS_SCHEDULED"` for the life of the board — live in-progress status for team seasons is not yet populated on `event_status`, so do not build logic on it. Golf tournaments do transition (`STATUS_SCHEDULED` → `STATUS_IN_PROGRESS` → `STATUS_FINAL`) because each competition spans only a few days. For a season board's live standing, read the [`progress`](#competition-stats-progress) block on the single-competition endpoint instead — its `phase`/`entries` update throughout the season even while `event_status` stays `STATUS_SCHEDULED`.
</Note>

## Reading the Board

Each market is a grid of **participants × sportsbooks**. Every participant (a team, or a golfer — golfers use `TYPE_TEAM` like UFC fighters) carries one line whose `prices` object is keyed by affiliate ID. Prices are American odds, so championship longshots run large: `+50000` is a real price on a 500/1 team.

```bash theme={null}
curl "https://therundown.io/api/v2/sports/2/futures?key=YOUR_API_KEY&affiliate_ids=22,25"
```

To render an odds board, walk `markets[].participants[]` and read each participant's `lines[0].prices`:

```python theme={null}
import requests

resp = requests.get(
    "https://therundown.io/api/v2/sports/2/futures",
    headers={"X-TheRundown-Key": "YOUR_API_KEY"},
    params={"affiliate_ids": "22,25"},
)

for event in resp.json()["events"]:
    print(event["schedule"]["event_name"])
    for market in event["markets"]:
        print(f"  {market['market_description']} (market {market['market_id']})")
        for p in market["participants"]:
            prices = p["lines"][0]["prices"]
            quotes = ", ".join(
                f"book {aff}: {q['price']:+}" for aff, q in sorted(prices.items())
            )
            print(f"    {p['name']}: {quotes}")
```

A few field-level notes:

* **Snapshot `price` values are numbers** (e.g. `1000` for +1000). The markets delta feed serializes `price` and `previous_price` as strings, so normalize them deliberately when applying deltas. Treat snapshot value `0.0001` as off the board, as everywhere else in the API — see [Sentinel Values](/reference/sentinel-values).
* **A price with `closed_at` set** is no longer offered by that book (the golfer missed the cut, the book pulled the number). It is included so you can show the last traded price; filter on `closed_at` absence for a live board.
* **Join on `participants[].id`**, not `name` — it is the stable team/golfer identifier.

## The Date Window: Interval Overlap

Because competitions are intervals, `from`/`to` filter by **overlap**, not by start date. An event is returned when its `[event_date, settle_by]` span intersects your window: `settle_by >= from` and `event_date <= to`.

| You want                            | Request                                               | Why it works                                                                                                     |
| ----------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Everything in flight right now      | `GET /api/v2/sports/6/futures`                        | `from` defaults to **now**; a season future that started months ago still matches because it has not settled yet |
| Competitions that settle this month | `?from=2026-08-01&to=2026-08-31&offset=300`           | `settle_by` on or after the 1st, started by the 31st                                                             |
| Settled history for a past window   | `?include_settled=true&from=2026-07-01&to=2026-07-25` | see below                                                                                                        |

Two behaviors worth internalizing:

* **`from=today` is not "starts today".** It means "has not finished yet" — that is what makes the default listing "active boards" work.
* **`include_settled=true` also lifts the default `from=now`.** Settled competitions' intervals ended in the past, so a default `from` of now would hide exactly the history you asked for. With `include_settled=true` you get settled markets, terminal events, and an unbounded window start — constrain it with explicit `from`/`to` if you only want a slice.

Date-only values (`YYYY-MM-DD`) are interpreted in the timezone given by `offset` (minutes from UTC, e.g. `300` for US Central) — the [same convention](/guides/efficient-polling#shrink-every-response-first) as the game endpoints.

## Paging with Cursors

Futures paging is **keyset-based** — there is no `skip`/page-number parameter. When more results exist, the response carries `meta.next_cursor`; pass it back verbatim as `cursor`. A response without `next_cursor` is the last page.

```python theme={null}
def fetch_all_futures(sport_id, **params):
    events, cursor = [], None
    while True:
        if cursor:
            params["cursor"] = cursor
        resp = requests.get(
            f"https://therundown.io/api/v2/sports/{sport_id}/futures",
            headers={"X-TheRundown-Key": "YOUR_API_KEY"},
            params=params,
        )
        data = resp.json()
        events.extend(data["events"])
        cursor = data.get("meta", {}).get("next_cursor")
        if not cursor:
            return events
```

The token is opaque — never parse or construct one. Ordering is deterministic: soonest-to-resolve first (`settle_by`, then `event_date`, then `event_id`), so the first page is always the boards closest to settlement. `limit` defaults to 50 and is capped at 200.

## Keeping Prices Fresh: Snapshot → Delta

Futures price changes flow through the same [`/api/v2/markets/delta`](/api-reference/generated/v2-markets/get-market-price-changes-since-a-given-id) feed as game odds, and the futures snapshot hands you the bootstrap cursor directly in `meta.delta_last_id`.

<Warning>
  Futures market IDs are **not in the delta feed's default market set**. If you poll `/api/v2/markets/delta` without `market_ids`, you will receive game-market changes only and silently miss every futures move. Always pass the futures IDs explicitly, e.g. `market_ids=1141`.
</Warning>

The recipe:

1. **Snapshot** — `GET /api/v2/sports/{sportID}/futures`, store the board, save `meta.delta_last_id`.
2. **Poll** — `GET /api/v2/markets/delta?market_ids=1141&last_id=<saved>`. Apply each delta row to your board, then save the response's `meta.delta_last_id` for the next poll.
3. **Chain the cursor every poll, even when `deltas` is empty.** The returned watermark advances to the head of the feed regardless of whether any rows matched your filter — an empty response is cheap and keeps your cursor inside the validity window.

Delta rows for futures look exactly like game-market deltas. Unlike numeric snapshot prices, their `price` and `previous_price` values are decimal strings:

```json theme={null}
{
  "id": 1595215220,
  "event_id": "7685a32da628c77e031a2d9701128882",
  "sport_id": 40,
  "affiliate_id": 19,
  "market_id": 1141,
  "market_name": "tournament_winner",
  "participant_id": 139667,
  "participant_type": "TYPE_TEAM",
  "participant_name": "Stephan Jaeger",
  "line": "",
  "price": "8000.0000",
  "previous_price": "7800.0000",
  "change_type": "price_change",
  "updated_at": "2026-08-01T14:39:44Z",
  "is_main_line": true
}
```

Match rows to your cached board by `(event_id, market_id, participant_id, affiliate_id)`.

A complete worked loop:

```python theme={null}
import requests
import time

API_KEY = "YOUR_API_KEY"
BASE = "https://therundown.io/api/v2"
SPORT_ID = 40           # Golf
FUTURES_MARKET_IDS = "1141"

boards = {}             # event_id -> event

def snapshot():
    """Load all futures boards and return the delta cursor."""
    cursor = None
    watermark = None
    while True:
        params = {"market_ids": FUTURES_MARKET_IDS}
        if cursor:
            params["cursor"] = cursor
        resp = requests.get(
            f"{BASE}/sports/{SPORT_ID}/futures",
            headers={"X-TheRundown-Key": API_KEY},
            params=params,
        )
        data = resp.json()
        for event in data["events"]:
            boards[event["event_id"]] = event
        watermark = data["meta"]["delta_last_id"]
        cursor = data["meta"].get("next_cursor")
        if not cursor:
            print(f"Loaded {len(boards)} boards, watermark={watermark}")
            return watermark

def poll(last_id):
    """Apply futures price changes since last_id. Returns the new cursor."""
    resp = requests.get(
        f"{BASE}/markets/delta",
        headers={"X-TheRundown-Key": API_KEY},
        params={"last_id": last_id, "market_ids": FUTURES_MARKET_IDS},
    )
    if resp.status_code != 200:
        return None                     # stale cursor -> re-snapshot
    data = resp.json()
    for d in data.get("deltas", []):
        print(f"{d['participant_name']}: {d['previous_price']} -> {d['price']} "
              f"(book {d['affiliate_id']})")
    return data["meta"]["delta_last_id"]  # always chain, even on zero rows

cursor = snapshot()
while True:
    time.sleep(30)
    cursor = poll(cursor) or snapshot()
```

<Warning>
  Delta cursors do not have a fixed wall-clock lifetime. A cursor is rejected with an HTTP `400` when it falls too far behind the current head; the guard is based on intervening update volume across the platform. On a `400`, re-snapshot the futures endpoint and resume from its fresh `meta.delta_last_id`. Poll every 30 seconds to a few minutes and chain the returned cursor on every response. Empty responses are nearly free and advance your cursor to the head of the stream.
</Warning>

## Streaming Futures over WebSocket

Futures also stream natively on the [multiplexed WebSocket](/guides/multiplexed-websocket). The dedicated `futures` channel carries price updates for competition boards only — game odds never appear on it — and supports the same snapshot → delta bootstrap as the REST recipe above, over one connection:

```json theme={null}
{
  "action": "subscribe",
  "id": "golf-futures",
  "channel": "futures",
  "params": { "sport_ids": [40], "market_ids": [1141], "snapshot": true }
}
```

After the `subscribed` ack, the server replays the REST futures listing as one `snapshot` frame per sport — the same `{meta, events}` body as `GET /api/v2/sports/{sportID}/futures`, including each board's `settlement` state as of the snapshot — then sends `snapshot_complete` and begins live `market_price` deltas. The `snapshot_complete` frame carries `delta_last_id`, so you can fall back to REST delta polling at any point without a gap.

All filters (`sport_ids`, `event_ids`, `market_ids`, `affiliate_ids`) are optional for live frames — an unfiltered `futures` subscription streams every futures move on the platform. Snapshot requests are stricter because they replay the sport-scoped REST endpoints:

* `snapshot: true` requires positive `sport_ids`, with **no** `date` — boards are long-lived intervals, not dated fixtures.
* Adding `event_ids` narrows the snapshot to specific boards (via the single-competition endpoint) and requires exactly one sport in `sport_ids`.
* An active subscription can re-snapshot at any time with `{ "action": "snapshot", "id": "golf-futures" }`. The recovery variant `{ "action": "resync", "id": "golf-futures" }` does the same and additionally confirms with a `resync_ack` frame once the fresh snapshot is delivered and live deltas resume — use it when you suspect you missed frames.

Entitlement matches REST: **Ultra plan or higher for API keys, Pro or higher for web sessions**. Subscribing without it returns an `error` frame with code `forbidden`; a transient `futures_unavailable` means the server is still warming its competition index — retry shortly.

### Futures on the `markets` channel

A `markets` subscription stays game-only by default — the WebSocket twin of the delta feed's default market set. Competition frames flow on it only when the subscription opts in explicitly, by naming future-class `market_ids` (e.g. `[1141]`) or competition `event_ids` in its filters; sport-scoped filters alone never opt in. The opt-in affects **live frames only**: `markets` snapshots (`sport_ids` plus `date`) never include competition boards. If you want board state plus deltas, use the `futures` channel; if you only need futures moves merged into an existing game-odds stream, add the market IDs to your filters.

## Futures Market IDs

| Market                             | ID   | Sports             | Notes                                                                       |
| ---------------------------------- | ---- | ------------------ | --------------------------------------------------------------------------- |
| Tournament Winner                  | 1141 | All futures sports | Championship / outright winner. The one market every futures sport carries. |
| Top 5 Finish                       | 1392 | Golf               | Finish position 1–5, ties included                                          |
| Top 10 Finish                      | 1393 | Golf               | Finish position 1–10, ties included                                         |
| Top 20 Finish                      | 1394 | Golf               | Finish position 1–20, ties included                                         |
| Make The Cut                       | 1395 | Golf               | Yes/No per golfer, over/under-carried at line 0.5                           |
| First Round Leader                 | 1396 | Golf               | Leader after round 1; dead-heat rules on ties                               |
| Heisman Trophy                     | 1400 | NCAAF              | Season award, player-graded                                                 |
| NFL MVP                            | 1401 | NFL                | Season award, player-graded                                                 |
| NBA MVP                            | 1402 | NBA                | Season award, player-graded                                                 |
| MLB AL MVP                         | 1403 | MLB                | Season award, player-graded                                                 |
| MLB NL MVP                         | 1404 | MLB                | Season award, player-graded                                                 |
| NFL Offensive Rookie of the Year   | 1405 | NFL                | Season award, player-graded                                                 |
| NFL Defensive Rookie of the Year   | 1406 | NFL                | Season award, player-graded                                                 |
| NFL Comeback Player of the Year    | 1407 | NFL                | Season award, player-graded                                                 |
| NBA Rookie of the Year             | 1408 | NBA                | Season award, player-graded                                                 |
| NBA Defensive Player of the Year   | 1409 | NBA                | Season award, player-graded                                                 |
| NBA Sixth Man of the Year          | 1410 | NBA                | Season award, player-graded                                                 |
| MLB AL Cy Young                    | 1411 | MLB                | Season award, player-graded                                                 |
| MLB NL Cy Young                    | 1412 | MLB                | Season award, player-graded                                                 |
| MLB NL Comeback Player of the Year | 1413 | MLB                | Season award, player-graded                                                 |
| MLB AL Rookie of the Year          | 1414 | MLB                | Season award, player-graded                                                 |
| MLB NL Rookie of the Year          | 1415 | MLB                | Season award, player-graded                                                 |
| MLB AL Comeback Player of the Year | 1416 | MLB                | Season award, player-graded                                                 |

`market_ids` on the futures endpoint is intersected with the futures market set — passing game-market IDs there yields events with empty `markets` arrays (the event list itself still returns). In the other direction, remember that these IDs must be passed **explicitly** on `/api/v2/markets/delta`.

## Sports Coverage

Futures are live for the following sports. The number of sportsbooks pricing each board varies by sport while early access expands — check the `prices` keys on a live board rather than assuming a fixed set:

| Sport           | ID | What runs                                                             |
| --------------- | -- | --------------------------------------------------------------------- |
| NFL             | 2  | Super Bowl winner (one board per season)                              |
| MLB             | 3  | World Series winner                                                   |
| NCAAF           | 1  | National championship winner                                          |
| NHL             | 6  | Stanley Cup winner                                                    |
| NBA             | 4  | NBA championship winner                                               |
| NCAAB           | 5  | National championship winner                                          |
| WNBA            | 8  | WNBA championship winner                                              |
| EPL             | 11 | Premier League winner                                                 |
| Golf (PGA Tour) | 40 | One competition per tournament — winner plus the entry markets above  |
| Formula 1       | 41 | Two season competitions: Drivers' Champion and Constructors' Champion |

Team sports run one championship competition per season. Golf runs a competition per tournament, so expect a rolling list of upcoming events rather than a single board. F1 carries the two season championships only — no per-race markets.

## Settlement Lifecycle

A board's life is **open → trade → settle**. The `settlement` object tracks it per market, keyed by market ID:

* A fresh board's `settlement` is empty (`{}`) — a market with **no entry has not entered grading**. Entries appear as the competition nears resolution, carrying a pre-grading status: an in-progress golf tournament shows `{ "1141": { "status": "pending" } }`.
* Once graded, the entry becomes `"status": "settled"` with `settled_at`, and — for winner-shaped markets — `winning_line` (the winner's canonical name) and `winning_participant_id`. Entry markets like Top 10 or Make The Cut grade per participant, so they settle without a single `winning_line`.
* `"settled"` is the only terminal value. Treat any other `status` as "grading in progress" — the vocabulary may grow.

A real settled golf competition:

```json theme={null}
{
  "event_id": "f322ba0f4c5993c1f827a1e1b8dbae60",
  "sport_id": 40,
  "event_date": "2026-07-23T04:00:00Z",
  "settle_by": "2026-07-26T04:00:00Z",
  "event_status": "STATUS_FINAL",
  "settlement": {
    "1141": {
      "status": "settled",
      "winning_line": "Jackson Koivun",
      "winning_participant_id": 139644,
      "settled_at": "2026-07-29T02:37:50Z"
    },
    "1392": { "status": "settled", "settled_at": "2026-07-30T01:41:54Z" },
    "1393": { "status": "settled", "settled_at": "2026-07-30T01:41:54Z" },
    "1394": { "status": "settled", "settled_at": "2026-07-30T01:41:54Z" },
    "1395": { "status": "settled", "settled_at": "2026-07-30T01:41:54Z" },
    "1396": { "status": "settled", "settled_at": "2026-07-30T01:41:54Z" }
  },
  "schedule": {
    "event_name": "3M Open",
    "season_year": 2026,
    "league_name": "PGA Tour"
  }
}
```

Settlement changes visibility:

* A **settled market** disappears from the default listing (its prices and settlement entry are removed per market).
* An event where **every requested market has settled** — or whose status is terminal (final/canceled/abandoned) — disappears entirely.
* `include_settled=true` restores all of it, including the graded `settlement` blocks, which is how you fetch results after the fact.

## Fetching One Competition

Every entry in a futures listing carries an `event_id`. Fetch that one competition directly with [`GET /api/v2/sports/{sportID}/futures/{eventID}`](/api-reference/generated/v2-futures/get-one-futures-competition) — the same event shape as the listing, plus up to three optional detail-only keys covered below: `progress`, `leaders`, and `player_leaders`.

```bash theme={null}
curl "https://therundown.io/api/v2/sports/40/futures/f322ba0f4c5993c1f827a1e1b8dbae60?key=YOUR_API_KEY"
```

<Note>
  This endpoint always serves the competition, even after it settles. The listing hides settled markets and terminal events by default, but a detail fetch is a deep link to a competition you already hold the ID for, and it keeps working after settlement — graded results stay visible in `settlement`. There is no `include_settled` parameter here. An unknown `event_id`, an ID from a different sport, or a game (non-competition) event ID returns a `404`.
</Note>

## Competition Stats (Progress)

Some competitions carry a live, non-odds status document — a leaderboard or a standings table — alongside the price board. It rides in the optional `progress` key on the single-competition endpoint above; the listing endpoint does not carry it.

<Note>
  `progress` is **omitted entirely** (never `null`) when no progress document exists yet for the competition. "Priced, no progress" is the normal state for a fresh team-championship board — not a degraded response — so check for the key's presence rather than assuming it is always there.
</Note>

Every `progress` document shares one envelope:

```json theme={null}
{
  "kind": "stroke_play_leaderboard",
  "as_of": "2026-07-25T18:42:00Z",
  "phase": { "status": "STATUS_IN_PROGRESS", "label": "Round 3 of 4", "unit": "round", "current": 3, "total": 4 },
  "context": { "...": "kind-specific" },
  "entries": [ { "...": "kind-specific, ranked" } ]
}
```

* **`kind`** picks the shape of `context` and `entries` below. Two kinds are live today: `stroke_play_leaderboard` (golf) and `league_standings` (team-sport championships).
* **`phase`** is the same across every kind: `status` uses the same vocabulary as `event_status`, `label` is a ready-to-display phrase (never raw upstream text), and `current`/`total` are `null` when there is no meaningful position — a standings table between games has a `total` but no `current`.
* **`entries[].participant_id`** is always the SAME participant ID space `markets[].participants[].id` uses. Joining a progress row directly to a price is the whole point of the block.

### `stroke_play_leaderboard` (golf)

A round-3 leaderboard for the same 3M Open shown earlier in [Settlement Lifecycle](#settlement-lifecycle) — Jackson Koivun leads here and goes on to win:

```json theme={null}
{
  "progress": {
    "kind": "stroke_play_leaderboard",
    "as_of": "2026-07-25T18:42:00Z",
    "phase": { "status": "STATUS_IN_PROGRESS", "label": "Round 3 of 4", "unit": "round", "current": 3, "total": 4 },
    "context": { "cut_score": -4, "cut_count": 68, "purse": 8400000, "round": 3, "round_state": "in_progress" },
    "entries": [
      { "participant_id": 139644, "name": "Jackson Koivun", "position": "1", "tied": false, "score_to_par": -14, "today": -4, "thru": 12, "movement": 1, "country": "USA", "total_strokes": 199, "round_state": "active" },
      { "participant_id": 139667, "name": "Stephan Jaeger", "position": "2", "tied": false, "score_to_par": -12, "today": -2, "thru": 14, "movement": -1, "country": "GER", "total_strokes": 201, "round_state": "active" }
    ]
  }
}
```

`context` is round-level state shared by every golfer: `cut_score` and `cut_count` (once the cut is made), `purse`, and the current `round`/`round_state`. Each `entries[]` row is one golfer: `position` (`"1"`; ties render like `"T2"` — check `tied`), `score_to_par`/`today` relative to par, `thru` (holes completed this round, `"F"` once finished, or an ISO-8601 tee time before the round starts), `status` when a golfer is cut or withdraws (`active`/`cut`/`wd`), `total_strokes`, `movement` (position change since the prior update), and `country`.

### `league_standings` (team-sport championships)

A mid-season NFL standings snapshot, on the same event as the moneyline board at the top of this guide:

```json theme={null}
{
  "progress": {
    "kind": "league_standings",
    "as_of": "2026-08-07T09:05:00Z",
    "phase": { "status": "STATUS_IN_PROGRESS", "label": "Week 14", "unit": "week", "current": 14, "total": 18 },
    "context": {},
    "entries": [
      { "participant_id": 61, "name": "Buffalo Bills", "rank": 1, "wins": 10, "losses": 3, "ties": 0, "win_pct": 0.769, "points": 0, "conference": "AFC", "division": "AFC East", "seed": 1, "streak": "W3" },
      { "participant_id": 63, "name": "New England Patriots", "rank": 3, "wins": 7, "losses": 6, "ties": 0, "win_pct": 0.538, "points": 0, "conference": "AFC", "division": "AFC East", "seed": 6, "streak": "L1" }
    ]
  }
}
```

Core fields (`participant_id`, `name`, `rank`, `wins`, `losses`, `ties`, `win_pct`, `points`) are always present; sport-specific fields — `conference`, `division`, `group_rank`, `seed`, `games_behind`, `streak`, `clinch`, `points_for`, `points_against`, `home_record`, `away_record`, `last_ten`, and (soccer) `position`, `goal_difference`, `goals_for`, `goals_against` — appear only when applicable. `rank` is league-wide, not scoped to a conference or division.

## Leaders: Curated Season-Stat Leaderboards

Two more optional detail-only keys on the single-competition endpoint, `leaders` and `player_leaders`, surface a small, hand-verified set of season-stat leaderboards alongside the odds board — team-grain and player-grain respectively. Both share one shape:

```json theme={null}
{
  "stat": "Total Yards",
  "stat_id": 1120,
  "season_year": 2025,
  "entries": [
    { "participant_id": 61, "name": "Buffalo Bills", "value": 6432, "rank": 1 },
    { "participant_id": 63, "name": "New England Patriots", "value": 5211, "rank": 2 }
  ]
}
```

* **`stat`** is our own curated display name — never an upstream stat's raw field or column name. **`stat_id`** is our internal identifier, exposed the same way `market_id` is elsewhere. **`season_year`** is the most recent season with data, resolved per request rather than hardcoded.
* **`entries`** holds the top 5 ranked rows. Most stats rank highest-value-first; a few (ERA, goals-against average) rank the *lowest* value as `rank: 1` — check the stat before assuming direction.

**`leaders`** (team-grain) is competition-scoped: `entries[].participant_id` is the SAME participant ID space `progress` and `markets[].participants[].id` use. Curated today for NFL, NCAAF, NBA, WNBA, NCAAB, MLB, NHL, MLS, EPL, Ligue 1, Bundesliga, La Liga, and Serie A. Win/loss/tie records and standings points are never sourced here — that is `progress`'s `league_standings` job.

**`player_leaders`** (player-grain) differs in two ways: it is **league-wide, not competition-scoped** (player season stats carry no team linkage, so entries are the sport's overall leaders for the labeled season, not just this competition's two teams), and `entries[].participant_id` is the **player** ID space — the same normalized player IDs player-prop market participants use, not the team ID space `leaders`/`progress` share. Curated today for NFL, NCAAF, NBA, WNBA, NCAAB, MLB, and NHL; soccer is excluded (no reliable per-player goals/assists season data upstream).

```json theme={null}
{
  "player_leaders": [
    {
      "stat": "Passing Yards",
      "stat_id": 1101,
      "season_year": 2025,
      "entries": [
        { "participant_id": 15847, "name": "Josh Allen", "value": 4306, "rank": 1 }
      ]
    }
  ]
}
```

<Note>
  Both `leaders` and `player_leaders` are **omitted entirely** (never an empty array) when the sport has no curated stat set, the board has no resolvable team participant, or no season-stat rows exist yet — the same omission contract as `progress`.
</Note>

## Common Pitfalls

<AccordionGroup>
  <Accordion title="The progress key is missing from my response">
    `progress` is omitted entirely — never present as `null` — when no progress document exists yet for that competition. This is normal for a freshly opened board or a sport without a collector yet, not an error. Check `leaders`/`player_leaders` and the odds board in the meantime, and poll again later.
  </Accordion>

  <Accordion title="My delta poll returns game odds but never futures moves">
    Futures market IDs are not in the delta feed's default set. Pass them explicitly: `market_ids=1141` (add the golf and award IDs if you track them). This is the single most common futures integration bug.
  </Accordion>

  <Accordion title="My markets WebSocket subscription never shows futures moves">
    Same rule as the delta feed: a `markets` subscription is game-only unless it explicitly names future-class `market_ids` or competition `event_ids` — sport-scoped filters alone never opt in. Subscribe to the [`futures` channel](#streaming-futures-over-websocket) for board snapshots plus deltas, or add the futures market IDs to your existing filters.
  </Accordion>

  <Accordion title="My delta cursor gets rejected with a 400">
    Your cursor fell too far behind the current head. The guard is based on intervening update volume, not a fixed wall-clock expiry. Re-snapshot the futures endpoint, take the fresh `meta.delta_last_id`, and resume. To avoid it, poll every 30 seconds to a few minutes and chain the returned cursor on every response — including empty ones, which still advance the watermark to head.
  </Accordion>

  <Accordion title="A championship that started months ago shows up under from=today">
    Working as designed: the date window filters by **interval overlap**, and a season future that started in September still overlaps today because it has not settled. `from` bounds `settle_by` ("has not finished"), `to` bounds `event_date` ("has started by").
  </Accordion>

  <Accordion title="include_settled=true returns far more history than I expected">
    `include_settled=true` also lifts the implicit `from=now` — otherwise settled history (whose intervals ended in the past) would be unreachable. Add explicit `from`/`to` bounds to scope the history you want.
  </Accordion>

  <Accordion title="Yesterday's tournament board vanished from the default listing">
    Settled markets and terminal events are hidden by default. The board did not disappear — fetch it with `include_settled=true` to see the graded `settlement` block, including the winner.
  </Accordion>

  <Accordion title="event_status never leaves STATUS_SCHEDULED on a season board">
    Expected for team-sport season competitions today — live status transitions for season-long boards are not yet populated. Use `settle_by` and the `settlement` block, not `event_status`, to reason about where a season board stands.
  </Accordion>
</AccordionGroup>

## Billing

Futures responses are metered as data points like every other odds endpoint — the standard usage headers (`X-Datapoints`, `X-Datapoints-Used`, `X-Datapoints-Remaining`) apply, and your plan's data delay applies to futures prices as well. Filter with `affiliate_ids` and `market_ids` to keep snapshot costs down, and lean on the delta feed for updates. WebSocket futures work the same way: snapshot frames bill exactly like the REST reads they replay, live frames bill per data point, and futures usage appears under its own category in your account's request log. See [Rate Limits](/rate-limits) and the [Efficient Polling guide](/guides/efficient-polling).
