GET /api/v2/sports/{sportID}/futures, using the same market → participants → lines → prices shape as game odds.
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. Requests on lower tiers return a
403 with an upgrade_url (see Errors).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_dateis when the competition starts (opening kickoff of the NFL season, Thursday’s first tee time), andsettle_byis 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_idfor the whole competition. Theevent_idis 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, noscore. A competition has no away/home sides, so futures events carry noteamsarray and no score block. The human-readable identity lives inschedule.event_name(e.g."NFL Super Bowl Winner (2026 Season)").
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 block on the single-competition endpoint instead — its phase/entries update throughout the season even while event_status stays STATUS_SCHEDULED.Reading the Board
Each market is a grid of participants × sportsbooks. Every participant (a team, or a golfer — golfers useTYPE_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.
markets[].participants[] and read each participant’s lines[0].prices:
- Snapshot
pricevalues are numbers (e.g.1000for +1000). The markets delta feed serializespriceandprevious_priceas strings, so normalize them deliberately when applying deltas. Treat snapshot value0.0001as off the board, as everywhere else in the API — see Sentinel Values. - A price with
closed_atset 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 onclosed_atabsence for a live board. - Join on
participants[].id, notname— 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.
Two behaviors worth internalizing:
from=todayis not “starts today”. It means “has not finished yet” — that is what makes the default listing “active boards” work.include_settled=truealso lifts the defaultfrom=now. Settled competitions’ intervals ended in the past, so a defaultfromof now would hide exactly the history you asked for. Withinclude_settled=trueyou get settled markets, terminal events, and an unbounded window start — constrain it with explicitfrom/toif you only want a slice.
YYYY-MM-DD) are interpreted in the timezone given by offset (minutes from UTC, e.g. 300 for US Central) — the same convention as the game endpoints.
Paging with Cursors
Futures paging is keyset-based — there is noskip/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.
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 feed as game odds, and the futures snapshot hands you the bootstrap cursor directly in meta.delta_last_id.
The recipe:
- Snapshot —
GET /api/v2/sports/{sportID}/futures, store the board, savemeta.delta_last_id. - Poll —
GET /api/v2/markets/delta?market_ids=1141&last_id=<saved>. Apply each delta row to your board, then save the response’smeta.delta_last_idfor the next poll. - Chain the cursor every poll, even when
deltasis 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.
price and previous_price values are decimal strings:
(event_id, market_id, participant_id, affiliate_id).
A complete worked loop:
Streaming Futures over WebSocket
Futures also stream natively on the multiplexed WebSocket. The dedicatedfutures 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:
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: truerequires positivesport_ids, with nodate— boards are long-lived intervals, not dated fixtures.- Adding
event_idsnarrows the snapshot to specific boards (via the single-competition endpoint) and requires exactly one sport insport_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 aresync_ackframe once the fresh snapshot is delivered and live deltas resume — use it when you suspect you missed frames.
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_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 theprices keys on a live board rather than assuming a fixed set:
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. Thesettlement object tracks it per market, keyed by market ID:
- A fresh board’s
settlementis 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"withsettled_at, and — for winner-shaped markets —winning_line(the winner’s canonical name) andwinning_participant_id. Entry markets like Top 10 or Make The Cut grade per participant, so they settle without a singlewinning_line. "settled"is the only terminal value. Treat any otherstatusas “grading in progress” — the vocabulary may grow.
- 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=truerestores all of it, including the gradedsettlementblocks, which is how you fetch results after the fact.
Fetching One Competition
Every entry in a futures listing carries anevent_id. Fetch that one competition directly with GET /api/v2/sports/{sportID}/futures/{eventID} — the same event shape as the listing, plus up to three optional detail-only keys covered below: progress, leaders, and player_leaders.
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.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 optionalprogress key on the single-competition endpoint above; the listing endpoint does not carry it.
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.progress document shares one envelope:
kindpicks the shape ofcontextandentriesbelow. Two kinds are live today:stroke_play_leaderboard(golf) andleague_standings(team-sport championships).phaseis the same across every kind:statususes the same vocabulary asevent_status,labelis a ready-to-display phrase (never raw upstream text), andcurrent/totalarenullwhen there is no meaningful position — a standings table between games has atotalbut nocurrent.entries[].participant_idis always the SAME participant ID spacemarkets[].participants[].iduses. 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 — Jackson Koivun leads here and goes on to win:
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:
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:
statis our own curated display name — never an upstream stat’s raw field or column name.stat_idis our internal identifier, exposed the same waymarket_idis elsewhere.season_yearis the most recent season with data, resolved per request rather than hardcoded.entriesholds the top 5 ranked rows. Most stats rank highest-value-first; a few (ERA, goals-against average) rank the lowest value asrank: 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).
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.Common Pitfalls
The progress key is missing from my response
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.My delta poll returns game odds but never futures moves
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.My markets WebSocket subscription never shows futures moves
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 for board snapshots plus deltas, or add the futures market IDs to your existing filters.My delta cursor gets rejected with a 400
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.A championship that started months ago shows up under from=today
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”).include_settled=true returns far more history than I expected
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.Yesterday's tournament board vanished from the default listing
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.event_status never leaves STATUS_SCHEDULED on a season board
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.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 and the Efficient Polling guide.