Skip to content
Product Documentation

sportsbook-lines

The complete line history of a game: one row per change-only observation per (operator, market, selection) — every price move BetFlux saw, across every market a book offered, from scrape start through game end. Roughly 190,000 rows × 80 columns per game (typically ~20–35 MB of Parquet). This is the download-shaped dataset: fetch a game’s file once, keep it, and run every experiment locally. Available on every tier.

Per-game only — a date-range pull would debit ~190k quota rows for every game in the window, so the CLI requires --game:

Terminal window
betflux get sportsbook-lines --game MLB_BOS_NYY_20260715 --output lines.parquet
Terminal window
curl -s "https://api.betflux.ai/v1/games/MLB_BOS_NYY_20260715/sportsbook-lines" \
-H "Authorization: Bearer $BETFLUX_API_KEY" -o lines.parquet

The endpoint supports HTTP Range, so DuckDB can query the URL directly and read only the column chunks it needs — see Response formats. Quota is still debited at the full row count per request, so for repeated queries, download once and query the local file.

sportsbook-lines is the one dataset served before a game settles. Until the final file exists, the same endpoint answers 404 with problem type not-final, and the body points at the two live routes:

{
"type": "https://api.betflux.ai/problems/not-final",
"title": "Game is live",
"status": 404,
"game_id": "MLB_WAS_DET_20260922",
"live": {
"segments": "/v1/games/MLB_WAS_DET_20260922/sportsbook-lines/segments",
"board": "/v1/games/MLB_WAS_DET_20260922/sportsbook-lines/board"
}
}
  • GET .../sportsbook-lines/segments lists the game’s Parquet segments, each with a URL. The URL carries ?rows=<rows> — the row count the listing promised — so fetch the listed URLs verbatim. Concatenate the files in seq order and you have the same columns the final file has.
  • To poll, pass the response’s cursor back as ?cursor=<seq>:<rows>: only new segments come back, and a segment you have partly read carries from_row, so you read rows [from_row, rows). The cursor is a position, not a timestamp — a later segment can end earlier than the one before it, because a slower book’s file lands after a faster book’s later-stamped rows. It is "0:0" while the game has no segment yet.
  • Two 409s mean list again, and neither costs quota: live-segment-behind from a segment URL whose object holds fewer rows than it promised, and live-cursor-reset from a cursor the log no longer holds (the log was rebuilt — list again without a cursor). See Errors.
  • Once the game settles, /segments and /board answer 404 with reason: "final_available" and the dataset route under final — fetch that instead; the final file replaces the live artifacts.
  • GET .../sportsbook-lines/board (and /board/{operator}) gives one row per selection currently believed to be quotable: the segment columns plus last_changed_at, operator_observed_through and quote_state (live | stale_suspect). This is what to poll in-game; segments are for building history.

Live rows are provisional — the final build may revise them, and it replaces the live artifacts the moment it lands. Listing routes debit no quota rows. Each row of a live segment is charged once per account per month: reading a segment again costs nothing, and each poll of the growing open tail costs only the rows it gained since you last read it. The final file, once it lands, is charged at its full row count like any other download.

  • timestamp is when the observation was recorded. Rows exist only when something meaningful changed — the gap between a selection’s consecutive rows is exactly how long the previous price stood.
  • Raw operator vocabulary rides along: operator_market_type and operator_category show what the book called a market next to what BetFlux classified it as.
  • Six de-vig methods (multiplicative, additive, power, Shin, probit, logit) each contribute fair probability / American / decimal columns, computed from the market’s priced sides at that observation. In v5 artifacts, synthetic complements use only validated calibration quotes observed at or before the row’s timestamp; all quotes at the same timestamp are processed together. Classification and selection retention also use only history through that timestamp. Method parameters (power_k, shin_z, probit_shift, logit_shift) are included. Logit is the odds-ratio method (every odds ratio scaled by one constant).

The calibration_source, synthetic_overround, calibration_sample_count, calibration_first_timestamp and calibration_last_timestamp columns describe the estimate available for synthetic complements. Real paired complements do not use it; MECE rows have null calibration fields. The assumed default has a zero sample count and null sample timestamps. Artifacts before v5 used a retrospective whole-game margin and must be rebuilt before point-in-time use.

72 public columns, grouped by family.

ColumnTypeNullableDescription
game_idstringno
leaguestringno
game_datestringnoCalendar date (Parquet date, YYYY-MM-DD).
game_startstringnoUTC timestamp (Parquet timestamptz).
home_teamstringno
away_teamstringno
operatorstringno
timestampstringnoUTC timestamp (Parquet timestamptz).
ColumnTypeNullableDescription
market_external_idstringno
market_typestringno
structurestringno
periodstringno
market_statusstringyes
market_namestringno
sgpbooleanno
market_team_idsstring[]yes
market_player_idsstring[]yes
market_min_amountnumberyes
market_max_amountnumberyes
market_period_elapsed_start_secintegeryes
market_period_elapsed_end_secintegeryes
market_ordinal_positionintegeryes
market_aggregationstringyes
market_metricstring[]yes
selection_countintegerno
market_keystringyesGlobal 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. Always populated from v3 on.

Raw signals from BetFlux’s market classifier — useful when you want to audit or re-derive a classification yourself.

ColumnTypeNullableDescription
operator_statusstringyes
operator_market_typestringyes
operator_categorystringyes
paired_odds_complementbooleanno
ColumnTypeNullableDescription
selection_external_idstringno
selection_namestringyes
selection_statusstringyes
sidestring[]yes
selection_team_idsstring[]yes
selection_player_idsstring[]yes
selection_min_amountnumberyes
selection_max_amountnumberyes
selection_metricstringyes
ColumnTypeNullableDescription
spreadnumberyes
totalnumberyes
oddsintegerno
decimal_oddsnumberno
implied_probabilitynumberno
complement_oddsnumberyesAmerican odds of the complement used for de-vigging. Real paired sides use the partner quote; independent or unpaired sides use synthetic_overround calibrated only through this observation timestamp. Null for MECE or when no consistent synthetic complement exists; the latter also produces null fair values.

complement_odds is a real book price when a PAIRED* selection has a matching partner. Independent selections and unpaired selections instead use synthetic_overround to synthesize a complement. That margin is estimated from validated quotes available through this timestamp, or defaults to 5% when there are too few samples. These remain model-dependent fair-value estimates. When no consistent complement exists, complement_odds and the fair-value columns are null; the boundary depends on the margin (at or above +1900 for a 5% margin).

ColumnTypeNullableDescription
synthetic_overroundnumberyesMargin available for synthetic complements at this observation. Measured from verified complementary polls at or before timestamp, or the 5% default. Null for MECE; real paired complements do not use this estimate.
calibration_sourcestringyesSource of synthetic_overround: validated_operator_market_type, validated_operator, or assumed_default. Null for MECE. Present on point-in-time sportsbook-lines v5 artifacts.
calibration_sample_countintegeryesNumber of accepted complementary polls in the selected calibration population. Zero for the assumed default; null for MECE.
calibration_first_timestampstringyesEarliest accepted observation used in the selected synthetic-margin estimate. Null for an assumed default or MECE.
calibration_last_timestampstringyesLatest accepted observation used in the selected synthetic-margin estimate; never later than this row timestamp. Null for an assumed default or MECE.

Six method families; each null when the method doesn’t apply to the market’s shape, when its solver degenerated on this selection, or when the selection has no complement to de-vig against (see complement_odds).

ColumnTypeNullableDescription
multiplicative_fair_probnumberyes
multiplicative_fair_americannumberyes
multiplicative_fair_decimalnumberyes
additive_fair_probnumberyes
additive_fair_americannumberyes
additive_fair_decimalnumberyes
power_fair_probnumberyes
power_fair_americannumberyes
power_fair_decimalnumberyes
power_knumberyes
shin_fair_probnumberyes
shin_fair_americannumberyes
shin_fair_decimalnumberyes
shin_znumberyes
probit_fair_probnumberyes
probit_fair_americannumberyes
probit_fair_decimalnumberyes
probit_shiftnumberyes
logit_fair_probnumberyes
logit_fair_americannumberyes
logit_fair_decimalnumberyes
logit_shiftnumberyes