GuidesFANTASY PREMIER LEAGUE

FPL API from JavaScript

Prefix https://proxy.cors.dev/ to any fantasy.premierleague.com/api URL and the FPL API works from browser JavaScript: free, no signup, no key, JSON up to 1 MiB, 600 requests per minute per client IP. FPL sends no Access-Control-Allow-Origin header, so the same fetch() straight from your page fails with a CORS error. Live gameweek points, fixtures, manager history, picks, transfers and mini-league tables all fit the free tier. bootstrap-static, the 1.7 MiB file with all 668 players, prices and clubs, is over the 1 MiB limit and needs Pro at $5/month, with a 7-day trial and no card.

Why the FPL API fails in the browser

The Fantasy Premier League API is the JSON the FPL website loads from https://fantasy.premierleague.com/api/. None of the endpoints we tested sends Access-Control-Allow-Origin, so the browser hides the response and fetch() rejects with a blocked by CORS policy error. Python, curl and Postman work because only browsers enforce CORS.

Through https://proxy.cors.dev/ the same response comes back with the header. FPL compresses its JSON with gzip, but the free 1 MiB limit counts the decoded body: bootstrap-static is 169 KB on the wire and 1,783,651 bytes decoded, so the free tier returns 502 with X-Cors-Error: response_too_large. Every other public endpoint in the table below is under 500 KB.

A mini-league table on the free tier

Classic league standings come 50 managers per page from /api/leagues-classic/{league}/standings/, about 11 KB per page. The league id is the number in the standings address, such as fantasy.premierleague.com/leagues/314/standings/c; 314 is the Overall league.

A live mini-league table on the free tier
// Markup: <h2 id="league-name"></h2><ol id="league-table"></ol>
// Replace 314, the Overall league, with the id from your league's standings URL
const leagueId = 314;

async function fpl(path) {
  const response = await fetch('https://proxy.cors.dev/https://fantasy.premierleague.com/api' + path, {
    credentials: 'omit',
  });
  if (!response.ok) {
    throw new Error(`FPL request failed: ${response.headers.get('X-Cors-Error') ?? response.status}`);
  }
  return response.json();
}

const heading = document.querySelector('#league-name');
const table = document.querySelector('#league-table');
Promise.all([fpl('/events/'), fpl(`/leagues-classic/${leagueId}/standings/`)])
  .then(([events, { league, standings }]) => {
    const current = events.find((event) => event.is_current);
    heading.textContent = `${league.name}, ${current?.name ?? 'pre-season'}`;
    table.replaceChildren(
      ...standings.results.map((row) => {
        const item = document.createElement('li');
        item.textContent = `${row.entry_name} (${row.player_name}): ${row.total} points, ${row.event_total} this gameweek`;
        return item;
      }),
    );
  })
  .catch((error) => {
    table.textContent = error.message;
  });

standings.has_next is true when another page exists; request it with ?page_standings=2. /api/events/ is 28 KB and marks the current gameweek with is_current, so you never need bootstrap-static just to know which gameweek it is.

Live gameweek points

/api/event/{gw}/live/ returns every player's stats for one gameweek, 472 KB for all 668 players in gameweek 7, with stats.total_points, minutes, goals, assists, bonus and expected goals. /api/entry/{team}/event/{gw}/picks/ returns a manager's 15 picks, each with a multiplier: without a chip, 2 for the captain, 1 for the rest of the starting 11 and 0 on the bench.

Sum each pick's total_points times its multiplier for a live score; FPL's own figure is entry_history.points in the picks response. Picks for a gameweek appear only after its deadline: gameweek 8 picks returned 404 before the October 23 deadline.

Every player and price from bootstrap-static

bootstrap-static is the file every FPL tool starts from: 668 players with prices, points, ownership and status, the 20 clubs, all 38 gameweeks, positions and game settings. At 1.7 MiB it is over the free limit, and so is /api/elements/ at 1.66 MiB.

Pro relays responses up to 6 MiB for $5/month and 500,000 requests per billing period; the 7-day trial includes 1,000 requests and needs no card. Requests carry an origin-locked publishable key in the X-Cors-Key header, which cors.dev never sends to FPL.

Pro: every player and price from bootstrap-static
// YOUR_CONNECTION_KEY is the publishable key of a Connection that allows your origin and fantasy.premierleague.com
const response = await fetch('https://proxy.cors.dev/https://fantasy.premierleague.com/api/bootstrap-static/', {
  headers: { 'X-Cors-Key': 'YOUR_CONNECTION_KEY' },
  credentials: 'omit',
});
if (!response.ok) {
  throw new Error(`FPL request failed: ${response.headers.get('X-Cors-Error') ?? response.status}`);
}
const { elements, teams, element_types: positions, events } = await response.json();

const club = new Map(teams.map((team) => [team.id, team.short_name]));
const position = new Map(positions.map((type) => [type.id, type.singular_name_short]));
const current = events.find((event) => event.is_current);

console.log(`${elements.length} players, ${current?.name ?? 'pre-season'}`);
console.table(
  [...elements]
    .sort((a, b) => b.total_points - a.total_points)
    .slice(0, 10)
    .map((player) => ({
      name: player.web_name,
      club: club.get(player.team),
      position: position.get(player.element_type),
      price: `£${(player.now_cost / 10).toFixed(1)}m`,
      points: player.total_points,
      selected: `${player.selected_by_percent}%`,
    })),
);

now_cost is in tenths of a million pounds, so Haaland's 157 is £15.7m, and element_type 1 to 4 maps to GKP, DEF, MID and FWD through element_types. Add X-Cors-Cache: 300 to cache the file for 5 minutes instead of downloading 1.7 MiB on every page view. The teams entries are not a league table: their played, win and points fields were all 0 in October 2026. For the real Premier League table, ESPN's soccer/eng.1 standings work directly; see ESPN API from JavaScript.

FPL API endpoints and response sizes

Paths are relative to https://fantasy.premierleague.com/api/, and the trailing slash is optional. Manager 1 and league 314 are public examples; swap in your own ids.

FPL API endpoints through cors.dev, checked October 2026
PathReturnsSizeFree tier
bootstrap-static/All 668 players with prices, 20 clubs, 38 gameweeks1.7 MiB, 169 KB gzipped502 response_too_large, needs Pro
elements/All 668 players1.66 MiB502 response_too_large, needs Pro
events/All 38 gameweeks: deadlines, averages, top scores28 KBWorks
event-status/Bonus and league update status for the current gameweek219 BWorks
fixtures/All 380 fixtures with kickoff times, scores and difficulty276 KBWorks
fixtures/?event=7The 10 fixtures of gameweek 724 KBWorks
event/7/live/Stats and points for all 668 players in gameweek 7472 KBWorks
element-summary/1/One player's upcoming fixtures, this season and past seasons16 KBWorks
entry/1/One manager: team name, points, rank, leagues18 KBWorks
entry/1/history/Points per gameweek, past seasons, chips played2.7 KBWorks
entry/1/event/7/picks/The 15 picks, captain and chip for gameweek 71.9 KBWorks
entry/1/transfers/Every transfer with buy and sell prices5.9 KBWorks
leagues-classic/314/standings/Classic league table, 50 managers per page11 KBWorks
dream-team/7/Team of the week and top player494 BWorks
team/set-piece-notes/Set-piece notes for the 20 clubs2.3 KBWorks, placeholder text so far
my-team/1/A manager's own team58 B error403 from FPL, needs a login
entry/1/event/8/picks/Picks for a gameweek before its deadline23 B error404 from FPL until the deadline

The 2026/27 season and API changes

/api/events/ lists 38 gameweeks for 2026/27, from a gameweek 1 deadline on August 21, 2026 to gameweek 38 on May 30, 2027. On October 18, 2026, gameweek 7 was current, gameweek 8 had a deadline of October 23 at 18:30 UK time, and bootstrap-static counted 11,124,702 managers.

The API is unofficial and undocumented, and paths change between seasons: older paths such as /api/bootstrap-dynamic/ and /api/leagues-classic-standings/314 now return 404. Check response.ok, show the error, and re-test your endpoints when a new season opens in the summer.

Limits

  • Free tier: GET and HEAD, JSON and other text responses up to 1 MiB decoded, a 10 second upstream deadline and 600 requests per minute per client IP, with no daily or monthly quota. No signup and no key.
  • Size: bootstrap-static and /api/elements/ are the only endpoints we found over 1 MiB. A full gameweek of live stats is 472 KB and works free.
  • Login: endpoints for your own team, such as /api/my-team/{team}/, need an FPL login and return 403 without one. Nothing else on this page needs a login, key or cookie.
  • Pro: $5/month for 500,000 requests per billing period, responses up to 6 MiB, 600 requests per minute and 10 concurrent per account, and opt-in caching of 1 to 300 seconds with X-Cors-Cache.

Good questions.

Is there an official FPL API?

No. fantasy.premierleague.com/api is the undocumented JSON API the FPL website uses itself. Public data needs no key, but the API is unofficial and its paths change between seasons.

Does the FPL API need an API key?

No. Players, fixtures, live points, manager history, picks and league tables need no key or login. Only your own team endpoints, such as /api/my-team/{team}/, need a logged-in FPL account and return 403 otherwise.

Why do I get a CORS error calling the FPL API?

fantasy.premierleague.com sends no Access-Control-Allow-Origin header, so browsers block the response. Prefix the URL with https://proxy.cors.dev/ to call it from a web page, or call it from a server, where CORS does not apply.

Why does bootstrap-static fail through a CORS proxy?

It is 1,783,651 bytes decoded, over the 1 MiB free limit, so cors.dev returns 502 with X-Cors-Error: response_too_large. Pro relays up to 6 MiB for $5/month; the 7-day trial includes 1,000 requests and needs no card.

How do I find my FPL team id and league id?

Your team id is the number after /entry/ in the address of your Points page, such as fantasy.premierleague.com/entry/1234567/event/7. A league id is the number in the league standings address, such as fantasy.premierleague.com/leagues/314/standings/c.

How do I get the current gameweek from the FPL API?

/api/events/ returns all 38 gameweeks in 28 KB. The current one has is_current set to true and the next one is_next. It works on the free tier, unlike bootstrap-static.

How do I get live FPL points for my team?

Fetch /api/event/{gw}/live/ for every player's points that gameweek and /api/entry/{team}/event/{gw}/picks/ for your 15 picks, then add each pick's total_points times its multiplier. Both work on the free tier.