Skip to content
Product Documentation

closing-lines

One row per (game, operator, market, selection, side): where the side opened, where it closed, how much it moved, and book-only CLV. Built from change-only odds records observed from scrape start through game end. One row per priced selection id per side — ≈ 12,000 rows for an MLB game with full props coverage — the compact, analysis-ready summary of a game’s odds; reach for sportsbook-lines when you need every intermediate move.

Terminal window
curl -s "https://api.betflux.ai/v1/games/NBA_ORL_DET_20260503/closing-lines" \
-H "Authorization: Bearer $BETFLUX_API_KEY" -o closing.parquet

One Parquet file per game. Date-range queries are a client-side loop over /v1/games — the CLI and SDK do it for you, with local filters (operator, market_type, team, player_id, side):

Terminal window
betflux get closing-lines --league NBA \
--date-from 2026-05-01 --date-to 2026-05-07 --operator FANDUEL
  • One row per selection id per side: market_key (= OPERATOR:market_external_id) and selection_external_id identify the market and selection by the operator’s own ids, so two markets or two players that share a name never share a row — see Market identity.
  • Books that mint a new selection id per line value (DraftKings, BetMGM, Pinnacle, FanDuel alternates) show a moved line as a new row whose opening equals its closing. To get DraftKings’ main-line close for a side, take the row with the latest closing_ts among the market’s rows for that side — a heuristic until a line-stable key ships. FanDuel main markets keep one id, so that row’s opening → closing is the whole pre-game move.
  • Opening = the side’s quote when its league’s market goes live (see Opening cutoff). Closing = the latest eligible quote strictly before game_start: both the market and selection must be explicitly active, prices must be valid, and a recognized market must have its required selection values and compatible sides. Its observed market meaning must also agree with the market’s type at close; an earlier points quote cannot become a rebounds quote or another player’s quote through relabeling. Missing or unknown status does not establish an active quote.
  • A closing quote’s price, applicable spread/total, and timestamp come from the same observation. A later suspended or incomplete observation does not replace it, and an old threshold is never combined with a newer price.
  • From dataset_version v6, a selection first observed at or after game_start (an in-play-only market) has no row: it can never have a closing line. Find those in market-results or sportsbook-lines. Games still served at v5 include them with null closing columns.
  • From dataset_version v8, a closing quote also needs its book to have been observed for the game, in any market, in the 24 hours before game_start. A book we stopped receiving odds from has no close: its last quote could be days or months old. A market whose price simply stayed put still closes, because the rest of the book kept updating. Games still served at v7 carry that stale quote in the closing columns, mostly NFL before November 2025.
  • From dataset_version v10, darkness is judged per operator event: when a book deleted its event for the game and relisted it under a new one, the old event’s selections have no close, while the new event’s close as usual. Rows without an operator event id are judged across the whole book, as before.
  • If no eligible pre-game quote exists, all closing columns are null, including for all-suspended histories. The row, opening observation, and history statistics remain present.
  • Odds come in three encodings per snapshot: American (*_odds), decimal (*_decimal), and implied probability (*_implied, vig included).
  • line_move_count counts odds changes between consecutive records; first_seen_at / last_seen_at and min_odds/max_odds/distinct_odds_count span all records including live ones.
  • scope is reserved and always null.

Books post look-ahead prices weeks before a market is really trading, often just to show the game, and then reprice when it goes live. From dataset_version v10 the opening is taken at a per-league cutoff measured from when the books actually reprice (adr-0096), so those early prices don’t feed opening_* or CLV:

League Opening cutoff (opening_cutoff_ts)
NFL Monday 08:00 ET after the previous week’s Sunday. A Thursday game belongs to the coming Sunday, a Monday game to the Sunday before it.
NCAAF Sunday 12:00 ET after the previous Saturday. Weekday games belong to the coming Saturday; a Monday game to the Saturday before it.
Other leagues 48 hours before game_start.

opening_method says which quote opens the row:

  • AS_OF_CUTOFF: the book was being captured for this game in the 24 hours before the cutoff, so the opening is the quote it was showing at the cutoff. opening_ts can be earlier than the cutoff when the book held that price.
  • FIRST_AFTER_CUTOFF: the book was not being captured then, so its last quote at the cutoff may be a look-ahead price. The opening is its first quote after the cutoff and before game_start.
  • Null: neither exists, so the opening columns and CLV are null.

first_quote_odds, first_quote_implied, first_quote_spread and first_quote_total keep the first quote ever seen, which is what opening_* meant before v10. first_seen_at is still its time. On games built before v10 the new columns are null and opening_* is the first quote.

Book-only CLV compares a side’s own open to its own close at the same book:

  • clv_odds = closing_odds − opening_odds in American odds cents, counting −100 and +100 as the same price: −102 → +100 is 2, −110 → +110 is 20
  • clv_implied = opening_implied − closing_implied (implied-probability delta; implied probabilities include vig)

Both are null when either price is unavailable, or when the opening and closing observations represent incompatible market meanings or selection identities — different periods or metrics, or a relabeled selection.

CLV uses the opening above, so from v10 it measures the move from the live market’s open, not from a look-ahead price.

Before dataset_version v9, clv_odds subtracts the raw American odds, so a price that crossed even money reads 200 too far from zero (−102 → +100 shows 202). Moves that stay on one side of even money are the same in every version.

For cross-book fair-value work, sportsbook-lines carries devigged fair-value columns computed by six de-vig methods.

ColumnTypeNullableDescription
game_idstringnoGame id: LEAGUE_AWAY_HOME_YYYYMMDD (ET date; `_2` suffix for the second game of a doubleheader).
leaguestringnoLeague code, e.g. NBA, NFL, MLB, NHL, NCAAM, NCAAF.
game_datestringnoCalendar date of the scheduled start in US Eastern time (YYYY-MM-DD).
game_startstringnoScheduled start time, ISO-8601 UTC.
home_teamstringnoCanonical home team name.
away_teamstringnoCanonical away team name.
operatorstringnoSportsbook operator code, e.g. FANDUEL, DRAFTKINGS, PINNACLE.
market_typestringnoMarket type, e.g. SPREAD, MONEYLINE, TOTAL, PLAYER_METRIC_OVER_UNDER. A type, not an identity: a book can offer several markets of one type (main + alternate lines, one per player).
periodstringnoGame segment the market covers, e.g. the full game, a half, or a quarter.
scopestringyesReserved for future market scoping; always null.
market_keystringnoGlobal market identity: `OPERATOR:market_external_id` — the operator code plus the operator's own market id. Join key across closing-lines, market-results and sportsbook-lines.
selection_namestringyesOperator's label for the selection (e.g. a player for prop markets, `Over`); display only — shared across selections, never an identity. Null for plain team markets.
sidestringnoPriced side within the selection, e.g. HOME, AWAY, OVER, UNDER.
opening_oddsintegeryesAmerican odds of the opening quote: the quote at the league's opening cutoff when the book was being captured then, else the first quote after the cutoff and before game_start; null when neither exists. See `opening_method` and adr-0096. The first quote ever seen is `first_quote_odds`.
opening_decimalnumberyesDecimal odds at open.
opening_impliednumberyesImplied probability at open (vig included, 0-1).
opening_spreadnumberyesPoint spread (handicap) at open; null for non-spread markets.
opening_totalnumberyesTotal (over/under line) at open; null for non-total markets.
opening_tsstringyesTimestamp of the opening quote, ISO-8601 UTC; it can be before `opening_cutoff_ts` when the book held that quote through the cutoff.
closing_oddsintegeryesAmerican odds of the last eligible active quote strictly before game_start; null when no eligible pre-game quote exists.
closing_decimalnumberyesDecimal odds at close.
closing_impliednumberyesImplied probability at close (vig included, 0-1).
closing_spreadnumberyesPoint spread (handicap) at close.
closing_totalnumberyesTotal (over/under line) at close.
closing_tsstringyesTimestamp of the last eligible active pre-game quote, ISO-8601 UTC; null when no eligible pre-game quote exists.
min_oddsintegeryesLowest American odds observed across all records, pre-game and live.
max_oddsintegeryesHighest American odds observed across all records, pre-game and live.
distinct_odds_countintegeryesNumber of distinct American-odds values observed.
line_move_countintegeryesNumber of records where the odds changed from the previous record.
pre_game_recordsintegeryesChange-only records observed before game_start.
live_recordsintegeryesChange-only records observed at or after game_start.
first_seen_atstringyesTimestamp this side was first observed, ISO-8601 UTC.
last_seen_atstringyesTimestamp this side was last observed (including live), ISO-8601 UTC.
clv_oddsintegeryesclosing_odds - opening_odds in American odds cents, counting -100 and +100 as the same price (-102 to +100 is 2); null when either is missing or the opening and closing market meanings are incompatible.
clv_impliednumberyesopening_implied - closing_implied; null when either is missing or the opening and closing market meanings are incompatible.
market_player_idsstring[]yesPlayer UUIDs referenced by the market, as of the closing record.
selection_player_idsstring[]yesPlayer UUIDs referenced by the selection, as of the closing record.
market_metricstring[]yesStructured stat metric(s) the market prices (SportsMetric enum, e.g. FOOTBALL_RECEIVING_YARDS); null for non-player-metric markets. Join key for prop analysis — no name parsing needed (adr-0027).
selection_metricstringyesStructured metric for the priced selection; set only for method-of-score selections, else null.
market_external_idstringnoOperator's market id — the identity of the market within (game, operator); `market_key` is `operator` + `:` + this value. Same value as `market_external_id` on market-results and sportsbook-lines.
market_namestringyesOperator's market label, e.g. `Run Line Alternate`; display only.
selection_external_idstringnoOperator's selection id — the row identity together with `market_key` and `side`. For FanDuel main markets it is stable and the line moves on it (one row, opening → closing). DraftKings, BetMGM, Pinnacle and FanDuel alternates mint a new id per line value, so one market carries several rows per side and a moved line is a new row with opening == closing.
is_mainlinebooleanyesTrue when the selection belongs to the operator's primary (non-alternate) full-game spread, moneyline or total as of the closing quote — the headline line, not a rung of the alternate ladder. Period lines, team totals and props are false. Null on games built before v7.
opening_methodstringyesHow the opening quote was chosen: AS_OF_CUTOFF (the quote the book was showing at `opening_cutoff_ts`, the book being captured for this event in the 24 hours before it) or FIRST_AFTER_CUTOFF (the first quote after the cutoff and before game_start). Null when there is no opening, and on games built before v10.
opening_cutoff_tsstringyesThe league's opening cutoff for this game, ISO-8601 UTC: Monday 08:00 ET after the previous NFL Sunday, Sunday 12:00 ET after the previous NCAAF Saturday, else 48 hours before game_start (adr-0096). Null on games built before v10.
first_quote_oddsintegeryesAmerican odds of the first quote ever seen for this side, which may be a look-ahead price posted weeks before the market was live. Before v10 this was `opening_odds`. Null on games built before v10.
first_quote_impliednumberyesImplied probability of the first quote ever seen (vig included, 0-1). Null on games built before v10.
first_quote_spreadnumberyesPoint spread of the first quote ever seen; null for non-spread markets and on games built before v10.
first_quote_totalnumberyesTotal of the first quote ever seen; null for non-total markets and on games built before v10.
dataset_versionstringyesVersion of the dataset this game's file was built at, e.g. `v5`. Games in one pull can differ: versions of a compatibility line only add columns or carry documented changes (see `versions` on /v1/datasets). Columns added after a game's version read null for it. Files built before this column existed lack it entirely (null when read with DuckDB `union_by_name = true`); the `X-Betflux-Dataset-Version` response header still names their version.