> 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/tutorials/schedule-and-calendar/schedule.md).

# Schedule

This guide covers how to retrieve the full race calendar for a season using the Schedules endpoint, and what data is available at the schedule level.

#### When to use this

Use the Schedules endpoint when you want to:

* Retrieve all race weekends for a season in a single call, each with their sessions and venue
* Build a season calendar view showing all rounds with dates and circuit names
* Get a structured overview of the season without querying each stage individually

#### What the schedule returns

The Schedule is a denormalised view that combines stage, fixture, and venue data for a given league and season in one response. Unlike querying stages or fixtures separately, the Schedules endpoint returns all three together without requiring nested includes.

The Schedule entity contains three nested objects per race weekend:

* **Stages** - the race weekend itself (e.g. "Bahrain Grand Prix 2025")
* **Fixtures** - each individual session within that weekend (Practice 1, Qualifying, Race, etc.)
* **Venues** - the circuit where the weekend takes place

#### Retrieving the schedule

The Schedules endpoint requires a `season_id`. See [Leagues and seasons](https://docs.sportmonks.com/v3/motorsport-api/tutorials-and-guides/tutorials/schedule-and-calendar/leagues-and-seasons) for how to find it.

```http
GET https://api.sportmonks.com/v3/motorsport/schedules/seasons/{season_id}
?api_token={your_token}
```

A schedule response looks like this (partial):

```json
{
  "data": [
    {
      "id": 77475992,
      "sport_id": 2,
      "league_id": 3468,
      "season_id": 25273,
      "type_id": 72,
      "name": "Bahrain Grand Prix 2025",
      "sort_order": 1,
      "finished": 1,
      "is_current": 0,
      "starting_at": "2025-03-14",
      "ending_at": "2025-03-16",
      "fixtures": [
        {
          "id": 19398653,
          "name": "Practice 1",
          "starting_at": "2025-03-14 01:30:00",
          "state_id": 5,
          "venue_id": 343575,
          "leg": "1/3",
          "length": 60
        },
        {
          "id": 19398655,
          "name": "Qualifying",
          "starting_at": "2025-03-15 14:00:00",
          "state_id": 5,
          "venue_id": 343575,
          "leg": "3/3",
          "length": 60
        },
        {
          "id": 19398657,
          "name": "Race",
          "starting_at": "2025-03-16 15:00:00",
          "state_id": 5,
          "venue_id": 343575,
          "leg": "1/1",
          "length": 57
        }
      ],
      "venues": [
        {
          "id": 343575,
          "name": "Bahrain International Circuit",
          "city_name": "Sakhir",
          "country_id": 38,
          "capacity": 70000,
          "image_path": "https://cdn.sportmonks.com/images/motorsport/venues/343575.png",
          "latitude": "26.0325",
          "longitude": "50.5106",
          "surface": "Asphalt"
        }
      ]
    }
  ]
}
```

Note the `leg` field on each fixture. It shows the session's position within its session type: `"1/3"` means Practice 1 of 3, `"3/3"` means the third qualifying session. For race sessions it returns `"1/1"`. The `length` field returns planned session length in minutes for practice and qualifying, and total lap count for race sessions.

#### Working with the data

**Build a race calendar**

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

```javascript
const API_TOKEN = 'your_token';

async function getRaceCalendar(seasonId) {
  const response = await fetch(
    `https://api.sportmonks.com/v3/motorsport/schedules/seasons/${seasonId}?api_token=${API_TOKEN}`
  );
  const { data } = await response.json();

  return data
    .sort((a, b) => a.sort_order - b.sort_order)
    .map(round => {
      const venue = round.venues?.[0] ?? {};
      const race = round.fixtures?.find(f => f.name === 'Race');
      return {
        round: round.sort_order,
        name: round.name,
        circuit: venue.name,
        city: venue.city_name,
        raceDate: race?.starting_at ?? null,
        finished: round.finished === 1
      };
    });
}
```

{% endtab %}

{% tab title="Python" %}

```python
import requests

API_TOKEN = "your_token"

def get_race_calendar(season_id):
    url = f"https://api.sportmonks.com/v3/motorsport/schedules/seasons/{season_id}"
    params = {"api_token": API_TOKEN}
    response = requests.get(url, params=params)
    rounds = response.json()["data"]

    calendar = []
    for round_ in sorted(rounds, key=lambda r: r["sort_order"]):
        venue = round_["venues"][0] if round_["venues"] else {}
        race = next((f for f in round_["fixtures"] if f["name"] == "Race"), None)
        calendar.append({
            "round": round_["sort_order"],
            "name": round_["name"],
            "circuit": venue.get("name"),
            "city": venue.get("city_name"),
            "race_date": race["starting_at"] if race else None,
            "finished": bool(round_["finished"])
        })
    return calendar
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php
function getRaceCalendar(string $apiToken, int $seasonId): array {
    $url = "https://api.sportmonks.com/v3/motorsport/schedules/seasons/{$seasonId}?api_token={$apiToken}";
    $response = json_decode(file_get_contents($url), true);
    $rounds = $response['data'];
    usort($rounds, fn($a, $b) => $a['sort_order'] <=> $b['sort_order']);
    return array_map(function($round) {
        $venue = $round['venues'][0] ?? [];
        $race = null;
        foreach ($round['fixtures'] as $fixture) {
            if ($fixture['name'] === 'Race') {
                $race = $fixture;
                break;
            }
        }
        return [
            'round' => $round['sort_order'],
            'name' => $round['name'],
            'circuit' => $venue['name'] ?? null,
            'city' => $venue['city_name'] ?? null,
            'race_date' => $race['starting_at'] ?? null,
            'finished' => $round['finished'] === 1
        ];
    }, $rounds);
}
```

{% endtab %}
{% endtabs %}

**Find the next upcoming race weekend**

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

```java
async function getNextRace(seasonId) {
  const calendar = await getRaceCalendar(seasonId);
  const now = new Date();
  return calendar.find(r => r.raceDate && !r.finished 
        && new Date(r.raceDate) > now) ?? null;
}
```

{% endtab %}

{% tab title="Python" %}

```python
from datetime import datetime, timezone

def get_next_race(season_id):
    calendar = get_race_calendar(season_id)
    now = datetime.now(timezone.utc)
    upcoming = [
        r for r in calendar
        if r["race_date"] and not r["finished"]
        and datetime.fromisoformat(r["race_date"]).replace(tzinfo=timezone.utc) > now
    ]
    return upcoming[0] if upcoming else None
```

{% endtab %}

{% tab title="PHP" %}

```php
<?php

function get_next_race($seasonId) {
    $calendar = get_race_calendar($seasonId);
    $now = new DateTimeImmutable("now", new DateTimeZone("UTC"));

    $upcoming = array_filter($calendar, function ($race) use ($now) {
        if (empty($race["race_date"]) || !empty($race["finished"])) {
            return false;
        }

        $raceDate = new DateTimeImmutable($race["race_date"], new DateTimeZone("UTC"));

        return $raceDate > $now;
    });

    $upcoming = array_values($upcoming);

    return $upcoming[0] ?? null;
}
```

{% endtab %}
{% endtabs %}

#### Common pitfalls

**Querying by `league_id` instead of `season_id`.** The Schedules endpoint requires a `season_id`. Pass the league ID to the seasons endpoint first to resolve the current season. See [Leagues and seasons](https://docs.sportmonks.com/v3/motorsport-api/tutorials-and-guides/tutorials/schedule-and-calendar/leagues-and-seasons).

**Assuming `venues` always has one entry.** The `venues` array on each round is a list. In all known cases it contains exactly one entry per round, but defensively access it as `venues[0]` with a null check.

**Matching session type by `name` string.** Session names like `"Practice 1"` and `"Race"` are reliable across the current dataset, but can vary for sprint weekends (where `"Practice 1"` may be replaced by `"Sprint Qualifying"`). Check the `leg` field as a secondary signal for session ordering.

**The Schedule entity has no include options.** Unlike Fixtures or Stages, the Schedule endpoint returns stages, fixtures, and venues as a flat embedded response. There are no additional includes available on this endpoint.

#### Common errors

| Status | Likely cause                                                  |
| ------ | ------------------------------------------------------------- |
| `401`  | Missing or invalid `api_token`                                |
| `404`  | The `season_id` does not exist or is not in your subscription |

#### See also

**Related tutorials**

* [Leagues and seasons](https://docs.sportmonks.com/v3/motorsport-api/tutorials-and-guides/tutorials/schedule-and-calendar/leagues-and-seasons)
* [Stages](https://docs.sportmonks.com/v3/motorsport-api/tutorials-and-guides/tutorials/schedule-and-calendar/stages)
* [Fixtures](https://docs.sportmonks.com/v3/motorsport-api/tutorials-and-guides/tutorials/fixtures)

**Reference**

* [Schedule entity](https://docs.sportmonks.com/v3/motorsport-api/endpoints-and-entities/entities/schedule.md)
* [Stage entity](https://docs.sportmonks.com/v3/motorsport-api/endpoints-and-entities/entities/stage.md)

#### FAQ

**What is the difference between the Schedule endpoint and the Stages endpoint?** The Schedule endpoint returns a denormalised view of the entire season at once, combining stages, fixtures, and venues in a single response without extra includes. The Stages endpoint returns stages individually or paginated, with includes available to attach fixtures separately. Use Schedule for building a full season calendar; use Stages when you need more control over what gets included.

**Does the Schedule endpoint paginate?** Yes. For a full season with 24 rounds, the default page size of 25 is usually sufficient, but use cursor pagination to be safe. See [Pagination](https://docs.sportmonks.com/v3/motorsport-api/tutorials-and-guides/tutorials/introduction/pagination).

**How do I tell if a race weekend has a sprint race?** Check the fixture names within a round. Sprint weekends replace one practice session with a sprint qualifying and sprint race session. The `leg` field will also reflect the changed session count.


---

# 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/tutorials/schedule-and-calendar/schedule.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.
