> For the complete documentation index, see [llms.txt](https://docs.sportmonks.com/v3/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sportmonks.com/v3/motorsport-api/tutorials-and-guides/guides/season-dashboard-guide.md).

# Season Dashboard Guide

> **🏎️ Motorsport API Required**
>
> This guide requires an active Motorsport API subscription (€79/mo, 3,000 API calls/hr).
>
> [View pricing →](https://www.sportmonks.com/formula-one-api/)

This guide walks through how to build a season dashboard using the Motorsport API - combining championship standings, race results, and the season schedule into a complete season overview.

**What you'll learn:**

* How to retrieve the current season and its key IDs in a single call
* How to fetch driver and constructor standings with participant data
* How to retrieve the season schedule and race results
* How to structure and cache data efficiently for a dashboard

**Prerequisites:** Active Motorsport API subscription, API token, familiarity with REST APIs

### 1. When to use a season dashboard approach

This approach is well suited to:

* Season overview pages showing standings, completed results, and upcoming races
* Fantasy F1 applications needing current championship positions and points
* Data tools comparing driver or constructor performance across a season
* Press and media dashboards tracking championship progression

It is a read-heavy, low-frequency data pattern. Unlike live timing, season dashboard data changes only after each race weekend - typically once every two weeks. Cache aggressively.

### 2. Step 1: Get the current season ID

Everything in the season dashboard is keyed on the season ID. The most efficient way to get it without hardcoding:

```http
GET /v3/motorsport/leagues/3468?api_token=YOUR_TOKEN&include=currentSeason
```

The `currentSeason` include returns the active season object embedded in the league response. Extract `currentSeason.id` - this is your season ID for all subsequent calls.

```javascript
const res = await fetch(
  `https://api.sportmonks.com/v3/motorsport/leagues/3468
?api_token=${TOKEN}&include=currentSeason`
);
const { data } = await res.json();
const seasonId = data.currentSeason.id; // e.g. 25273
const seasonName = data.currentSeason.name; // e.g. "2025"
```

Alternatively, if you already know the season ID (e.g. `25273` for 2025), you can skip this call and hardcode it. The season ID is stable - it will not change once assigned.

You can also confirm the season is still current and check when standings were last updated:

```http
GET /v3/motorsport/seasons/25273
?api_token=YOUR_TOKEN
```

The `standings_recalculated_at` field tells you exactly when the standings data was last updated - use this as a cache invalidation signal. If the timestamp has not changed since your last fetch, standings have not been updated and you do not need to re-fetch.

### 3. Step 2: Fetch driver and constructor standings

With the season ID in hand, fetch both championship tables in parallel:

**World Drivers' Championship:**

```http
GET /v3/motorsport/standings/drivers/seasons/25273
?api_token=YOUR_TOKEN&include=participant;stage
```

**World Constructors' Championship:**

```http
GET /v3/motorsport/standings/teams/seasons/25273
?api_token=YOUR_TOKEN&include=participant;stage
```

Both endpoints share the same response shape. The `participant` include resolves `participant_id` to a full driver or team profile. The `stage` include resolves `stage_id` to the most recently completed race weekend - use this to display "Standings after Round X: \[Grand Prix Name]" context in your UI.

Both endpoints support pagination - iterate through all pages to get the full grid (20 drivers, 10 teams).

```javascript
async function fetchAllPages(url, token) {
  const results = [];
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const res = await fetch(`${url}&page=${page}&api_token=${token}`);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);

    const payload = await res.json();
    results.push(...payload.data);

    hasMore = payload.pagination?.has_more ?? false;
    page++;
  }

  return results;
}

const BASE = 'https://api.sportmonks.com/v3/motorsport';

const [driverStandings, teamStandings] = await Promise.all([
  fetchAllPages(
    `${BASE}/standings/drivers/seasons/25273?include=participant;stage`,
    TOKEN
  ),
  fetchAllPages(
    `${BASE}/standings/teams/seasons/25273?include=participant;stage`,
    TOKEN
  )
]);
```

### 4. Step 3: Fetch the season schedule

The Schedules endpoint returns the complete season calendar - all race weekends, all sessions, and circuit data - in a single paginated response:

```http
GET /v3/motorsport/schedules/seasons/25273?api_token=YOUR_TOKEN
```

The response is nested: stages (race weekends) containing fixtures (sessions) containing venues (circuits with metadata). No includes needed - all data is pre-embedded.

Paginate through all stages and separate them into completed and upcoming rounds:

```javascript
async function fetchSchedule(seasonId, token) {
  const stages = [];
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const res = await fetch(
      `https://api.sportmonks.com/v3/motorsport/schedules/seasons/${seasonId}?api_token=${token}&page=${page}`
    );
    if (!res.ok) throw new Error(`HTTP ${res.status}`);

    const payload = await res.json();
    stages.push(...payload.data);

    hasMore = payload.pagination?.has_more ?? false;
    page++;
  }

  const completed = stages.filter(s => s.finished);
  const upcoming = stages.filter(s => !s.finished && !s.is_current);
  const current = stages.find(s => s.is_current) ?? null;

  return { completed, current, upcoming };
}
```

Cache this response aggressively - schedule data changes infrequently. Refresh it at the start of each race weekend and after any official FIA calendar revision.

### 5. Step 4: Fetch race results

For a season results table showing each driver's finish position per race, use the Race Results endpoint. It returns all race and sprint race fixtures with full lineup data for a specific driver or team:

```http
GET /v3/motorsport/results/seasons/25273/drivers/DRIVER_ID
?api_token=YOUR_TOKEN
```

This endpoint has no includes available - all data is pre-embedded. The response is nested: stages containing fixtures containing lineups containing details.

Each `detail` object in the lineup has a `type_id` identifying the data point (position, points, grid position, etc.). Resolve these via the [Results & Live Data Type Reference](https://docs.sportmonks.com/v3/motorsport-api/welcome/results-and-live-data-type-reference).

The `player_id` in lineup objects is the same value as `participant_id` in standings responses - use it as the join key to match standings data with race result data for the same driver.

For a full season results grid across all drivers, you would need to call this endpoint once per driver. For most dashboard use cases, fetch results on demand when a user selects a specific driver rather than pre-fetching all 20 drivers upfront.

### 6. Step 5: Assemble the dashboard

A typical season dashboard requires four data sets:

| Data                  | Endpoint                                      | Refresh frequency               |
| --------------------- | --------------------------------------------- | ------------------------------- |
| Season ID + metadata  | `leagues/3468?include=currentSeason`          | Once per session / on load      |
| Driver standings      | `standings/drivers/seasons/SEASON_ID`         | After each race weekend         |
| Constructor standings | `standings/teams/seasons/SEASON_ID`           | After each race weekend         |
| Season schedule       | `schedules/seasons/SEASON_ID`                 | Weekly / on FIA calendar change |
| Driver race results   | `results/seasons/SEASON_ID/drivers/DRIVER_ID` | On demand per driver            |

Fetch the first four in parallel on dashboard load. Use `standings_recalculated_at` from the season endpoint to decide whether a standings re-fetch is needed:

```javascript
async function loadDashboard(token) {
  const BASE = 'https://api.sportmonks.com/v3/motorsport';

  // Step 1: get season ID
  const leagueRes = await fetch(
    `${BASE}/leagues/3468?api_token=${token}&include=currentSeason`
  );
  const { data: league } = await leagueRes.json();
  const seasonId = league.currentSeason.id;
  const lastRecalculated = league.currentSeason.standings_recalculated_at;

  // Step 2: check cache
  const cached = getFromCache('dashboard');
  if (cached && cached.standings_recalculated_at === lastRecalculated) {
    return cached;
  }

  // Step 3: fetch in parallel
  const [driverStandings, teamStandings, schedule] = await Promise.all([
    fetchAllPages(
      `${BASE}/standings/drivers/seasons/${seasonId}?include=participant;stage`,
      token
    ),
    fetchAllPages(
      `${BASE}/standings/teams/seasons/${seasonId}?include=participant;stage`,
      token
    ),
    fetchSchedule(seasonId, token)
  ]);

  const dashboard = {
    seasonId,
    seasonName: league.currentSeason.name,
    standings_recalculated_at: lastRecalculated,
    driverStandings,
    teamStandings,
    schedule
  };

  saveToCache('dashboard', dashboard);
  return dashboard;
}
```

### 7. Common pitfalls

**Fetching standings without paginating**

Standings endpoints paginate. A 20-driver grid will return results across multiple pages. Always paginate to get all standings entries - stopping at page 1 will miss drivers further down the championship.

**Not using `standings_recalculated_at` as a cache signal**

Standings are recalculated after each race. The `standings_recalculated_at` field on the season object is the correct signal to check before re-fetching. Polling standings on a fixed timer wastes API calls between race weekends when nothing has changed.

**Expecting race results to include practice and qualifying**

The Race Results endpoint returns race sessions and sprint race sessions only. Practice, qualifying, and sprint qualifying fixtures are excluded. Use `GET Fixture by ID` with `?include=lineups.details` for session-level results from non-race sessions.

**Using `starting_at` string order for race results**

The Race Results response contains stages in the order returned by the API. Always use `sort_order` on the stage to order rounds chronologically in your results table - do not rely on `starting_at` string sorting.

**Pre-fetching all driver race results on load**

With 20 drivers, fetching all race results upfront means 20 paginated API calls. Fetch race results on demand when a user selects a driver, not on initial dashboard load.

### 8. Related features

* [Leagues](https://docs.sportmonks.com/v3/motorsport-api/endpoints-and-entities/endpoints/leagues) - League and `currentSeason` include
* [Seasons](https://docs.sportmonks.com/v3/motorsport-api/endpoints-and-entities/endpoints/seasons) - Season metadata and `standings_recalculated_at`
* [Standings](https://docs.sportmonks.com/v3/motorsport-api/endpoints-and-entities/endpoints/standings) - Driver and constructor championship tables
* [Race Results](https://docs.sportmonks.com/v3/motorsport-api/endpoints-and-entities/endpoints/race-results) - Season result records per driver or team
* [Schedules](https://docs.sportmonks.com/v3/motorsport-api/endpoints-and-entities/endpoints/schedules) - Full season calendar
* [Stages](https://docs.sportmonks.com/v3/motorsport-api/endpoints-and-entities/endpoints/stages) - Individual race weekend data
* [Results & Live Data Type Reference](https://docs.sportmonks.com/v3/motorsport-api/welcome/results-and-live-data-type-reference)

### 9. FAQ

**Q: How do I know when standings have been updated after a race?**

Check `standings_recalculated_at` on the season object. This timestamp updates after each race weekend once points have been processed. If it has changed since your last fetch, re-fetch standings. If it has not changed, your cached data is still current.

**Q: How do I display "Standings after Round X"?**

Include `?include=stage` on the standings endpoints. The `stage` object embedded on each standing record is the most recently completed race weekend. Use its `name` and `sort_order` to display "Standings after Round 12: Formula 1 British Grand Prix".

**Q: Can I get the full season results grid for all drivers in one call?**

No. The Race Results endpoint is scoped to a single driver or team per call. For a full grid, call it once per driver. For a more efficient alternative, use the Fixtures endpoint with `?include=lineups.details` on individual race fixtures, which returns all drivers' results for that specific session in a single call.

**Q: How do I find a driver's ID to use in the race results endpoint?**

Use the search endpoint: `GET /v3/motorsport/drivers/search/norris?api_token=YOUR_TOKEN`. The `id` returned is the same value used as `player_id` in race results responses and `participant_id` in standings responses.

**Q: Is this available in all subscription plans?**

All Motorsport API endpoints require an active Motorsport API subscription (€79/mo). [View pricing →](https://www.sportmonks.com/formula-one-api/)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.sportmonks.com/v3/motorsport-api/tutorials-and-guides/guides/season-dashboard-guide.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
