To get Premier League standings from an API, fetch the competition from Soccer Data's /competitions/{competition_id} endpoint to read its current_season_id, then call /competitions/{competition_id}/seasons/{season_id}/standings. The whole table comes back in one response, one row per team in position order, with matches played, wins, draws, losses, goals and points. The same calls work for any league Soccer Data covers, and for tournament group tables.
Soccer Data is a read-only REST API: every request is a GET with your key in the X-Api-Key header, and every answer is JSON. Keep the key on your server.
Step 1: find the competition id
Search competitions by name. The closest match comes first:
curl "https://api.soccer-data.co/v1/competitions?search=Premier%20League&per_page=3" \
-H "X-Api-Key: YOUR_API_KEY"
{
"data": [
{ "id": "competition_705uv6jm8myn", "name": "Premier League", "country": "England", "type": "league" }
]
}
The Premier League is competition_705uv6jm8myn. Ids never change, so look it up once and store it. You can also filter instead of searching: country_code=GB-ENG&type=league lists the English leagues, and country=Spain&type=league the Spanish ones.
Step 2: read the current season id
A table belongs to one season of one competition, so you need a season id as well:
curl "https://api.soccer-data.co/v1/competitions/competition_705uv6jm8myn" \
-H "X-Api-Key: YOUR_API_KEY"
{
"data": {
"id": "competition_705uv6jm8myn",
"name": "Premier League",
"type": "league",
"current_season_id": "season_hr1ie7yrsdmt",
"total_teams": 20
}
}
At the time of writing, current_season_id is season_hr1ie7yrsdmt, Premier League 26/27. Read it from the competition rather than hard-coding it, and your table moves on to the new season by itself. It is null for a competition that is no longer played, so handle that case.
Step 3: get the table
curl "https://api.soccer-data.co/v1/competitions/competition_705uv6jm8myn/seasons/season_hr1ie7yrsdmt/standings" \
-H "X-Api-Key: YOUR_API_KEY"
{
"data": [
{
"team": { "id": "team_hof12pbei216", "name": "Manchester City" },
"position": 1,
"matches_played": 5,
"wins": 5,
"draws": 0,
"losses": 0,
"goals_for": 13,
"goals_against": 5,
"goal_difference": 8,
"points": 15,
"group_label": null
}
]
}
The real response has all 20 rows. Rows come in position order and the table isn't paged, so don't send page or per_page: the endpoint rejects them with a 400.
| Field | Meaning |
|---|---|
team |
The team's id and name. |
position |
Place in the table, from 1. |
matches_played, wins, draws, losses |
The team's record. |
goals_for, goals_against, goal_difference |
Goals scored, conceded and the difference. |
points |
Points so far. |
group_label |
The group, such as "A", in a tournament. null in a league. |
A Python example
This reads the current season and prints the table. It needs the requests package:
import os, requests
API = "https://api.soccer-data.co/v1"
HEADERS = {"X-Api-Key": os.environ["SOCCER_DATA_KEY"]}
def current_table(competition_id, group=None):
competition = requests.get(f"{API}/competitions/{competition_id}", headers=HEADERS).json()["data"]
season_id = competition["current_season_id"]
if season_id is None:
raise ValueError(f'{competition["name"]} has no current season')
params = {"group": group} if group else {}
res = requests.get(
f"{API}/competitions/{competition_id}/seasons/{season_id}/standings",
headers=HEADERS,
params=params,
)
res.raise_for_status()
return res.json()["data"]
for row in current_table("competition_705uv6jm8myn"):
print(f'{row["position"]:>2} {row["team"]["name"]:<26}{row["matches_played"]:>3}{row["goal_difference"]:>+5}{row["points"]:>4}')
Swap in another competition id for any other league. The season id only changes once a season, so you can store it and refresh it daily instead of fetching the competition before every table.
Past seasons
/competitions/{competition_id}/seasons lists every season, newest first, and marks the current one with is_current: true:
curl "https://api.soccer-data.co/v1/competitions/competition_705uv6jm8myn/seasons" \
-H "X-Api-Key: YOUR_API_KEY"
Then pass an older season id to the same standings endpoint. Premier League 25/26 is season_98l95mxugqs9; Arsenal finished top of that table with 85 points from 26 wins in 38 matches.
How far back tables go depends on the competition. In the Premier League they start in 2014/15. To check any competition season by season, call /coverage/leagues/{competition_id} and look at standings.available. A season id only works with its own competition: pairing it with another returns 400 SEASON_COMPETITION_MISMATCH.
Group tables for tournaments
Tournaments with a group stage have one table per group. List the groups first. This is the 2022 World Cup:
curl "https://api.soccer-data.co/v1/competitions/competition_7vpwx91c7j7h/seasons/season_st2tyewgtq2j/groups" \
-H "X-Api-Key: YOUR_API_KEY"
That returns labels from A to H, each with a display name such as "Group A". Ask for one group's table with group:
curl "https://api.soccer-data.co/v1/competitions/competition_7vpwx91c7j7h/seasons/season_st2tyewgtq2j/standings?group=A" \
-H "X-Api-Key: YOUR_API_KEY"
Leave group out and you get every group's table in one list, sorted by group_label and then position. A league without groups returns an empty list from /groups, and a knockout-only cup such as the FA Cup has no table, so its standings come back as an empty list.
A team's standings across competitions
/teams/{team_id}/standings lists every competition and season in which a team has a table entry. For Arsenal:
curl "https://api.soccer-data.co/v1/teams/team_f0ibhxdw7hs5/standings" \
-H "X-Api-Key: YOUR_API_KEY"
{
"data": [
{
"competition": { "id": "competition_705uv6jm8myn", "name": "Premier League" },
"seasons": [
{ "id": "season_hr1ie7yrsdmt", "name": "Premier League 26/27", "group_label": null }
]
},
{
"competition": { "id": "competition_b25me9a3vyf7", "name": "UEFA Europa League" },
"seasons": [
{ "id": "season_dgf6gghi4mf8", "name": "UEFA Europa League 22/23", "group_label": "A" }
]
}
]
}
This is a list of places to look, not positions. Fetch a season's table with those ids to see where the team stands, and pass group_label as group when it has one. If you only need one team's position, points and recent form, /teams/{team_id}/stats with season_id returns all three in a single call.
How fresh the table is during matches
Tables move while matches are in play, so the standings endpoint is cached like live data:
| Plan | Live data normally refreshed every |
|---|---|
| Free | 5 minutes |
| Pro and Business | 15 seconds |
Asking again within that window returns the same table with X-Cache: HIT, and still counts as a request. If a fresh table can't be fetched, you may get an older copy marked X-Cache: STALE, with its age in seconds in X-Cache-Age.
You already know when a league is playing from its fixtures (/matches with competition_id, date_from and date_to). Refresh the table every 15 to 30 seconds while its matches are on, and cache it on your server for longer the rest of the time.
Any other league
Nothing in these steps is specific to the Premier League. Find the competition id with search, country or country_code, read current_season_id, and call the standings endpoint. Soccer Data covers 1,100 competitions, but depth varies, so check that a table exists first: /coverage/leagues?data_type=standings lists the competitions that have one.
Next steps
- League tables guide: past seasons, groups and common mistakes.
- Standings endpoint reference: every field and note.
- Find any ID: look up competitions, seasons and teams.
- Create an account to get an API key.
Frequently asked questions
Is there an API for Premier League standings?
Yes. Soccer Data returns the Premier League table from GET /competitions/{competition_id}/seasons/{season_id}/standings, with every team's position, matches played, wins, draws, losses, goals and points in one response. Get the season id from current_season_id on GET /competitions/{competition_id}.
How do I get the current season id for a league?
Fetch the competition from Soccer Data's GET /competitions/{competition_id} endpoint and read current_season_id. It is null for competitions that are no longer played, and GET /competitions/{competition_id}/seasons lists older seasons, newest first.
How often is the league table updated during matches?
Soccer Data caches standings like live data, so on the paid plans, Pro and Business, the table normally refreshes every 15 seconds while matches are in play. Polling more often returns the same cached table and still counts as a request.
Can I get standings for past seasons?
Yes. List a competition's seasons with Soccer Data's GET /competitions/{competition_id}/seasons, then pass an older season id to the standings endpoint. How far back tables go depends on the competition; Premier League tables start in 2014/15.
How do I get World Cup or other tournament group tables?
List the groups with Soccer Data's GET /competitions/{competition_id}/seasons/{season_id}/groups, then call the standings endpoint with group=A (or any label) for one group's table. Without group, you get every group's table in one list, sorted by group and position.
How do I find every league table a team appears in?
Call Soccer Data's GET /teams/{team_id}/standings. It lists each competition and season in which the team has a table entry, with a group_label for group tables, and you then fetch those seasons' standings for positions.