ESPN API in Python: requests examples for the free site endpoints
One requests call pulls a week of NFL results out of ESPN's public site API. 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. The API powers ESPN's own site and apps, and its public endpoints answer to any client. The sections below map the endpoints worth calling from Python, show working examples, and list the limits of an undocumented API.
The endpoints, checked live
All of them live under https://site.api.espn.com/apis/site/v2/sports/. The league segment, football/nfl in the URLs below, is the only part that changes when you move between sports. Each row was hit on the checked date:
| Endpoint | What it returns | Auth | Checked 2026-09-10 |
|---|---|---|---|
/football/nfl/scoreboard | One week of fixtures and results, nested JSON | none | 200, 16 events, 138 KB |
/football/nfl/teams | All 32 clubs with name, colors, and logos | none | 200, 148 KB |
/football/nfl/teams/kc | One club, same team object | none | 200 |
/basketball/nba/scoreboard | Same shape, different league | none | 200, 1 upcoming event |
The NBA row is the same URL with basketball/nba swapped in, and it returned the October 3 preseason opener on the checked date. Other leagues follow the same pattern; only the two above are claimed here because only those two were tested.
Example 1: the scoreboard, with scores
The quick-answer call again, with the score lines pulled out. Competitors sit under each event's competitions list, and the homeAway key is the only reliable way to tell the two sides apart, because 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()
for e in r.json()["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 those two games 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. Two field facts that save debugging time: score arrives as a string, not a number, and the deep nesting is a display feed, built for ESPN's pages, so read the fields you need instead of assuming the whole structure stays put.
Example 2: the teams list and one club
The teams endpoint returns every club in one call. The 32 abbreviations it hands back are worth keeping around:
import requests
BASE = "https://site.api.espn.com/apis/site/v2/sports/football/nfl"
r = requests.get(f"{BASE}/teams", timeout=30)
r.raise_for_status()
teams = r.json()["sports"][0]["leagues"][0]["teams"]
abbrs = [t["team"]["abbreviation"] for t in teams]
print(r.status_code, len(abbrs)) # 200 32
print(" ".join(abbrs))
# ARI ATL BAL BUF CAR CHI CIN CLE DAL DEN DET GB HOU IND JAX KC LV
# LAC LAR MIA MIN NE NO NYG NYJ PHI PIT SF SEA TB TEN WSH
That list matches the home_team and away_team columns in the nflverse games.csv, and the abbreviations inside scoreboard output, so live results and historical rows join without a mapping table. A single club comes back from the same path with an abbreviation appended:
r = requests.get(f"{BASE}/teams/kc", timeout=30)
r.raise_for_status()
kc = r.json()["team"]
print(kc["displayName"], kc["abbreviation"]) # Kansas City Chiefs KC
print(kc["color"]) # e31837
The color hex arrives without the # prefix, and the same team object carries logos, links, and a record summary that resets with the season calendar; it read 0-0 on the checked date, between seasons. Treat anything on this object as current state, not history.
The honest limits
The scoreboard and teams endpoints are free and open, and three caveats decide whether they fit your project:
First, the API is undocumented. There is no published schema and no versioning promise, so ESPN can change field names, nesting, or response size without notice. The examples here ran clean on 2026-09-10, and the same shape held across every call, but that is an observation, not a contract.
Second, no key does not mean no limits. The endpoint publishes no rate limit, so the safe pattern is a timeout on every call, caching responses you already have, and backing off on errors instead of hammering retries. A scoreboard pull per script run is a fraction of a request per minute; a scraping loop is how undocumented endpoints get shut.
Third, the API covers now, not history. The scoreboard returns weeks, the teams endpoint returns current state, and neither gives you 1999-2026 game rows with closing lines. For that, the pinned snapshot routes in the NFL data API in Python guide cover the gap, and the next section shows where the snapshot lives.
From live calls to a pinned dataset
A common shape for a working script: ESPN for this week's scoreboard, a pinned CSV for every game before this season. The free 22-row sample on this site carries the same schema as the full games.csv, and the full pack ships 7,548 games with spreads, totals, and moneylines, plus derived tables and a column 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.
ESPN API questions
- Is the ESPN API free to use from Python?
- Yes for the site endpoints. The scoreboard and team URLs under
site.api.espn.comanswer a plainrequests.getcall with no account, no key, and no headers, and both returned 200 on 2026-09-10. ESPN sells developer products with licensed feeds on top, but the site API itself costs nothing to call. - Does the ESPN API need an API key?
- Not for the endpoints shown here. The scoreboard, teams, and per-team calls all returned 200 with an unauthenticated
requests.geton the checked date. The flip side of no key is no contract. There is no published rate limit, so keep a timeout on every call, cache responses you already have, and back off on errors instead of retrying in a tight loop. - Is the ESPN site API official and stable?
- It is public and served by ESPN, but it is undocumented: there is no published schema, no versioning promise, and the response shape can change without notice. Scores arrive as strings, nesting runs deep, and fields like a team's record summary reset with the season calendar. For analysis you rerun over months, pair the live calls with a pinned snapshot CSV so joins and columns hold between runs.
Sources and license
The ESPN site API is a public, undocumented endpoint served by ESPN; this page makes no claim on its content or continued availability. Game rows referenced as history 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". Team names are factual data; no logos or league marks appear anywhere in the pack. This page and the pack describe data only and offer no betting advice, picks, or predictions.