NFL stats by team CSV: 861 team-season rows, 1999-2025
The NFL stats-by-team file in this catalog is team-records-by-season.csv: 861 rows, one per team per season from 1999 through 2025, playoffs included. Each row carries 15 columns: games played, wins, losses, ties, points for and against, the against-the-spread record with pushes broken out, and the over/under record. All of it was computed from the 7,548-row games table by a script that ships with the pack, so you can audit or rebuild every number.
You can work with the table three ways: query it free over MCP (100 rows per call, no signup), compute your own per-team splits from the free games sample, or download the full 861-row CSV with the full pack. The sections below show the schema, real sample rows, and the queries.
The 15 columns
| Column | Meaning |
|---|---|
| season | NFL season year, 1999 to 2025 |
| team | 3-letter nflverse abbreviation, e.g. KC, PHI, DAL |
| games | Games played that season, regular season plus playoffs |
| wins, losses, ties | W-L-T record, playoff games included |
| points_for | Total points scored |
| points_against | Total points allowed |
| ats_wins | Games where the team covered the closing spread |
| ats_losses | Games where the team failed to cover |
| ats_pushes | Games that landed exactly on the spread (integer lines only) |
| ats_win_pct | ats_wins / (ats_wins + ats_losses), pushes excluded |
| overs | Games whose combined score went over total_line |
| unders | Games that went under |
| total_pushes | Games whose combined score equaled total_line exactly |
ATS stands for against the spread. A team covers when its margin of victory beats the line it gave, computed as margin + team_spread > 0. A push lands exactly on the line, which only happens on integer spreads like -3; half-point lines like 3.5 cannot push. The same three-way split applies to totals. Pushes count as neither a cover nor a miss, and ats_win_pct excludes them.
Sample rows from the file
Ten of the 861 rows, copied verbatim from team-records-by-season.csv. Read a row like 2024,KC as: 20 games, a 17-3 record, 462 points scored, 409 allowed.
| season | team | games | wins | losses | ties | points_for | points_against | ats_wins | ats_losses | ats_pushes | ats_win_pct | overs | unders | total_pushes |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1999 | ARI | 16 | 6 | 10 | 0 | 245 | 382 | 5 | 11 | 0 | 0.312 | 7 | 9 | 0 |
| 1999 | BAL | 16 | 8 | 8 | 0 | 324 | 277 | 8 | 8 | 0 | 0.5 | 7 | 9 | 0 |
| 1999 | DAL | 17 | 8 | 9 | 0 | 362 | 303 | 11 | 5 | 1 | 0.688 | 7 | 10 | 0 |
| 2024 | KC | 20 | 17 | 3 | 0 | 462 | 409 | 17 | 3 | 0 | 0.85 | 9 | 11 | 0 |
| 2024 | DET | 18 | 15 | 3 | 0 | 595 | 387 | 16 | 2 | 0 | 0.889 | 10 | 7 | 1 |
| 2024 | TEN | 17 | 3 | 14 | 0 | 311 | 460 | 2 | 15 | 0 | 0.118 | 10 | 7 | 0 |
| 2025 | KC | 17 | 6 | 11 | 0 | 362 | 328 | 11 | 6 | 0 | 0.647 | 4 | 13 | 0 |
| 2025 | PHI | 18 | 11 | 7 | 0 | 398 | 348 | 11 | 6 | 1 | 0.647 | 7 | 11 | 0 |
| 2025 | BUF | 19 | 13 | 6 | 0 | 538 | 422 | 15 | 4 | 0 | 0.789 | 10 | 9 | 0 |
| 2025 | ARI | 17 | 3 | 14 | 0 | 355 | 488 | 4 | 13 | 0 | 0.235 | 10 | 7 | 0 |
A few rows here mark things to know before you aggregate. KC 2024 shows 20 games because playoffs count: 17-3 is the 15-2 regular season plus a 2-1 playoff run. The first three seasons carry 31 teams each (the Texans joined in 2002), which is why 27 seasons produce 861 rows instead of 864. And franchise codes stay at their old homes: OAK, SD, STL, plus WAS through 2021 and WSH from 2022. Map those before joining across decades.
Query examples: filter by team and season
Free over MCP, no download
The dataset-mcp server queries the released 861-row file directly. Start the server, then call query_dataset. Every Chiefs season since 2020, first page:
npx --allow-git=all -y github:jayjex/dataset-mcp # plain form works on npm 10/11: npx -y github:jayjex/dataset-mcp query_dataset("nfl-games", {file: "nfl-team-records-by-season.csv", where: [{column: "team", op: "=", value: "KC"}, {column: "season", op: "gt", value: 2019}], limit: 10}) total_matched: 6 | next_offset: null 2020,KC,19,16,3,0,542,434,17,2,0,0.895,9,10,0 2021,KC,20,14,6,0,588,448,18,2,0,0.9,12,8,0 2022,KC,20,17,3,0,584,444,18,2,0,0.9,9,11,0 ...
Filters combine, operators are =, contains, gt, and lt, and get_stats returns counts, min/max, and top-N values for any column without paging through rows. Setup details live on the for agents page. This runs against the full file and costs nothing.
Team splits at the game level work the same way on the main games table. Every 2024 Chiefs game, home or away, free:
query_dataset("nfl-games", {where: [{column: "season", op: "=", value: 2024}, {column: "home_team", op: "=", value: "KC"}], limit: 5})
total_matched: 10 | next_offset: 5
query_dataset("nfl-games", {where: [{column: "season", op: "=", value: 2024}, {column: "away_team", op: "=", value: "KC"}], limit: 5})
total_matched: 10 | next_offset: 5
Ten home games, ten away games, 20 total: the playoff games count too. Compare those call outputs against the single KC 2024 row in the team table and the whole derivation checks out in a browser tab. For the live side of the same abbreviations, the ESPN API in Python guide pulls all 32 club codes from ESPN's teams endpoint and shows how they join back to this file.
In pandas, on the full CSV
After loading the table from the pack:
import pandas as pd
df = pd.read_csv("team-records-by-season.csv")
kc = df[(df.team == "KC") & (df.season >= 2015)]
# point differential per team-season
df["point_diff"] = df.points_for - df.points_against
top_2025 = df[df.season == 2025].sort_values("point_diff", ascending=False).head(10)
# best single-season cover rates since 2010
best_ats = df[df.season >= 2010].nlargest(10, "ats_win_pct")[
["season", "team", "wins", "losses", "ats_wins", "ats_losses", "ats_win_pct"]]
In Excel or Google Sheets
Open the CSV, click any cell, Data → Create a filter. Set team = KC to get the 27 season rows, or season = 2025 to get all 32 teams from one season. Sort by points_against ascending for best defenses, or by ats_win_pct for the year's biggest overperformers against closing lines.
How to download the 861 rows
| Free | Full pack | |
|---|---|---|
| team-records-by-season.csv | queryable over MCP, 100 rows/call | complete 861-row CSV download |
| season-summaries.csv | all 27 rows, free download | same table |
| games.csv | 22-row sample | all 7,548 rows, 46 columns |
| derive script + dictionary | described in this guide | derive.py included, full dictionary |
Checkout and download run through Getly. The full pack is 3 CSVs (7,548 + 861 + 27 rows), the derivation script, a data dictionary, and source SHA-256 checksums.
Source and license
Every number in the table was computed from nflverse's schedules/games.csv, downloaded 2026-09-06 with the SHA-256 recorded in the pack. nflverse-data is released under CC BY 4.0, and the derived table carries the same terms. If you publish work built on this data, credit it like this: "Data from nflverse, CC BY 4.0". Team names are factual data; no logos or league marks appear in the pack.
What this data is for
The table is historical record: per-team-per-season results and line outcomes as they stood at each game. Common uses are research and analysis, such as ranking point differentials across eras, comparing franchise scoring trends, or splitting a model's training data by team-season. This page and the pack describe the data only and offer no betting advice, picks, or predictions. To roll the game rows up by league side instead of by franchise, the NFL data by conference guide maps every abbreviation to AFC or NFC and runs the head-to-head on the real sample.
FAQ
Where can I get NFL stats by team as a CSV?
Data Vault's NFL pack includes team-records-by-season.csv: 861 rows with games, wins, losses, ties, points for and against, ATS record, and over/under record for every team and season from 1999 through 2025. The whole table is queryable free over MCP (100 rows per call, no signup), and the complete CSV downloads with the pack from Getly.
What seasons and teams does the 861-row table cover?
Seasons 1999 through 2025, playoffs included, so a Super Bowl team shows 20-21 games that season. The first three seasons carry 31 teams each and the rest carry 32 after the Texans joined in 2002, which is why the table has 861 rows instead of 864.
Can I query team-season stats without downloading anything?
Yes. The dataset-mcp server queries the released 861-row file directly: run npx -y github:jayjex/dataset-mcp, then filter on any column. A call like query_dataset("nfl-games", {file: "nfl-team-records-by-season.csv", where: [{column: "team", op: "=", value: "KC"}], limit: 10}) returns the Chiefs' season rows. No API key, no cost, 100 rows per call.
What do ats_wins and overs mean in the file?
ats_wins counts games where the team covered the closing spread, ats_losses counts failures, and ats_pushes counts games that landed exactly on an integer line; ats_win_pct divides wins by wins plus losses, so pushes are excluded. overs, unders, and total_pushes apply the same three-way split to the closing total line. These are historical data columns for research, not predictions or betting advice.