GuidesESPN
ESPN API from JavaScript
ESPN's hidden API is free JSON with no key that browsers can call directly: in all 10 leagues we tested, scores, news, standings and schedules on site.api.espn.com sent Access-Control-Allow-Origin: *. Don't put a CORS proxy in front of it: of the 12,140 requests to site.api.espn.com through cors.dev in the 14 days to October 18, 2026, 11,237 got a 403 from ESPN and not one returned data. Two endpoints, /teams and /teams/{team}, never sent a CORS header and depth charts only sometimes did; those need your own server.
Call ESPN directly, not through a proxy
ESPN closed its official developer API in 2014. What developers now call the hidden ESPN API is a set of undocumented JSON endpoints, mostly under https://site.api.espn.com/apis/site/v2/sports/{sport}/{league}. They take no key, no signup and no auth header, and most of them answer every origin with Access-Control-Allow-Origin: *, so a plain fetch() works from any page, including localhost and GitHub Pages.
We sent each request with four different Origin headers, including http://localhost:5173 and a github.io domain, and got the header every time.
Why a CORS proxy gets 403 Access Denied
ESPN's CDN refuses requests from cors.dev's servers on Cloudflare. In our tests, every request through https://proxy.cors.dev/ to site.api.espn.com came back as an Akamai error page instead of JSON:
GET https://proxy.cors.dev/https://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard
403
content-type: text/html
x-cors-source: upstream
<HTML><HEAD>
<TITLE>Access Denied</TITLE>
</HEAD><BODY>
<H1>Access Denied</H1>
You don't have permission to access "http://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard" on this server.<P>
Reference #18.8ccf3617.1792365497.19b29ac9
<P>https://errors.edgesuite.net/18.8ccf3617.1792365497.19b29ac9</P>
</BODY>
</HTML>X-Cors-Source: upstream means the 403 came from ESPN, not from the proxy. site.web.api.espn.com and sports.core.api.espn.com answered with a shorter 403 Forbidden page. In our logs from October 4 to 18, 2026, site.api.espn.com answered every proxied request with 403, while sports.core.api.espn.com still worked through cors.dev on October 10 and 11 and returned 403 by October 18.
If your page loads cors.dev Auto, its default native-first mode keeps ESPN calls direct: Auto sends each request natively first and tries the proxy only when the browser blocks it. See Auto routing modes.
Live scores from the scoreboard
/scoreboard returns the current games for a league with teams, scores, status, leaders and broadcasts. On October 18, 2026 the NFL scoreboard held 14 games in 224 KB. Add ?dates=20261018 for one day or ?seasontype=2&week=7 for one NFL week.
// Markup: <ul id="scores"></ul>
// Other leagues: basketball/nba, baseball/mlb, hockey/nhl, soccer/eng.1
const league = 'football/nfl';
async function espn(path) {
const response = await fetch(`https://site.api.espn.com/apis/site/v2/sports/${league}${path}`);
if (!response.ok) {
throw new Error(`ESPN request failed: ${response.status}`);
}
return response.json();
}
function describe(event) {
const { competitors } = event.competitions[0];
const away = competitors.find((side) => side.homeAway === 'away');
const home = competitors.find((side) => side.homeAway === 'home');
const { state, shortDetail } = event.status.type;
if (state === 'pre') {
return `${away.team.abbreviation} at ${home.team.abbreviation}, ${shortDetail}`;
}
return `${away.team.abbreviation} ${away.score}, ${home.team.abbreviation} ${home.score} (${shortDetail})`;
}
const list = document.querySelector('#scores');
espn('/scoreboard')
.then(({ events }) => {
if (events.length === 0) {
list.textContent = 'No games scheduled';
return;
}
list.replaceChildren(
...events.map((event) => {
const item = document.createElement('li');
item.textContent = describe(event);
return item;
}),
);
})
.catch((error) => {
list.textContent = error.message;
});status.type.state is pre, in or post, and shortDetail is a ready-made label such as Final, FT, 3:52 - 4th or a start time in US Eastern time. Scores are strings on the scoreboard. For one game in depth, /summary?event=401873004 adds the box score, drives, scoring plays, odds and win probability; the event id is events[].id on the scoreboard.
A team's schedule and results
/teams/{team}/schedule returns one team's season with dates, opponents and final scores. Abbreviations work in place of ids: teams/kc/schedule and teams/12/schedule both return the same 277 KB for the Kansas City Chiefs.
// Uses espn() from the example above, in a module script or the DevTools console
// Ids and abbreviations both work: teams/12 and teams/kc are the Kansas City Chiefs
const { team, events } = await espn('/teams/kc/schedule');
console.log(team.displayName, team.recordSummary, team.standingSummary);
console.table(
events.map((event) => {
const competition = event.competitions[0];
const us = competition.competitors.find((side) => side.team.id === team.id);
const them = competition.competitors.find((side) => side.team.id !== team.id);
const { completed, shortDetail } = competition.status.type;
const outcome = us.winner ? 'W' : them.winner ? 'L' : 'T';
return {
week: event.week?.text,
date: new Date(event.date).toLocaleDateString(),
opponent: (us.homeAway === 'home' ? 'vs ' : 'at ') + them.team.abbreviation,
result: completed ? `${outcome} ${us.score.displayValue}-${them.score.displayValue}` : shortDetail,
};
}),
);Unlike the scoreboard, scores here are objects with value and displayValue, and games in progress or not yet played have no score. Rosters work the same way: /teams/kc/roster lists players in groups such as offense, defense, special teams and injured reserve.
ESPN API endpoints and response sizes
Paths are on https://site.api.espn.com/apis/site/v2/sports/ unless a host is given. Event 401873004 is the Cardinals at Rams game of October 18, 2026, and athlete 4373626 is Tyler Allgeier. Every endpoint in the table returned 403 through cors.dev.
| Path | Returns | Size | Browser |
|---|---|---|---|
| football/nfl/scoreboard | This week's games: teams, scores, status, leaders | 224 KB, 14 games | Works directly |
| football/nfl/scoreboard?dates=20261018 | Games on one date | 195 KB, 12 games | Works directly |
| football/nfl/summary?event=401873004 | One game: box score, drives, scoring plays, win probability | 570 KB | Works directly |
| football/nfl/news | Latest headlines | 23 KB | Works directly |
| football/nfl/teams/kc/schedule | One team's season with results | 277 KB, 17 games | Works directly |
| football/nfl/teams/kc/roster | Players by unit, plus injured reserve and practice squad | 354 KB | Works directly |
| football/nfl/injuries | Injury report for every team | 8.7 MB | Works directly |
| football/college-football/rankings | AP Top 25 and coaches poll | 289 KB | Works directly |
| site.api.espn.com/apis/v2/sports/football/nfl/standings | AFC and NFC standings | 158 KB | Works directly |
| site.web.api.espn.com/apis/common/v3/sports/football/nfl/athletes/4373626 | One player's profile and stats | 83 KB | Works directly |
| sports.core.api.espn.com/v2/sports/football/leagues/nfl/teams/12 | Team record with links to related data | 10 KB | Works directly |
| football/nfl/teams | All 32 teams with ids, abbreviations and logos | 149 KB | Blocked: no CORS header |
| football/nfl/teams/kc | One team: record, logos, next game | 23 KB | Blocked: no CORS header |
| football/nfl/teams/kc/depthcharts | Depth chart by position | 216 KB | Blocked: no CORS header |
The split held across all 10 leagues: scoreboards, news, schedules, rosters and standings always sent the header, and /teams and /teams/{team} never did.
| League | Path | Scoreboard |
|---|---|---|
| NFL | football/nfl | 224 KB, 14 games |
| NBA | basketball/nba | 41 KB, 3 games |
| WNBA | basketball/wnba | 25 KB, 1 game |
| MLB | baseball/mlb | 19 KB, 1 game |
| NHL | hockey/nhl | 42 KB, 2 games |
| College football | football/college-football | 262 KB, 16 games |
| Men's college basketball | basketball/mens-college-basketball | 15 KB, 2 games |
| Premier League | soccer/eng.1 | 46 KB, 4 games |
| MLS | soccer/usa.1 | 14 KB, 1 game |
| Champions League | soccer/uefa.champions | 113 KB, 9 games |
Endpoints without CORS headers need a server
/teams and /teams/{team} send no Access-Control-Allow-Origin header. Depth charts are unreliable: the NFL's never sent it in our checks, while NBA and MLB depth charts sent it on some requests and not on others. The browser blocks any response without the header, and a CORS proxy does not help, because ESPN refuses the proxy too. Fetch these from your own backend and pass the JSON to your page. cors.dev runs on Cloudflare Workers and gets the 403, so a Cloudflare Worker may too; from our test server outside Cloudflare, all three answered 200.
Team ids, names, abbreviations and logos also come inside the scoreboard, schedule and roster responses, which do send the header, so many apps never need /teams.
MLB Stats API and the NHL
The MLB Stats API at statsapi.mlb.com also sends Access-Control-Allow-Origin: *, so call it directly too. /api/v1/schedule?sportId=1 lists today's games, /api/v1/standings?leagueId=103,104 returns both leagues in 81 KB and /api/v1/teams/147/roster is the New York Yankees roster in 10 KB. Responses carry a copyright notice from MLB Advanced Media that points to its terms of use.
The NHL's own API is different: api-web.nhle.com sends no CORS header, so it does need a proxy; see NHL API from JavaScript. ESPN's hockey/nhl endpoints send the header and work directly.
Limits and polling
- No documentation: ESPN publishes no docs or rate limits for these endpoints, and nothing guarantees they stay the same. Check
response.okand show a fallback when a request fails. - Polling: the scoreboard is cached for 1 to 8 seconds (
Cache-Controlmax-age), so polling every 10 to 30 seconds is enough for live games. - Size: a scoreboard is 14 KB to 262 KB, but the NFL injury report is 8.7 MB and the men's college basketball standings 6.2 MB, so request the narrowest endpoint you need.
Good questions.
Is there an official ESPN API?
Not anymore. ESPN stopped issuing public API keys in August 2014 and switched off the existing keys in December 2014. The hidden ESPN API is the set of undocumented JSON endpoints on site.api.espn.com, site.web.api.espn.com and sports.core.api.espn.com, which work without a key.
Does the ESPN API need an API key?
No. None of the endpoints on this page take a key, a token or a login. A plain GET from the browser returns JSON.
Does the ESPN API support CORS?
Mostly. Scoreboard, summary, news, standings, rankings, roster, schedule, injuries and athlete endpoints send Access-Control-Allow-Origin: *. The /teams list and the single-team endpoint send no CORS header, and depth charts send it only some of the time, so read those from a server.
Why does the ESPN API return 403 Access Denied?
ESPN's CDN answers requests from cors.dev, which runs on Cloudflare, with a 403 page instead of JSON. Call ESPN directly from the browser, where the same request returns 200 with CORS headers.
What is the ESPN API endpoint for NBA scores?
https://site.api.espn.com/apis/site/v2/sports/basketball/nba/scoreboard. Swap basketball/nba for football/nfl, baseball/mlb, hockey/nhl or soccer/eng.1 for other leagues. It sends CORS headers, so fetch it directly.
How do I get ESPN team ids?
Scoreboard, schedule and roster responses include team ids, and abbreviations such as kc or bos work in their place in team URLs. The /teams list has the full set but sends no CORS header, so read it from a server.
Is the MLB Stats API free to call from the browser?
It needs no key, and statsapi.mlb.com sends Access-Control-Allow-Origin: *, so fetch it directly without a proxy. Its responses include a copyright notice pointing to MLB's terms for using the data.