NFL data API in Python: requests examples with live endpoints
Two lines of Python ask ESPN's site API for a week of NFL results, no key, no signup:
import requests
r = requests.get(
"https://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard",
params={"seasonType": 2, "week": 1, "dates": 2025},
timeout=30,
)
r.raise_for_status()
events = r.json()["events"]
print(len(events)) # 16
print(events[0]["name"]) # Dallas Cowboys at Philadelphia Eagles
That ran on 2026-09-10 against the real endpoint, output verbatim. A second free route, the nflverse-data releases, serves the entire game history as one CSV that the same requests call can download. The sections below cover both, then the trade-off against a pinned snapshot CSV.
The free routes, checked live
The league itself runs no public API for Python or anything else. What people mean by an NFL data API is one of the routes below or a paid commercial feed. All of these were hit on 2026-09-10:
| Route | What you get | Auth | Checked 2026-09-10 |
|---|---|---|---|
ESPN site API, scoreboard | One week of fixtures and results as nested JSON | none | 200, 16 events |
ESPN site API, teams | Team metadata per club, abbreviation and venue | none | 200 |
nflverse-data releases, schedules/games.csv | Full 1999-2026 game history, one 2.1 MB CSV | none | 200, 7,548 rows, updated 2026-09-07 |
nfl_data_py (PyPI) | Wrapper that pulls the same nflverse releases into pandas, import_schedules() | none | version 0.3.3, pins pandas < 2.0 |
nflreadpy (PyPI) | Newer wrapper, load_schedules() into polars | none | version 0.1.5 |
The wrappers are a real option and worth one caveat: nfl_data_py 0.3.3 requires numpy < 2.0 and pandas < 2.0, so it can collide with a current environment. Calling the URLs directly with requests avoids the dependency pinning entirely, which is what the examples here do.
Example 1: ESPN scoreboard JSON
The quick-answer call again, with the score lines pulled out. Competitors sit under each event's competitions list, ESPN returns scores as strings, and the homeAway key tells you which side is which, since the array order is not guaranteed:
import requests
r = requests.get(
"https://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard",
params={"seasonType": 2, "week": 1, "dates": 2025},
timeout=30,
)
r.raise_for_status()
data = r.json()
for e in data["events"][:2]:
comp = e["competitions"][0]["competitors"]
home = next(c for c in comp if c["homeAway"] == "home")
away = next(c for c in comp if c["homeAway"] == "away")
print(f'{away["team"]["abbreviation"]} {away["score"]} at '
f'{home["team"]["abbreviation"]} {home["score"]}')
# DAL 20 at PHI 24
# KC 21 at LAC 27
Week 1 of the 2025 season returned 16 events, and the two games above closed 24-20 and 27-21. Swap params for {"seasonType": 2, "week": 1, "dates": 2026} to see the current season's opening week fill in as results land. Team lookups follow the same pattern at /teams/kc, also 200 on the checked date.
One honest limit: this is an undocumented site endpoint, not a published product. The response shape held long enough for the example above, but treat the deep nesting as a display feed, read the fields you need, and do not build a long-lived pipeline on assumptions about the structure.
Example 2: the nflverse games.csv over HTTP
Where ESPN covers a week, the nflverse-data releases cover every season since 1999 in one file. The download URL redirects to a CDN, and requests follows it automatically:
import csv, io, requests url = "https://github.com/nflverse/nflverse-data/releases/download/schedules/games.csv" r = requests.get(url, timeout=60) r.raise_for_status() print(r.status_code, len(r.content)) # 200 2177170 rows = list(csv.reader(io.StringIO(r.text))) print(len(rows) - 1) # 7548
7,548 game rows, the same count the preprocessed pack carries. Before you parse, you can ask the release how fresh it is:
stamp = requests.get(
"https://github.com/nflverse/nflverse-data/releases/download/schedules/timestamp.json",
timeout=30,
).json()
print(stamp["last_updated"]) # 2026-09-07 04:16:18 EDT
To hand the file to pandas, pd.read_csv(io.StringIO(r.text)) works on the same response object, and the columns match the ones in the pandas quickstart, which loads this site's 22-row sample with the same schema.
Live API or snapshot CSV? The honest trade-off
Both routes get you the same game rows, and the right pick depends on how often you rerun and what has to stay true between runs:
| Live API or release file | Pinned snapshot CSV | |
|---|---|---|
| Freshness | Current week the moment it lands; release file refreshed in season (2026-09-07 at the last check) | Static: results through the 2025 season plus scheduled 2026 rows, refresh means re-download |
| Cost per run | Scoreboard JSON is a few KB; full history is 2.1 MB every single call | Download once, everything after is local disk |
| Stability | ESPN is undocumented and can shift shape; the nflverse file follows its source schema | Schema pinned, SHA-256 checksums in the pack, joins do not break between runs |
| Work done for you | Raw fields: you normalize spreads and derive records yourself | Normalized spread and moneyline columns, 861 team records, 27 season summaries, data dictionary |
| Best for | Dashboards, current-week scripts, one-off lookups | Repeat analysis, offline work, agents and notebooks that need reproducible inputs |
The practical answer for most projects is both, in one script: pull the scoreboard for this week's games, load the snapshot for everything before this season. Neither makes the other wrong; they cover different halves of the freshness-versus-stability line.
From live calls to a pinned dataset
The free 22-row sample on this site is the first 22 rows of that same games.csv lineage, and the full pack ships the whole table preprocessed with the derived tables, the column guide, and SHA-256 checksums so the snapshot can be verified like the release file can. For a whole page on the ESPN side of that pairing, the ESPN API in Python guide covers the scoreboard and team endpoints. Raw nflverse route vs preprocessed pack, laid out in detail: the nflverse comparison guide.
Checkout and download run through Getly. The full pack is 3 CSVs (7,548 + 861 + 27 rows), data dictionary, source SHA-256 checksums. The sample downloads free here, no signup.
NFL data API questions
- Is there a free NFL API for Python?
- Yes, two of them work without keys. ESPN's site API returns a week of scores and fixtures as JSON, and the nflverse-data releases publish the full
games.csvthat a plainrequestscall can download. The league itself runs no official public API; the paid commercial feeds are separate products. Both free routes were checked live on 2026-09-10 and returned 200. - Do I need an API key for the ESPN scoreboard or the nflverse files?
- No for either. A
requests.getcall with no auth headers returned 200 for both on 2026-09-10. The difference is the contract: ESPN's endpoint is undocumented, so its response shape can change without notice, while the nflverse file is a full 2.1 MB download on every call, with the update time recorded in itstimestamp.json. - Should my script call a live API or load a snapshot CSV?
- Use the live route for the current week's scoreboard and the snapshot for any analysis you rerun over days or weeks. The nflverse file costs a 2.1 MB download per run and the ESPN shape can shift, while a snapshot CSV keeps a pinned schema, ships with SHA-256 checksums and derived tables like team records and season summaries, and works offline. Many scripts use both: live results on top of a pinned history.
Sources and license
All game rows trace back to the nflverse-data project, file schedules/games.csv, released under CC BY 4.0; credit it as "Data from nflverse, CC BY 4.0". Samples on this site carry the same license. The ESPN endpoint is a public site API with no documentation contract behind it. No logos or league marks appear anywhere in the pack; team names are factual data. This page and the pack describe data only and offer no betting advice, picks, or predictions.