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.
Fetching
Section titled “Fetching”Per-game only — a date-range pull would debit ~190k quota rows for every
game in the window, so the CLI requires --game:
betflux get sportsbook-lines --game MLB_BOS_NYY_20260715 --output lines.parquetcurl -s "https://api.betflux.ai/v1/games/MLB_BOS_NYY_20260715/sportsbook-lines" \ -H "Authorization: Bearer $BETFLUX_API_KEY" -o lines.parquetThe 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.
Live games
Section titled “Live games”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/segmentslists 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 inseqorder and you have the same columns the final file has.- To poll, pass the response’s
cursorback as?cursor=<seq>:<rows>: only new segments come back, and a segment you have partly read carriesfrom_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-behindfrom a segment URL whose object holds fewer rows than it promised, andlive-cursor-resetfrom a cursor the log no longer holds (the log was rebuilt — list again without a cursor). See Errors. - Once the game settles,
/segmentsand/boardanswer404withreason: "final_available"and the dataset route underfinal— 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 pluslast_changed_at,operator_observed_throughandquote_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.
Reading the grain
Section titled “Reading the grain”timestampis 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_typeandoperator_categoryshow 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.
Columns
Section titled “Columns”72 public columns, grouped by family.
Game and observation identity
Section titled “Game and observation identity”| Column | Type | Nullable | Description |
|---|---|---|---|
game_id | string | no | |
league | string | no | |
game_date | string | no | Calendar date (Parquet date, YYYY-MM-DD). |
game_start | string | no | UTC timestamp (Parquet timestamptz). |
home_team | string | no | |
away_team | string | no | |
operator | string | no | |
timestamp | string | no | UTC timestamp (Parquet timestamptz). |
Market
Section titled “Market”| Column | Type | Nullable | Description |
|---|---|---|---|
market_external_id | string | no | |
market_type | string | no | |
structure | string | no | |
period | string | no | |
market_status | string | yes | |
market_name | string | no | |
sgp | boolean | no | |
market_team_ids | string[] | yes | |
market_player_ids | string[] | yes | |
market_min_amount | number | yes | |
market_max_amount | number | yes | |
market_period_elapsed_start_sec | integer | yes | |
market_period_elapsed_end_sec | integer | yes | |
market_ordinal_position | integer | yes | |
market_aggregation | string | yes | |
market_metric | string[] | yes | |
selection_count | integer | no | |
market_key | string | yes | Global 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. |
Classification signals
Section titled “Classification signals”Raw signals from BetFlux’s market classifier — useful when you want to audit or re-derive a classification yourself.
| Column | Type | Nullable | Description |
|---|---|---|---|
operator_status | string | yes | |
operator_market_type | string | yes | |
operator_category | string | yes | |
paired_odds_complement | boolean | no |
Selection
Section titled “Selection”| Column | Type | Nullable | Description |
|---|---|---|---|
selection_external_id | string | no | |
selection_name | string | yes | |
selection_status | string | yes | |
side | string[] | yes | |
selection_team_ids | string[] | yes | |
selection_player_ids | string[] | yes | |
selection_min_amount | number | yes | |
selection_max_amount | number | yes | |
selection_metric | string | yes |
| Column | Type | Nullable | Description |
|---|---|---|---|
spread | number | yes | |
total | number | yes | |
odds | integer | no | |
decimal_odds | number | no | |
implied_probability | number | no | |
complement_odds | number | yes | American 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).
Calibration provenance
Section titled “Calibration provenance”| Column | Type | Nullable | Description |
|---|---|---|---|
synthetic_overround | number | yes | Margin 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_source | string | yes | Source 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_count | integer | yes | Number of accepted complementary polls in the selected calibration population. Zero for the assumed default; null for MECE. |
calibration_first_timestamp | string | yes | Earliest accepted observation used in the selected synthetic-margin estimate. Null for an assumed default or MECE. |
calibration_last_timestamp | string | yes | Latest accepted observation used in the selected synthetic-margin estimate; never later than this row timestamp. Null for an assumed default or MECE. |
De-vig fair values
Section titled “De-vig fair values”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).
| Column | Type | Nullable | Description |
|---|---|---|---|
multiplicative_fair_prob | number | yes | |
multiplicative_fair_american | number | yes | |
multiplicative_fair_decimal | number | yes | |
additive_fair_prob | number | yes | |
additive_fair_american | number | yes | |
additive_fair_decimal | number | yes | |
power_fair_prob | number | yes | |
power_fair_american | number | yes | |
power_fair_decimal | number | yes | |
power_k | number | yes | |
shin_fair_prob | number | yes | |
shin_fair_american | number | yes | |
shin_fair_decimal | number | yes | |
shin_z | number | yes | |
probit_fair_prob | number | yes | |
probit_fair_american | number | yes | |
probit_fair_decimal | number | yes | |
probit_shift | number | yes | |
logit_fair_prob | number | yes | |
logit_fair_american | number | yes | |
logit_fair_decimal | number | yes | |
logit_shift | number | yes |