NFL betting data: what a sports betting dataset contains, and how one is built
What "NFL betting data" means as a dataset
Strip the word "betting" down to its records and the dataset is modest: one row per game, carrying the closing point spread, the final score, the closing total, and the kickoff conditions. No tips, no systems, no odds feeds. That is what a sports betting dataset is, and the NFL Games & Betting Lines pack is exactly that shape: 7,548 games from 1999 through the full 2026 schedule, 7,276 of them played with a closing line, 46 columns per row. Use it to study what the lines were and what happened next, which is the whole of what historical analysis can honestly do.
The free 22-row sample carries the same 46 columns and downloads here with no signup, so every table and join on this page can be reproduced before buying anything.
The four ingredients of a betting dataset
Column names below come straight from the games CSV:
| Ingredient | Column(s) | Coverage in the pack |
|---|---|---|
| Closing point spread | spread_line (stored relative to the home team) | every played game since 1999 |
| Final scores and margin | away_score, home_score, result (home minus away) | every played game since 1999 |
| Closing total | total_line, scored against total (combined points) | same rows as the spread |
| Kickoff conditions | roof, surface, temp, wind (blank for domes) | same rows; moneylines and odds reach back to 2006 (5,407 games) |
Two rows from the free sample show the shape:
| game_id | spread_line | total_line | Final | result | roof |
|---|---|---|---|---|---|
| 1999_01_KC_CHI | -3 (KC favored) | 38 | CHI 20-17 | 3 | outdoors |
| 1999_01_BUF_IND | -3 (BUF favored) | 45.5 | IND 31-14 | 17 | dome |
Read from that: Kansas City closed as a 3-point road favorite and lost outright; Buffalo closed as a 3-point road favorite and lost by 17. The rows record that; what anyone should do next is not in the file. The full column glossary, including rest days and starting quarterbacks, lives in the games.csv column guide.
Where betting data comes from, legally
The pack's source is nflverse-data, a public GitHub project that publishes game results with closing lines as plain CSV, licensed CC BY 4.0. Commercial analysis is allowed with a credit line. The free sports data review verifies that license and four other sources, league by league.
The honest limits on sources: sportsbook sites show current odds but sell no historical bulk downloads, their terms forbid scraping, and the popular aggregators license their data rather than give it away. A public, openly licensed release is the clean route, and it is why this pack's rows are checksummed copies of nflverse output with derived tables built on top.
How the dataset is assembled: joining spreads to results on game_id
Betting datasets are two stories joined per game. The lines are one story, set before kickoff: a spread, a total, an expected margin. The results are the other, known only after: final scores, the actual margin, the actual combined total. The join key is a per-game identifier, game_id, in the format season_week_AWAY_HOME, for example 1999_01_KC_CHI. In the full pack both stories already live in one row of games.csv; the join below is shown because separate spreads and results files are the common situation when you build a betting dataset from raw sources.
Real example, run against the free sample. First the lines and the results are pulled into two tables, each keyed by game_id:
import pandas as pd
games = pd.read_csv("https://jayjex.github.io/data-vault/data/nfl-games/sample.csv",
dtype={"game_id": str})
spreads = games[["game_id", "season", "week", "spread_line", "total_line"]]
results = games[["game_id", "gameday", "away_team", "home_team",
"away_score", "home_score", "result"]]
Then one merge lines them up per game:
joined = spreads.merge(results, on="game_id", how="inner") print(joined.shape) # (22, 11) on the sample print(joined.head(5).to_string(index=False))
The printed output from that exact run:
spreads: (22, 5) results: (22, 7) merged: (22, 11)
game_id season week spread_line total_line gameday away_team home_team away_score home_score result
1999_01_MIN_ATL 1999 1 -4.0 49.0 1999-09-12 MIN ATL 17 14 -3
1999_01_KC_CHI 1999 1 -3.0 38.0 1999-09-12 KC CHI 17 20 3
1999_01_PIT_CLE 1999 1 -6.0 37.0 1999-09-12 PIT CLE 43 0 -43
1999_01_OAK_GB 1999 1 9.0 43.0 1999-09-12 OAK GB 24 28 4
1999_01_BUF_IND 1999 1 -3.0 45.5 1999-09-12 BUF IND 14 31 17
On the full file, the same merge is what assembles the history: 7,548 game rows, of which 7,276 played games carry a closing line, and 272 are the scheduled 2026 games still waiting on scores. Run the merged table through result - spread_line and every cover-and-push question from the spread history guide becomes arithmetic; that is also the check worth running on any spread history CSV you download from anywhere.
After the join, the derived tables in the pack save the group-bys: team-records-by-season.csv (861 rows) carries per-team ATS and over/under records, and season-summaries.csv (27 rows, free in full) carries per-season cover and over/under rates. Both are built from this same joined view.
Weather and totals: the columns that make rows researchable
Weather is what separates a betting dataset from a scores list, because totals react to it and any total analysis needs the conditions on the row. In games.csv the columns are roof, surface, temp (Fahrenheit), and wind (mph), recorded at kickoff, blank for dome games. In the 22-row sample, 6 games are domes with blank temp and 16 are outdoors with values, from 67°F in Green Bay to 80°F in Chicago. The stadium weather guide works those columns across the full file, dome vs outdoor scoring included.
Totals follow the same join logic as spreads: total_line is the pre-kickoff number, total is what the two teams actually scored combined, and over/under records in the derived tables are the comparison, counted per season in season-summaries.csv.
What a betting dataset cannot do
The file holds closing lines and finished games. It holds no opening lines, no line movement, no injury reports, and no probabilities for games that have not happened. Closing lines describe what the market priced at kickoff; they are a record of the past, and nothing in a 7,276-row history predicts week 1 of the next season. This page and the pack are for research and analysis of completed games. If you are looking for picks or staking advice, they are not here, and no historical CSV can supply them.
Get the betting dataset
Start free: the 22-row sample CSV (46 columns, spread to weather) and the complete season-summaries.csv download with no signup, and the MCP queries on the for agents page filter the full 7,548-row file at no cost. The full pack adds all rows, the two derived tables, a data dictionary, and SHA-256 checksums. Prefer a notebook? The pandas quickstart loads the sample in two lines, and the Excel and Google Sheets guide covers the spreadsheet route.
Checkout and download run through Getly. The sample and season-summaries download free here, no signup. Source: nflverse game and betting data, CC BY 4.0. Snapshot taken 2026-09-06; results run through the 2025 season.
NFL betting data questions
What data do you need to analyze NFL betting?
Four things, one row per game: the closing point spread, the final score, the closing total, and kickoff conditions. In the games CSV those are spread_line (stored relative to the home team), result (home minus away), total_line, and roof/temp/wind. The full file covers 7,548 games from 1999 through the 2026 schedule, 7,276 of them played with a closing line.
Where can I legally get historical NFL betting data?
nflverse-data publishes game results with closing lines as a direct CSV download on GitHub, licensed CC BY 4.0, which allows commercial use with attribution. On this site, the 22-row sample and the full season-summaries.csv download free under the same license. Sportsbook sites display odds but offer no bulk downloads and forbid scraping, so openly licensed releases are the legal route.
How do you join a spreads CSV to game results?
Merge on a shared per-game key. Here that key is game_id, formatted season_week_AWAY_HOME (example: 1999_01_KC_CHI). In pandas: spreads.merge(results, on="game_id", how="inner"). On the sample the inner join returns all 22 rows with lines and scores side by side; on the full file the same join assembles 7,276 played games from 1999 through 2025.