The free source most public NFL analysis runs on is nflverse: 24 data releases on GitHub, 4,376 files, no key, CC BY 4.0. The catalog, how to load it with nflreadr or nflreadpy (nfl_data_py is archived and deprecated), the two releases that are traps, and a timed test: after Thursday night's game the 2026 play-by-play file was replaced 4 hours 18 minutes after kickoff.
By C. B. Zakarian · Published October 16, 2026
The free source most public NFL analysis is built on is nflverse: a GitHub repository that publishes its data as release files, with no key and no account, under a CC BY 4.0 licence. On October 16, 2026 it held 25 releases (24 of data and one for testing) and 4,376 files, about 6.6 GB. You load them with nflreadr in R or nflreadpy in Python, or read a file's URL directly. After Thursday night's game the 2026 play-by-play file was replaced 4 hours 18 minutes after kickoff.
Related: Expected points (EPA) · Chapter 3: nflverse and nflfastR · Resources
Measured from the nflverse release listing and repository records read through GitHub's REST API at 1:20 am ET on October 16, 2026, and from the 2026 play-by-play file downloaded at that time.
Most searches for an NFL API are looking for one thing: a way to pull play-by-play, scores and player stats into code without paying. The free answer is not a REST service with endpoints and keys. It is a set of files. nflverse, an open-source project, runs automated jobs that rebuild the data and upload it to GitHub releases, one release per data set (play-by-play, schedules, rosters, injuries and so on), with one file per season in several formats: csv, compressed csv, parquet, R's rds and qs.
That makes the URL of a file the API. Every file follows the same pattern; the 2026 play-by-play file, for example, is at
https://github.com/nflverse/nflverse-data/releases/download/pbp/play_by_play_2026.parquet
and the season number and the format in the name are all that change. The two loader packages, nflreadr for R and nflreadpy for Python, build these URLs for you, cache the downloads and hand back a data frame.
This guide sticks to openly licensed data. Sports sites also serve JSON to their own apps through endpoints that have no public documentation; their terms of use govern those, not a published licence, so they are left out here. Paid data services are left out too: none were tested.
Sorted by size. "Newest file" is the date, Eastern time, of the most recent update to any file in the release.
| Release | What it holds | Files | MB | Newest file |
|---|---|---|---|---|
pbp | Play-by-play, every season since 1999, with EPA, win probability and CPOE | 164 | 4,327 | 2026-10-16 |
player_stats | The old player stats files (deprecated 2025-08-01) | 1,831 | 581 | 2025-05-07 |
stats_player | Player stats by week, season and season with playoffs | 554 | 393 | 2026-10-16 |
pbp_participation | Who was on the field for each play, from NGS (no 2026 file) | 46 | 393 | 2026-02-10 |
weekly_rosters | Rosters by week, since 2002 | 104 | 311 | 2026-10-15 |
depth_charts | Depth charts | 109 | 268 | 2026-10-15 |
rosters | Season rosters | 434 | 71 | 2026-10-15 |
snap_counts | Snap counts | 77 | 48 | 2026-10-13 |
ftn_charting | Play charting provided by FTN | 21 | 39 | 2026-10-14 |
contracts | OverTheCap contract data | 7 | 33 | 2026-10-15 |
pfr_advstats | Pro Football Reference advanced stats | 209 | 26 | 2026-10-16 |
stats_team | Team stats by week, season and season with playoffs | 554 | 22 | 2026-10-16 |
players_components | Component files for the players build | 12 | 18 | 2026-10-15 |
injuries | Injury reports and practice participation | 77 | 18 | 2026-10-15 |
misc | One-off files, not updated automatically | 17 | 17 | 2026-09-16 |
players | Player information and ID mappings | 7 | 17 | 2026-10-15 |
nextgen_stats | NFL Next Gen Stats tables | 95 | 13 | 2026-10-15 |
espn_data | ESPN QBR, by season and by week | 12 | 4 | 2026-10-15 |
draft_picks | Draft picks since 1980 | 7 | 4 | 2026-10-14 |
schedules | Schedule and results: one row per game | 6 | 4 | 2026-10-16 |
combine | Combine results | 7 | 2 | 2026-03-12 |
officials | Officials assigned to each game | 7 | 2 | 2026-09-02 |
trades | Trades | 7 | <1 | 2026-05-19 |
teams | Team names, colors and logo links | 7 | <1 | 2026-03-27 |
Play-by-play is the core: 164 files and 4,327 MB, a file per season for every season from 1999 to 2026, each play carrying expected points added (EPA), win probability and completion percentage over expected (CPOE). The next most useful for most work are schedules, one row per game with the score, and the two stats releases, stats_player and stats_team, which hold weekly and season totals.
Two releases are traps. player_stats has the most files, 1,831, but its release notes read "DEPRECATED 2025-08-01: USE stats_player OR stats_team INSTEAD", and none of its files has changed since May 7, 2025. Code that still reads it gets no 2026 numbers. And misc is labelled in its notes as one-off files that are not updated automatically.
Thursday's game gives a clean test. Seattle at Denver kicked off at 8:15 pm ET on October 15, and Denver won 17-14. The listing read at 1:20 am ET showed:
Those five releases were the only ones changed since kickoff. Over the day before, 14 of the 24 releases had a file updated, the other nine between 8:16 am and 9:48 am ET on Thursday, and 17 of the 24 within the week. The files are also rebuilt when no game has been played: the 2026 play-by-play file read on Wednesday, October 14 carried a 10:31 am ET timestamp from that morning, and the weekly stats files read on Thursday a 10:12 am timestamp, both days with no game since Monday night.
These are a handful of observations, not a published schedule. What they show is the practical rhythm: the data is a morning-after source, not a live feed. Nothing in these releases updates during a game, and a timestamp says when a file was uploaded, not which games it holds, so check the latest week in the data before you trust it.
R. nflreadr is the loader the release notes themselves name (the play-by-play release reads "accessed with nflreadr::load_pbp()"):
install.packages("nflreadr")
library(nflreadr)
pbp <- load_pbp() # play-by-play, current season
games <- load_schedules() # schedules and results
Python. Use nflreadpy. Its README describes it as a Python port of nflreadr; it returns Polars data frames, and a to_pandas() call converts one for pandas users. The repository was created on September 8, 2025 and is MIT-licensed:
pip install nflreadpy
import nflreadpy as nfl
pbp = nfl.load_pbp() # current season, a Polars DataFrame
pbp = pbp.to_pandas() # if you work in pandas
games = nfl.load_schedules()
The older Python package, nfl_data_py, is the one most search results still point to. Its repository is archived, its last push was September 25, 2025, and its README now opens: "nfl_data_py has been deprecated in favour of nflreadpy ... No further nfl_data_py maintenance or updates are planned." New code should start with nflreadpy.
No package at all. Any tool that reads a URL can read a release file. In pandas, with pyarrow installed:
import pandas as pd
url = "https://github.com/nflverse/nflverse-data/releases/download/pbp/play_by_play_2026.parquet"
pbp = pd.read_parquet(url)
Choose parquet when you can. The 2026 play-by-play file is 28.2 MB as csv, 5.5 MB as compressed csv, 6.0 MB as parquet and 4.2 MB as rds: the parquet file is about a fifth of the csv and keeps each column's type.
The 2026 play-by-play file downloaded at 1:20 am ET has 14,031 rows and 372 columns, covering 80 games, weeks 1 through 6. Not every row is a play from scrimmage. The usual filter keeps rows marked as a pass or a run that carry an EPA value:
plays = pbp[((pbp["pass"] == 1) | (pbp["rush"] == 1)) & pbp["epa"].notna()]
tnf = plays[plays["game_id"] == "2026_06_SEA_DEN"]
tnf.groupby("posteam")["epa"].agg(["count", "mean"])
(tnf["epa"] > 0).groupby(tnf["posteam"]).mean() # success rate
That leaves 10,350 plays for the season: 5,677 passes, 4,153 runs and 520 rows whose play type is no_play, snaps wiped out by a penalty that the file still marks as a pass or a run. The 3,681 rows the filter drops are 974 other penalty rows, 827 kickoffs, 614 punts, 704 extra points and field goals, 152 kneel-downs and spikes, and 410 marker rows such as the start and end of each game and the end of each quarter.
Thursday's game has 189 rows and 142 plays. Seattle ran 83 plays at -.089 EPA per play with a success rate of .398; Denver ran 59 at -.113 with a success rate of .288. Denver won anyway, 17-14, and the file's own running score columns (total_home_score and total_away_score) end at 17 and 14, matching the game log. One game's per-play numbers and its result can point different ways; the success rate and expected points explainers explain what each measure can and cannot say.
catalog = GET api.github.com/repos/nflverse/nflverse-data/releases (all pages, every asset)
newest file = latest asset updated_at in each release, shown in Eastern time (UTC-4)
freshness = asset updated_at minus Thursday's kickoff, 2026-10-16 00:15 UTC
libraries = repository records and READMEs for nflreadr, nflreadpy, nfl_data_py, nflfastR
worked case = play_by_play_2026.parquet; plays = (pass == 1 or rush == 1) and EPA not missing
The script make_nfl_api_guide_chart.py reads the frozen listing, repository records and play-by-play file, builds the catalog and the timings, checks Thursday's final score in the file against the game log, draws the exhibit and asserts every number on this page.
Sources: the nflverse-data release listing, licence record and 2026 play-by-play file, read through GitHub's REST API at 1:20 am ET on October 16, 2026, with the play-by-play and stats listings read October 14 and 15 for the earlier timestamps; the repository records and READMEs of nflreadr, nflreadpy and nfl_data_py, read the same morning; the nflverse game log for Thursday's score.
Want the code behind these metrics? Work through the 43-chapter NFL analytics tutorial.
Browse tutorials Free tools