# Season standings

> **Included in all plans**
>
> Season standings are available on all base plans (Starter, Growth, Pro, Enterprise) at no additional cost. Plans differ in the number of accessible leagues and hourly API call limits.
>
> [Compare plans →](https://www.sportmonks.com/football-api/plans-pricing)

**Quick summary:** This tutorial covers the five standings endpoints available in API v3 (including by season, by round, live standings, and standing corrections) and shows how to enrich and filter them for real-world use in league tables and standings widgets.

**What you'll learn:**

* How to retrieve standings by season, round, and live league ID
* How to enrich standings responses with team names, league info, and detailed stats
* How to use standing corrections for leagues with point deductions
* How to select only the fields you need to reduce response size

**Time to complete:** 20 minutes **Skill level:** Beginner to Intermediate&#x20;

**Prerequisites:** [Authentication](https://docs.sportmonks.com/v3/welcome/authentication), [Includes](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/includes), [Leagues and Seasons](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/leagues-and-seasons)

### 1. When to use this feature

Standings show the ranked table of teams competing in a season, including games played, wins, losses, goals scored, goals conceded, and total points. They update after each match and can be retrieved historically by round.

#### Real-world use cases

* **Use case 1: League table display** You're building a football app and want to show the current Premier League table. GET Standings by Season ID returns the full ranked table for any season, enrichable with team names and detailed statistics.
* **Use case 2: Historical round-by-round standings** You want to show how a team's position changed over the course of a season. GET Standings by Round ID lets you retrieve the table as it stood at any specific round.
* **Use case 3: Live league table during an active match** You want the table to update in real time while matches are being played. GET Live Standings by League ID returns the current in-progress standings for any active season.
* **Use case 4: Displaying point deductions** A club receives a disciplinary sanction mid-season. GET Standing Corrections by Season ID provides the deduction data so you can accurately reflect the adjusted table.

#### When NOT to use this feature

❌ **Player rankings or top scorers** : Use the [Topscorers](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/standings/topscorer-standings) endpoint for individual player standings.

❌ **Expected points tables (xPTS)** : Use the [Expected Goals (xG)](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/expected) tutorial for expected-points-based standings.

### 2. How to retrieve data

#### Understanding the endpoints

**Base URL:**

```http
https://api.sportmonks.com/v3/football/standings
```

**Available endpoints:**

| Endpoint                              | Path suffix                        | Description                             |
| ------------------------------------- | ---------------------------------- | --------------------------------------- |
| GET All Standings                     | *(base URL)*                       | All standings in your subscription      |
| GET Standings by Season ID            | `/seasons/{season_id}`             | Full league table for a specific season |
| GET Standings by Round ID             | `/rounds/{round_id}`               | Table as it stood at a specific round   |
| GET Standing Corrections by Season ID | `/corrections/seasons/{season_id}` | Point deductions for a season           |
| GET Live Standings by League ID       | `/live/leagues/{league_id}`        | Real-time table for an active season    |

**Available parameters:**

| Parameter   | Type   | Required | Description                   | Example                           |
| ----------- | ------ | -------- | ----------------------------- | --------------------------------- |
| `api_token` | string | Yes      | Your API authentication token | `YOUR_API_TOKEN`                  |
| `include`   | string | No       | Related data to attach        | `participant;league;details.type` |
| `select`    | string | No       | Fields to return on includes  | `participant:name;league:name`    |
| `filters`   | string | No       | Filter by specific fields     | `standingSeasons:19686`           |

#### Step-by-step implementation

**Step 1: GET Standings by Season ID**

This is the most commonly used standings endpoint. Pass the `season_id` for the season you want.

```http
https://api.sportmonks.com/v3/football/standings/seasons/{season_id}
?api_token=YOUR_API_TOKEN
```

For example, the Danish Superliga 2022/2023 season (season ID: 19686):

```http
https://api.sportmonks.com/v3/football/standings/seasons/19686
?api_token=YOUR_API_TOKEN
```

**JavaScript:**

{% tabs %}
{% tab title="Javascript" %}

```javascript
const apiToken = 'YOUR_API_TOKEN';

async function getStandingsBySeason(seasonId, params = {}) {
  const query = new URLSearchParams({ api_token: apiToken, ...params });
  const response = await fetch(
    `https://api.sportmonks.com/v3/football/standings/seasons/${seasonId}?${query}`
  );
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json();
}

const table = await getStandingsBySeason(19686);
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

def get_standings_by_season(season_id, api_token, params=None):
    params = params or {}
    params['api_token'] = api_token
    r = requests.get(
        f'https://api.sportmonks.com/v3/football/standings/seasons/{season_id}',
        params=params,
        timeout=30
    )
    r.raise_for_status()
    return r.json()
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
function getStandingsBySeason($seasonId, $apiToken, $params = []) {
    $params['api_token'] = $apiToken;
    $url = "https://api.sportmonks.com/v3/football/standings/seasons/{$seasonId}?" . http_build_query($params);
    $response = file_get_contents($url);
    return json_decode($response, true);
}

$table = getStandingsBySeason(19686, 'YOUR_API_TOKEN');
print_r($table);
?>
```

{% endtab %}
{% endtabs %}

**Step 2: GET Standings by Round ID**

Returns the league table as it stood at a specific round, useful for historical analysis or "table at round N" features. You need the `round_id`, which you can retrieve from the [Rounds by Season ID endpoint](https://docs.sportmonks.com/v3/endpoints-and-entities/endpoints/rounds/get-rounds-by-season-id).

```http
https://api.sportmonks.com/v3/football/standings/rounds/274241
?api_token=YOUR_API_TOKEN
```

**Step 3: GET Live Standings by League ID**

Returns the current in-progress table for an active season. Note that this endpoint takes a `league_id`, not a `season_id`, because you can only retrieve live standings for a currently active season.

```http
https://api.sportmonks.com/v3/football/standings/live/leagues/501
?api_token=YOUR_API_TOKEN
```

{% hint style="info" %}
The response will be empty if the league has no active season or no matches are currently being played in that league.
{% endhint %}

**Step 4: GET Standing Corrections by Season ID**

Returns point deductions applied to teams in a given season. Use this alongside the main standings to accurately display adjusted positions.

```http
https://api.sportmonks.com/v3/football/standings/corrections/seasons/18441
?api_token=YOUR_API_TOKEN
```

**Step 5: Adding includes**

By default, standings return only IDs (participant ID, league ID, etc.) and not names. Use `include` to attach readable data.

```http
https://api.sportmonks.com/v3/football/standings/seasons/19686
?api_token=YOUR_API_TOKEN&include=participant;league;details.type
```

The most useful includes for standings:

* `participant` : Team name, logo, country, and venue
* `league` : League name and logo
* `details.type` : Detailed stats per team (matches played, wins, goals, form, etc.)

{% hint style="warning" %}
Including `.type` is expensive and not recommended for production. Fetch all types once from the [Types endpoint](https://docs.sportmonks.com/v3/definitions/types) and cache them. Only include `.type` when testing or if no other option is available.
{% endhint %}

**Step 6: Selecting specific fields**

Use `select` on includes to return only the fields you need, reducing response size significantly.

```http
https://api.sportmonks.com/v3/football/standings/seasons/19686
?api_token=YOUR_API_TOKEN&include=participant:name;league:name;details.type:name
```

**Step 7: Complete example**

{% tabs %}
{% tab title="Javascript" %}

```javascript
class FootballAPI {
  constructor(apiToken) {
    this.token = apiToken;
    this.baseURL = 'https://api.sportmonks.com/v3/football';
  }

  async getStandings(seasonId, params = {}) {
    const query = new URLSearchParams({ api_token: this.token, ...params });
    const r = await fetch(`${this.baseURL}/standings/seasons/${seasonId}?${query}`);
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    return r.json();
  }

  async getLiveStandings(leagueId, params = {}) {
    const query = new URLSearchParams({ api_token: this.token, ...params });
    const r = await fetch(`${this.baseURL}/standings/live/leagues/${leagueId}?${query}`);
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    return r.json();
  }
}

const api = new FootballAPI('YOUR_API_TOKEN');

const table = await api.getStandings(19686, {
  include: 'participant:name,image_path;details.type:name'
});

const liveTable = await api.getLiveStandings(501, {
  include: 'participant:name'
});
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

class FootballAPI:
    def __init__(self, api_token):
        self.token = api_token
        self.base_url = 'https://api.sportmonks.com/v3/football'

    def get_standings(self, season_id, params=None):
        params = params or {}
        params['api_token'] = self.token
        r = requests.get(
            f'{self.base_url}/standings/seasons/{season_id}',
            params=params, timeout=30
        )
        r.raise_for_status()
        return r.json()

    def get_live_standings(self, league_id, params=None):
        params = params or {}
        params['api_token'] = self.token
        r = requests.get(
            f'{self.base_url}/standings/live/leagues/{league_id}',
            params=params, timeout=30
        )
        r.raise_for_status()
        return r.json()

api = FootballAPI('YOUR_API_TOKEN')
table = api.get_standings(19686, {'include': 'participant:name,image_path'})
live_table = api.get_live_standings(501, {'include': 'participant:name'})
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
class FootballAPI {
    private $token;
    private $baseURL = 'https://api.sportmonks.com/v3/football';

    public function __construct($apiToken) {
        $this->token = $apiToken;
    }

    public function getStandings($seasonId, $params = []) {
        $params['api_token'] = $this->token;
        $url = "{$this->baseURL}/standings/seasons/{$seasonId}?" . http_build_query($params);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        $response = curl_exec($ch);
        curl_close($ch);

        return json_decode($response, true);
    }

    public function getLiveStandings($leagueId, $params = []) {
        $params['api_token'] = $this->token;
        $url = "{$this->baseURL}/standings/live/leagues/{$leagueId}?" . http_build_query($params);

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        $response = curl_exec($ch);
        curl_close($ch);

        return json_decode($response, true);
    }
}

$api = new FootballAPI('YOUR_API_TOKEN');
$table = $api->getStandings(19686, ['include' => 'participant:name,image_path']);
$liveTable = $api->getLiveStandings(501, ['include' => 'participant:name']);
print_r($table);
?>
```

{% endtab %}
{% endtabs %}

### 3. Working with the data

#### Understanding the response structure

```json
{
  "data": [
    {
      "id": 2622,
      "participant_id": 53,
      "sport_id": 1,
      "league_id": 501,
      "season_id": 19735,
      "stage_id": 77457866,
      "group_id": null,
      "round_id": 275092,
      "standing_rule_id": 13224,
      "position": 1,
      "result": "equal",
      "points": 73
    }
  ]
}
```

**Key fields:**

| Field              | Type    | Description                                                   |
| ------------------ | ------- | ------------------------------------------------------------- |
| `id`               | integer | Unique standing record ID                                     |
| `participant_id`   | integer | The team's ID, use with `include=participant` to get the name |
| `position`         | integer | Current league position                                       |
| `result`           | string  | Position change: `"up"`, `"down"`, or `"equal"`               |
| `points`           | integer | Total points accumulated                                      |
| `round_id`         | integer | The most recent round this standing reflects                  |
| `standing_rule_id` | integer | The tiebreaker rule applied at this position                  |

With `include=details.type`, each team object gains a `details` array where each entry represents a stat (matches played, wins, goals for, etc.) identified by `type_id`.

#### Processing the data

**Build a sorted league table with team names:**

```javascript
const data = await api.getStandings(19686, {
  include: 'participant:name,image_path'
});

const table = data.data
  .sort((a, b) => a.position - b.position)
  .map(row => ({
    position: row.position,
    team: row.participant?.name || `Team ${row.participant_id}`,
    logo: row.participant?.image_path,
    points: row.points,
    trend: row.result
  }));

console.table(table);
```

**Extract a specific stat from details (e.g. matches played, type\_id 129):**

```javascript
function getStat(details, typeId) {
  return details?.find(d => d.type_id === typeId)?.value ?? 0;
}

const enriched = data.data.map(row => ({
  team: row.participant?.name,
  position: row.position,
  points: row.points,
  played: getStat(row.details, 129),
  won: getStat(row.details, 130),
  drawn: getStat(row.details, 131),
  lost: getStat(row.details, 132)
}));
```

### 4. Common pitfalls

#### Pitfall 1: Using GET All Standings instead of by Season ID

GET All Standings returns every standing across every league in your subscription (thousands of records). Always use GET Standings by Season ID for a specific table.

```javascript
// ❌ Wrong: returns far more data than you need
const all = await fetch('/standings?api_token=...');

// ✅ Correct: returns only the season you need
const table = await fetch('/standings/seasons/19686?api_token=...');
```

#### Pitfall 2: Including `.type` on every request

Including `details.type` on standings triggers a type resolution for every stat on every team. This is expensive at scale.

```javascript
// ❌ Avoid in production
const table = await api.getStandings(19686, { include: 'details.type' });

// ✅ Fetch types once, then use IDs directly
const types = await fetchAndCacheTypes();
const table = await api.getStandings(19686, { include: 'details' });
// Resolve type names locally: types.get(detail.type_id)
```

#### Pitfall 3: Using live standings for historical data

GET Live Standings only works for currently active seasons. For historical or completed season data, use GET Standings by Season ID.

```javascript
// ❌ Wrong: will return nothing for a finished season
const old = await fetch('/standings/live/leagues/501?api_token=...');

// ✅ Correct: historical tables use season ID
const old = await fetch('/standings/seasons/19686?api_token=...');
```

#### Pitfall 4: Not handling group standings (cup competitions)

In cup competitions and leagues with group stages, standings are returned per group. Always check `group_id` in the response and handle both cases.

```javascript
const isGroupStage = data.data.some(row => row.group_id !== null);

if (isGroupStage) {
  const groups = {};
  for (const row of data.data) {
    const key = row.group_id;
    if (!groups[key]) groups[key] = [];
    groups[key].push(row);
  }
} else {
  renderTable(data.data.sort((a, b) => a.position - b.position));
}
```

### 5. Advanced usage

#### Advanced technique 1: Round-by-round position tracking

Show a team's position across every round of the season to visualise their trajectory.

```javascript
async function getTeamPositionHistory(rounds, teamId) {
  const requests = rounds.map(roundId =>
    api.getStandingsByRound(roundId, { include: 'participant:name' })
  );

  const results = await Promise.all(requests);

  return results.map((result, i) => {
    const teamRow = result.data.find(r => r.participant_id === teamId);
    return {
      round: rounds[i],
      position: teamRow?.position ?? null,
      points: teamRow?.points ?? null
    };
  });
}
```

#### Advanced technique 2: Combining standings with fixtures for a match-day view

On a match day, show the current table alongside the day's fixtures so users can see what's at stake.

```javascript
async function getMatchDayContext(leagueId, seasonId, date) {
  const [standings, fixtures] = await Promise.all([
    api.getLiveStandings(leagueId, { include: 'participant:name' }),
    api.getFixturesByDate(date, {
      filters: `fixtureLeagues:${leagueId}`,
      include: 'participants;scores'
    })
  ]);

  return {
    table: standings.data.sort((a, b) => a.position - b.position),
    todaysMatches: fixtures.data
  };
}
```

### 6. Common errors

#### 401 Unauthorised

**Cause:** Missing or invalid `api_token`. **Fix:** Confirm the token is correct and present on every request. Find it at [MySportmonks](https://my.sportmonks.com/).

#### 404 Not Found

**Cause:** The `season_id` or `round_id` does not exist or belongs to a league outside your subscription. **Fix:** Use GET All Seasons to verify the season ID, or check the [ID Finder](https://my.sportmonks.com/resources/id-finder).

#### 429 Too Many Requests

**Cause:** Too many requests within your hourly limit. **Fix:** Cache standings (they only change after each matchday). See the [Rate Limiting guide](https://docs.sportmonks.com/v3/api/rate-limit).

#### Empty `data` array from live standings

**Cause:** The league has no active season, or no matches are currently being played. **Fix:** Fall back to GET Standings by Season ID using the `currentSeason` ID retrieved from the leagues endpoint.

### See also

**Prerequisites**

* [Authentication](https://docs.sportmonks.com/v3/welcome/authentication) :  Setting up API access
* [Leagues and Seasons](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/leagues-and-seasons) : Finding season IDs and league IDs
* [Includes](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/includes) : Enriching responses with team and stat data

**Related data**

* [Fixtures](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/livescores-and-fixtures/fixtures) : Show the fixtures that produced the current table
* [Team Statistics](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/statistics/team-statistics) : Deeper per-team performance metrics
* [Topscorer Standings](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/standings/topscorer-standings) : Player rankings alongside the team table
* [Expected Goals (xG)](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/expected) : xPTS alongside actual standings
* [Season Schedule](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/season-schedule) : Combine tables with round and stage data

**Optimisation**

* [Filter and Select Fields](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/filter-and-select-fields) : Narrow standings data
* [Rate Limiting](https://docs.sportmonks.com/v3/api/rate-limit) : Standings change per matchday; cache accordingly

**Related endpoints**

* [Standings Endpoints](https://docs.sportmonks.com/v3/endpoints-and-entities/endpoints/standings) : Complete API endpoint reference
* [Rounds Endpoints](https://docs.sportmonks.com/v3/endpoints-and-entities/endpoints/rounds) : Retrieve round IDs for a season

### FAQ

**Q: How do I find the season ID for a league?**

Use the leagues endpoint with `include=currentSeason` to get the active season ID, or `include=seasons` for all historical seasons. See the [Leagues tutorial](https://docs.sportmonks.com/v3/tutorials-and-guides/tutorials/leagues-and-seasons/leagues).

**Q: How often are standings updated?**

Standings update after each processed match. During an active match day, they reflect completed fixtures only. Use GET Live Standings to see in-progress positions.

**Q: How do I handle leagues with group stages, like the UEFA Champions League?**

Group-stage standings include a non-null `group_id`. Group results into separate tables by `group_id` before rendering.

**Q: What does `result: "equal"` mean on a standing row?**

It means the team's position did not change since the last update. `"up"` means they gained positions; `"down"` means they dropped.

**Q: Are standings available on all plans?**

Yes. All plans include standings access for their covered leagues.

#### Best practices summary

✅ DO:

* Use GET Standings by Season ID as your primary standings endpoint
* Use `select` on includes to return only the fields you need
* Cache standings, they only change after each matchday
* Retrieve types once from the Types endpoint and cache them rather than including `.type`
* Handle `group_id` to support cup competitions and group stages

❌ DON'T:

* Use GET All Standings, always scope to a season or round
* Include `.type` in production requests
* Use live standings for historical or completed seasons
* Assume all leagues have a single-table format
