GuidesSPORTS APIS
Free sports API for a JavaScript project
For a JavaScript project, 6 free sports APIs work straight from the browser with no private key: TheSportsDB with its free key 123, MLB StatsAPI, ESPN's scoreboard, OpenF1, Jolpica F1 and Sportscore. We tested 12 sports APIs in Chromium, Firefox and WebKit on October 26, 2026; API-Football and balldontlie need a key you keep on a server, football-data.org and SportMonks send no usable CORS headers, and NBA.com refuses outside callers.
Which free sports API to use
Use TheSportsDB for many sports with team badges, MLB StatsAPI for baseball and the ESPN scoreboard for NBA scores. Formula 1 has two options: OpenF1 for sessions since 2023, and Jolpica F1 for full F1 history in the old Ergast format. For football, basketball, cricket or tennis with no key, Sportscore works from the browser. For deeper football data from a server, pick API-Football or football-data.org.
The Fantasy Premier League API and the NHL API have their own guides, and there is a bigger list of 70 free public APIs tested for CORS.
| API | Sports | Key | CORS from the browser | Free limit |
|---|---|---|---|---|
| TheSportsDB | Soccer, basketball, motorsport and more | Public test key 123 | Yes, any origin | 30 requests per minute, trimmed results |
| MLB StatsAPI | Baseball (MLB) | None | Yes, any origin | Not published, non-commercial use only |
| ESPN site API | NBA, MLB, NHL, soccer and more | None | Yes, but it blocks proxies | Not published, unofficial |
| OpenF1 | Formula 1 since 2023 | None | Yes, any origin | 3 per second, 30 per minute, live data paid |
| Jolpica F1 | Formula 1, Ergast format | None | Yes, any origin | 4 per second, 500 per hour |
| Sportscore | Football, basketball, cricket, tennis | None, attribution link | Yes, any origin | About 10,000 per day per IP |
| API-Football | Football (soccer) | Private key header | Only when the key header is sent | 100 requests per day |
| balldontlie | NBA, WNBA and more, one sport free | Private key header | Yes, echoes your origin | 5 requests per minute |
| football-data.org | Football, 12 competitions free | Private token header | No, broken preflight | 10 calls per minute |
| SportMonks | Football, 2 leagues free | Private token | No CORS headers | 3,000 calls per entity per hour |
| stats.nba.com | NBA | None | No answer to outside callers | Timed out on October 26, 2026 |
| cdn.nba.com | NBA | None | No, 403 Access Denied | Blocked |
TheSportsDB with the free key
TheSportsDB puts its free test key 123 in the URL path, as in /api/v1/json/123/searchteams.php?t=Arsenal. The old key 3 is disabled and now returns 403. The free key allows 30 requests per minute and trims results: a team search returns 1 team, eventslast.php and eventsnext.php return 1 match each, and a league table returns 5 rows. Premium costs $9 a month for 100 requests per minute and adds the v2 API, which takes the key in an X-API-KEY header.
// Runs in a module script or the DevTools console
// TheSportsDB sends Access-Control-Allow-Origin: *, so call it directly
const base = 'https://www.thesportsdb.com/api/v1/json/123/';
async function sportsDb(path) {
let response;
try {
response = await fetch(base + path);
} catch {
// Its 429 has no CORS header, so going over the free limit looks like a network error
throw new Error('Blocked or offline: the free key allows 30 requests a minute, retry in 2 minutes');
}
if (!response.ok) throw new Error(`TheSportsDB answered ${response.status}`);
return response.json();
}
const { teams } = await sportsDb('searchteams.php?t=' + encodeURIComponent('Arsenal'));
const team = teams?.[0];
if (!team) throw new Error('No team with that name');
const [last, next] = await Promise.all([
sportsDb('eventslast.php?id=' + team.idTeam),
sportsDb('eventsnext.php?id=' + team.idTeam),
]);
console.log(team.strTeam, team.strLeague, team.strBadge);
console.table([...(last.results ?? []), ...(next.events ?? [])].map((event) => ({
date: event.dateEvent,
match: event.strEvent,
score: event.intHomeScore === null ? '' : `${event.intHomeScore}-${event.intAwayScore}`,
status: event.strStatus,
})));Over the limit, TheSportsDB answers with a Cloudflare 429 (error code 1015, Retry-After: 120) that has no Access-Control-Allow-Origin header, so the browser reports a CORS or network error instead of a 429. In our burst of 40 parallel requests, 12 got 200 and 28 got that 429. Call it directly: it already sends CORS headers, and behind a shared proxy every user shares the same limit.
MLB StatsAPI schedule
MLB StatsAPI at statsapi.mlb.com/api/v1 needs no key and sends Access-Control-Allow-Origin: *. The schedule call below, with startDate, endDate and hydrate=team,linescore, returned 3 games for October 20 to 26, 2026. Every response points to MLB's copyright terms, which permit only individual, non-commercial, non-bulk use. The ESPN API guide lists more StatsAPI endpoints for standings and rosters.
// Runs in a module script or the DevTools console
// statsapi.mlb.com sends Access-Control-Allow-Origin: *, no key needed
const params = new URLSearchParams({
sportId: '1',
startDate: '2026-10-20',
endDate: '2026-10-26',
hydrate: 'team,linescore',
});
const response = await fetch('https://statsapi.mlb.com/api/v1/schedule?' + params);
if (!response.ok) throw new Error(`MLB StatsAPI answered ${response.status}`);
const { dates } = await response.json();
console.table(dates.flatMap((day) => day.games).map((game) => ({
date: game.officialDate,
away: game.teams.away.team.name,
home: game.teams.home.team.name,
score: `${game.teams.away.score ?? '-'}-${game.teams.home.score ?? '-'}`,
status: game.status.detailedState,
innings: game.linescore?.currentInning ?? '',
})));NBA data in the browser
NBA.com's own endpoints refuse outside callers. The live scoreboard JSON on cdn.nba.com returned 403 Access Denied with no CORS header from a datacenter IP and a US residential IP, and 520 through cors.dev. stats.nba.com never answered within 30 to 60 seconds from either IP, even with nba.com in the Origin and Referer headers, and timed out with a 504 upstream_timeout through cors.dev.
Use the unofficial ESPN scoreboard at site.api.espn.com/apis/site/v2/sports/basketball/nba/scoreboard instead. It needs no key, sends Access-Control-Allow-Origin: * and returns about 150 KB per day; ?dates=20261025 returned 7 games. ESPN blocks proxy traffic with 403 Access Denied, so call it directly. If you test with Puppeteer or Playwright, headless Chrome sends a HeadlessChrome client hint and gets a 403 with no CORS header, while Firefox, WebKit and normal Chrome get 200.
// Runs in a module script or the DevTools console
// ESPN sends Access-Control-Allow-Origin: * but blocks proxies, so call it directly
const date = '20261025';
const url = 'https://site.api.espn.com/apis/site/v2/sports/basketball/nba/scoreboard?dates=' + date;
const response = await fetch(url);
if (!response.ok) throw new Error(`ESPN answered ${response.status}`);
const { events = [] } = await response.json();
console.table(events.map((event) => {
const teams = event.competitions[0].competitors;
const away = teams.find((team) => team.homeAway === 'away');
const home = teams.find((team) => team.homeAway === 'home');
return {
tipoff: new Date(event.date).toLocaleString('en-US', { timeZone: 'UTC' }),
away: `${away.team.abbreviation} ${away.score}`,
home: `${home.team.abbreviation} ${home.score}`,
status: event.status.type.shortDetail,
};
}));The other option is balldontlie, which takes your bare key in the Authorization header with no Bearer prefix. The free plan gives 5 requests per minute for basic data on one sport; $9.99 a month buys 60 per minute. CORS works, since it echoes your Origin, but the key is visible in DevTools, so put it behind a server route on a public site.
Formula 1: OpenF1 and Jolpica
OpenF1 at api.openf1.org/v1 is unofficial, licensed CC BY-NC-SA 4.0, sends Access-Control-Allow-Origin: * and needs no key for historical data since 2023. The free tier allows 3 requests per second and 30 per minute. Live data during a session, from 30 minutes before to 30 minutes after, needs the Sponsor tier at EUR 9.90 a month, which also raises the limits to 6 per second and 60 per minute.
Jolpica F1 at api.jolpi.ca/ergast/f1 is the Ergast-compatible successor. It needs no key and sends Access-Control-Allow-Origin: *. Without a token you get a burst of 4 requests per second and 500 per hour, and the project says these limits will decrease, so cache what you fetch.
Football APIs that need a server
API-Football, from API-Sports, takes your key in the x-apisports-key header and gives 100 requests per day with limited seasons on the free plan; Pro is $19 for 7,500 per day. Its CORS quirk: when the request carries a key header, the preflight allows x-apisports-key and the response has Access-Control-Allow-Origin: *, but without a key /leagues returns 403 with no CORS header at all, so a missing key looks like a CORS failure.
football-data.org uses an X-Auth-Token header. The free tier covers 12 competitions (PL, ELC, BL1, SA, PD, FL1, DED, PPL, BSA, CL, EC, WC) with delayed scores at 10 calls per minute, counted down in the X-Requests-Available header. You cannot use the token from the browser: the GET sends no Access-Control-Allow-Origin, and the preflight for X-Auth-Token answers Access-Control-Allow-Origin: http://localhost, with no port, for every origin. The browser blocks every request that carries the token; that is their server, not your code.
SportMonks has a free plan for the Danish Superliga and the Scottish Premiership with 3,000 API calls per entity per hour, no card and no expiry; paid plans start at EUR 29 a month for 5 leagues. It sends no CORS headers on responses or on preflight, so fetch() failed in all 3 engines.
For these three, keep the key in a Worker or API route, as in the API proxy for frontend apps guide. Or use Sportscore, which needs no key, sends Access-Control-Allow-Origin: * on 8 GET endpoints cached for 60 seconds and allows about 10,000 requests per 24 hours per IP, as long as you show a visible dofollow "Powered by SportScore" link.
Limits to plan for
- TheSportsDB: 30 requests per minute; over that, a 429 without CORS headers.
- balldontlie: 5 requests per minute free.
- API-Football: 100 requests per day free.
- football-data.org: 10 calls per minute, 12 competitions.
- OpenF1 and Jolpica: 30 per minute and 500 per hour.
- Licenses: MLB permits only individual, non-commercial, non-bulk use, and OpenF1 is CC BY-NC-SA 4.0.
- The cors.dev free tier: none of the 6 browser-ready APIs need it. It serves no-key GET APIs without CORS at 600 requests per minute per IP, up to 1 MiB per response. It forwards only
Accept,If-None-MatchandIf-Modified-Since, sox-apisports-key,X-Auth-TokenandAuthorizationnever reach the API, and ESPN blocks it with a 403.
Good questions.
What is the best free sports API for a JavaScript project?
For a frontend with no backend, TheSportsDB with key 123, MLB StatsAPI, the ESPN scoreboard, OpenF1, Jolpica F1 and Sportscore all work from fetch() with Access-Control-Allow-Origin: *. TheSportsDB covers the most sports; Sportscore needs an attribution link.
Is there a free NBA API that works from the browser?
Yes, the unofficial ESPN scoreboard at site.api.espn.com/apis/site/v2/sports/basketball/nba/scoreboard needs no key and sends Access-Control-Allow-Origin: *. The official cdn.nba.com and stats.nba.com endpoints returned 403 or timed out on October 26, 2026, and balldontlie needs a key you should keep on a server.
Does TheSportsDB's key 3 still work?
No. Key 3 returns 403 with the message "The old '3' Free Sports API key is permanently disabled". Use the free key 123 in the URL path, which allows 30 requests per minute.
Why does football-data.org give a CORS error with my token?
Sending X-Auth-Token triggers a preflight, and api.football-data.org answers it with Access-Control-Allow-Origin: http://localhost whatever your origin is. That value never matches your page, so call football-data.org from a server.
Can I call API-Football from frontend code?
The browser request succeeds: v3.football.api-sports.io sends Access-Control-Allow-Origin: * when the request carries an x-apisports-key header. Still, keep it off a public page, because anyone with DevTools can copy the key and spend your 100 free daily requests.
Is there a free Formula 1 API?
Yes. OpenF1 (api.openf1.org/v1) and Jolpica F1 (api.jolpi.ca/ergast/f1) are free, need no key and send Access-Control-Allow-Origin: *. Use OpenF1 for session data since 2023 and Jolpica for full F1 history in the Ergast format.