Last updated August 21, 2026.
This document explains how to retrieve useful NFL data from ESPN's public-facing APIs.
ESPN does not officially document or guarantee these endpoints. Treat every response as an observed shape rather than a permanent contract. Use the APIs read-only, tolerate missing fields, limit concurrency, retry transient failures, and cache where appropriate.
ESPN exposes two useful but structurally different API families:
Core API
https://sports.core.api.espn.com/v2/sports/football/leagues/nfl
Site API
https://site.api.espn.com/apis/site/v2/sports/football/nfl
- The Core API is reference-oriented. Collection responses often contain
itemswhose useful value is a$refURL that must be fetched separately. - The Site API is presentation-oriented. Its roster, scoreboard, summary, and injury responses tend to embed the objects needed by ESPN's site.
- Do not apply a schema observed on one family to the other.
- Follow
$reflinks when the collection only returns references; do not resolve references that are unnecessary for the desired result.
| Data | Endpoint family | Status in this project |
|---|---|---|
| Teams | Core | Production verified |
| Current team rosters | Site | Production and research verified |
| Weekly event discovery | Core | Production verified |
| Game summary and boxscore | Site | Production and research verified |
| League injuries and player notes | Site | Production verified |
| Historical scoreboards | Site | Research verified for 2023-2025 |
| Core athlete enumeration | Core | Known pattern; not the current indexing path |
| Standalone Core boxscore | Core | Known pattern; not used by current workflows |
| Season statistics | Core | Not recently verified |
| Depth charts | Core/Site | Not recently verified |
| Event odds | Core | Not recently verified |
GET https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/teams?limit=100The collection is typically reference-based:
{
"items": [
{ "$ref": "https://sports.core.api.espn.com/.../teams/12?..." }
],
"next": { "$ref": "..." }
}Follow each required items[*].$ref. If next.$ref exists, continue until there is no next page.
Useful resolved fields include:
{
"id": "12",
"name": "Kansas City Chiefs",
"abbreviation": "KC",
"logos": [
{ "href": "https://a.espncdn.com/i/teamlogos/nfl/500/kc.png" }
]
}Normalize all IDs to strings. Team IDs are useful for joining rosters, weekly events, summaries, and injury groups.
First list and resolve teams, then request each team's Site API roster:
GET https://site.api.espn.com/apis/site/v2/sports/football/nfl/teams/{teamId}/rosterThe response embeds athletes in groups such as offense, defense, and special teams:
{
"athletes": [
{
"items": [
{
"id": "3139477",
"fullName": "Patrick Mahomes",
"position": { "abbreviation": "QB" },
"headshot": { "href": "https://a.espncdn.com/..." },
"status": { "type": "active", "name": "Active" }
}
]
}
]
}Attach the requested team ID and resolved team abbreviation to every athlete. Do not assume headshot is a string; it is commonly an object containing href.
This strategy avoids resolving a separate Core API reference for every athlete. It is effective for a current fantasy-player index, but it may omit free agents because they are not on a team roster.
The Core API also exposes an athlete collection:
GET https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/athletes?active=true&limit=500This collection is paginated and commonly returns athlete $ref pointers. Follow next.$ref and resolve the athlete references needed by the consumer. This route can provide broader league coverage, but it creates substantially more requests than the team-roster flow and has not been the current project's production indexing path.
No true ESPN player-name search endpoint has been established by this project. Build a local index keyed by athlete ID and normalized name, then apply prefix, substring, or fuzzy matching locally.
Current team data is not historical truth. A roster fetched today may show a player's new team even when analyzing a prior season.
GET https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/seasons/{season}/types/{seasonType}/weeks/{week}/events?limit=50Verified season types:
2: regular season3: postseason
The collection contains event $ref pointers. Resolve those references before expecting competition details.
For each resolved event:
{
"id": "401671345",
"competitions": [
{
"competitors": [
{
"id": "12",
"team": { "$ref": ".../teams/12?..." },
"homeAway": "home"
},
{
"id": "24",
"team": { "$ref": ".../teams/24?..." },
"homeAway": "away"
}
]
}
]
}Observed competitor responses are not perfectly consistent. Prefer competitor.id when present; otherwise extract the final path segment from competitor.team.$ref and remove its query string.
Build a map from both competitor team IDs to the event ID. To find an opponent, locate the event containing the desired team and select the other competitor. No matching event normally means a bye, an invalid week/season combination, or incomplete upstream data.
Do not infer the available week range from scheduling conventions. If a consumer needs dynamic selectors, obtain season/week metadata or validate event availability for the selected season type.
GET https://site.api.espn.com/apis/site/v2/sports/football/nfl/summary?event={eventId}This is the most useful verified single-game payload in this project. It can include:
header.competitions[0]: date, competitors, scores, winners, and statusboxscore.players: team-grouped player statisticsscoringPlays: scoring type, text, team, and running scoreheader.week: the NFL week associated with the event
Useful status fields include:
{
"date": "2026-09-11T00:20Z",
"status": {
"type": {
"name": "STATUS_FINAL",
"description": "Final",
"detail": "Final",
"completed": true
}
}
}Use status.type.completed === true as the strongest completion signal, with status.type.name === "STATUS_FINAL" as a practical fallback. Do not infer finality from a score, a winner flag alone, or a scheduled 0-0.
Scheduled events may have no player boxscore rows. Live responses can be partial and can change between requests. Final data is usually stable but ESPN can still make stat corrections, so “final” should not be treated as mathematically immutable.
Core event and boxscore resources are also known patterns:
GET https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/events/{eventId}
GET https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/events/{eventId}/competitions/{competitionId}/boxscoreThese have not been the current project's active scoring path. Do not assume the standalone Core boxscore has the same nesting as the Site API summary, and discover the competition ID from the event instead of relying on it always matching the event ID.
Within a Site API summary, player statistics are organized by team and statistic group:
boxscore.players[*]
.team
.statistics[*]
.name
.labels[]
.athletes[*]
.athlete.id
.athlete.displayName
.stats[]
The meaning of stats[n] comes from labels[n]. Never rely on a fixed array position.
{
"name": "passing",
"labels": ["C/ATT", "YDS", "AVG", "TD", "INT"],
"athletes": [
{
"athlete": { "id": "3139477", "displayName": "Patrick Mahomes" },
"stats": ["28/42", "315", "7.5", "3", "1"]
}
]
}Normalize by zipping labels and stats:
const statMap = Object.fromEntries(
group.labels.map((label, index) => [label, athlete.stats[index]])
);The extracted record can then be scoring-system neutral:
{
"athleteId": "3139477",
"passingYards": 315,
"passingTouchdowns": 3,
"interceptions": 1
}Common group names observed in NFL summaries include passing, rushing, receiving, and kicking. Common labels include YDS, TD, CAR, REC, TGTS, and XP, but consumers must still read the supplied labels.
Do not restrict extraction to the athlete's listed fantasy position. A running back can record a passing statistic, and other cross-position plays are possible.
Aggregate boxscore totals do not necessarily preserve the distance or attribution needed for every scoring event. The Site API summary's scoringPlays array provides fields such as:
{
"id": "play-id",
"type": {
"text": "Passing Touchdown",
"abbreviation": "TD"
},
"text": "Receiver Name 45 Yd pass from Quarterback Name",
"team": { "id": "12" },
"awayScore": 7,
"homeScore": 14
}Practical interpretation:
- Use athlete IDs to locate the correct boxscore participant first.
- Use scoring-play text only for information not otherwise keyed, such as scorer name, passer name, or play distance.
- A distance can commonly be parsed with
/([0-9]+)\s+Yd/i, but missing or differently worded descriptions must be tolerated. - Passing touchdown text commonly starts with the receiver and contains
pass from {passer}. - Two-point conversions may appear in parenthetical text rather than as a separate normalized record.
- Defensive and return plays require inspecting both
type.textand the description.
ESPN labels and text are not perfectly normalized. Observed examples include blocked-field-goal return wording and the typo Touchown Return. Keep raw event IDs and descriptions available for debugging rather than silently discarding unmatched scoring plays.
GET https://site.api.espn.com/apis/site/v2/sports/football/nfl/injuriesThe observed payload is grouped approximately as:
injuries[]
.injuries[]
.date
.status
.type.abbreviation
.type.description
.shortComment
.longComment
.athlete.links[]
.athlete.notes.items[]
This is not purely a list of currently injured players. It can blend official-looking designations with general player-status notes.
An embedded athlete.id has not been reliable in the payloads used by this project. The athlete ID can be extracted from an athlete link whose path contains /id/{athleteId}:
const match = link.href?.match(/\/id\/(\d+)(?:\/|$)/);type.abbreviationcan contain a designation such asQ,O, orIR.type.descriptionorstatuscan provide its human-readable label.- An abbreviation of
Arepresents active and should not be displayed as an injury designation. shortCommentandlongCommentsometimes only repeat the designation. Consumers may suppress those duplicates.- The same athlete can have multiple records. Select or merge by parsed date rather than response order.
Player notes can be embedded under athlete.notes.items. Observed useful fields include:
{
"id": "note-id",
"type": "news",
"date": "2026-08-18T09:00Z",
"headline": "Player returns to practice",
"text": "...",
"source": "RotoWire"
}These notes are status-feed summaries, not necessarily standalone ESPN articles. Deduplicate them, validate their dates, and apply a consumer-appropriate freshness window. The current application uses 30 days, but that is an application policy rather than ESPN API behavior.
The presence of an athlete in this endpoint does not prove the athlete is injured, and the absence of a designation does not guarantee availability. Offseason reporting can be especially different from official in-season game designations.
The Core-style pattern below has appeared in prior notes but has not been recently verified by this project:
GET https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/teams/{teamId}/injuriesPrefer the verified league Site API feed unless a team-specific workflow has independently confirmed the endpoint and response shape.
The verified current-roster endpoint is:
GET https://site.api.espn.com/apis/site/v2/sports/football/nfl/teams/{teamId}/rosterCurrent rosters are useful for present-day team and position metadata. They must not overwrite historical team attribution obtained from an old game summary.
Core-style roster and depth-chart patterns have appeared in prior references:
GET https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/teams/{teamId}/roster
GET https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/teams/{teamId}/depthchartsThese paths and their response shapes have not been recently verified here. Inspect current team resources for relevant $ref links and validate the payload before building a dependency on them.
Earlier versions of this guide listed the following pattern:
GET https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/athletes/{athleteId}/stats?season={season}&seasontype={seasonType}It has not been recently verified and should not be treated as a stable contract. When season totals are needed:
- Inspect the resolved athlete and season resources for current statistics
$reflinks. - Confirm the returned categories, labels, season, and season type.
- Preserve the query and raw payload used to generate any analysis.
- For custom scoring or auditable historical work, prefer aggregating verified game summaries. Season totals often omit the play-level detail needed for distance-based scoring.
A known Core API pattern is:
GET https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/events/{eventId}/competitions/{competitionId}/oddsThis endpoint has not been recently verified by this project. Odds can be provider-specific, can move over time, and may be missing or delayed. A consumer that needs an auditable frozen line must capture the provider, value, and timestamp rather than repeatedly querying the current value.
The KJFFL Upset Special deliberately uses a manually supplied league line. ESPN odds must not silently replace that user-supplied value.
For broad historical discovery, the Site API scoreboard has been effective:
GET https://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard?dates={calendarYear}&seasontype=2&limit=1000An NFL season crosses calendar years. To gather a season completely:
- Query
dates={season}. - Query
dates={season + 1}to capture January games. - Combine and deduplicate events by event ID.
- Keep only events where
event.season.year === seasonandevent.season.type === 2. - Fetch
/summary?event={eventId}for every retained event.
Cache each scoreboard and summary by season/event ID. Historical replay can require hundreds of requests, so use bounded concurrency; the project research script uses eight workers.
Current roster metadata may help label a player's present position, but historical team, opponent, and week attribution must come from the historical summary. Retain raw summaries or a compact normalized weekly record so disputed results can be reconstructed.
A common weekly flow is:
Athlete ID
-> current or historical team ID
-> weekly event ID
-> Site API game summary
-> team boxscore/stat group
-> athlete stat line and scoring plays
Recommended keys:
| Entity | Key and caution |
|---|---|
| Athlete | Normalize ESPN athlete ID to a string; do not join by name alone |
| Team | Normalize ESPN team ID to a string; abbreviations can change |
| Event | ESPN event ID identifies the game |
| Competition | Discover from the event; do not universally assume it equals the event ID |
| D/ST | Using team ID as a fantasy-player ID is a consumer convention, not an ESPN athlete identity |
Names remain useful for matching human-readable scoring-play text, but the athlete ID should establish which player is being evaluated first.
- Retry network failures,
429, and transient5xxresponses with bounded backoff. - Respect
Retry-Afterwhen present. - Do not repeatedly retry permanent validation or not-found responses without changing the request.
- Keep concurrency modest when resolving collections or replaying seasons.
| Data | Volatility | Practical guidance |
|---|---|---|
| Teams and IDs | Very low | Cache for a season; refresh occasionally |
| Current rosters | Medium | Refresh daily or when transactions matter |
| Player search index | Low/medium | Daily is usually sufficient |
| Future schedules | Medium | Refresh as scheduling changes matter |
| Live summaries | High | Poll conservatively while a game is active |
| Final summaries | Low | Cache long-term, while allowing stat corrections |
| Injury/status feed | High | Refresh more frequently near lineup decisions |
| Historical raw summaries | Very low | Cache by event ID for reproducibility |
| Odds | Very high | Store timestamp and provider with every captured value |
There is no documented service-level or rate-limit guarantee. “Cache aggressively” should not mean serving stale injury or live-game information without communicating its age.
- Assuming a Core collection embeds entities when it only returns
$refpointers. - Assuming Site and Core payloads share the same nesting.
- Treating stat array positions as fixed instead of mapping
labelstostats. - Treating a scheduled
0-0as a final shutout. - Expecting boxscore players to exist before kickoff.
- Treating every entry in
/injuriesas an active injury designation. - Using current roster membership to assign a historical game to a team.
- Restricting stat extraction to a player's nominal position.
- Assuming a missing event always means a bye without checking season type and week validity.
- Assuming final data can never be corrected.
- Using a current odds value when the business rule requires a frozen historical line.
- Joining scoring-play text by a short or ambiguous name without first locating the ESPN athlete ID.
- List and resolve Core teams.
- Fetch each Site team roster.
- Flatten athlete groups and attach team ID/abbreviation.
- Normalize desired positions and athlete names.
- Add team-based D/ST records if the consumer needs them.
- Cache the index and search locally.
- Determine the relevant team ID for that season/week.
- Fetch and resolve the Core weekly event collection.
- Map the team ID to an event ID.
- Fetch the Site game summary.
- Find the team, statistic group, and athlete ID.
- Zip
labelstostatsand normalize values. - Use scoring plays only when play-level detail is needed.
- Fetch the Site league
/injuriesfeed. - Flatten team injury groups.
- Extract athlete IDs from athlete links.
- Separate designation fields from embedded
type: "news"notes. - Exclude active-only designations where appropriate.
- Parse dates, select the newest designation, deduplicate notes, and apply an explicit freshness policy.
- Fetch Site scoreboards for the season year and next calendar year.
- Filter by ESPN season year/type and deduplicate event IDs.
- Download and cache each Site summary with bounded concurrency.
- Normalize weekly players, teams, boxscores, and scoring plays.
- Aggregate only after preserving enough weekly data for later auditing.
If you remember five things:
- Core collections are often
$refgraphs; Site responses are usually embedded presentation payloads. - Join by ESPN IDs and normalize them to strings.
- Map stat labels dynamically.
- Treat scheduled, live, final, missing, and revised data as distinct states.
- Preserve raw event IDs and payloads when reproducibility matters.