← All guides

Season calendar, next race and results

Build a race calendar with a "next race" countdown and a results page per round, in Python.

8 min read

Every racing app needs a calendar. This guide builds three things from the races endpoints:

  1. The full season calendar in round order.
  2. The next race, for a countdown or a push notification.
  3. The classified results of a finished round.

The calendar

GET /races/seasons/{year} returns every round of a season for one category:

{
  "round_number": 12,
  "name": "Qatar Airways Grand Prix of Great Britain",
  "circuit_name": "Silverstone Circuit, Silverstone",
  "country": "United Kingdom",
  "flag": "🇬🇧",
  "category": "MOTOGP",
  "race_date": "2026-08-09",
  "season_year": 2026
}

Rounds are not returned in order, so sort them by round_number:

import os
from datetime import date

import requests

HOST = "motogp-api-championship-data-statistics.p.rapidapi.com"
HEADERS = {"X-RapidAPI-Key": os.environ["RAPIDAPI_KEY"], "X-RapidAPI-Host": HOST}


def get(path: str, **params):
    res = requests.get(f"https://{HOST}{path}", headers=HEADERS, params=params, timeout=10)
    res.raise_for_status()
    return res.json()["data"]


def calendar(year: int, category: str = "MOTOGP") -> list[dict]:
    races = get(f"/races/seasons/{year}", category=category)
    return sorted(races, key=lambda r: r["round_number"])


for race in calendar(2026):
    print(f"R{race['round_number']:>2}  {race['race_date']}  {race['flag']}  {race['name']}")
...
R 6  2026-05-17  🇪🇸  Monster Energy Grand Prix of Catalunya
...
R12  2026-08-09  🇬🇧  Qatar Airways Grand Prix of Great Britain
...

The next race

race_date is an ISO date (YYYY-MM-DD), so it sorts and compares as a string or parses with date.fromisoformat:

def next_race(races: list[dict], today: date) -> dict | None:
    upcoming = [r for r in races if date.fromisoformat(r["race_date"]) >= today]
    return upcoming[0] if upcoming else None


race = next_race(calendar(2026), date.today())
if race:
    days = (date.fromisoformat(race["race_date"]) - date.today()).days
    print(f"Next: {race['name']} at {race['circuit_name']} in {days} days")
else:
    print("Season finished")

Results of a round

Once a round has been run, its results are available by season and round:

def results(year: int, round_number: int, category: str = "MOTOGP") -> list[dict]:
    rows = get(f"/races/seasons/{year}/round/{round_number}/results", category=category)
    return sorted(rows, key=lambda r: r["position"])


for row in results(2026, 1)[:3]:
    print(f"P{row['position']}  {row['rider_name']:<22} {row['team_name']:<32} +{row['points']} pts")
P1  Marco Bezzecchi        Aprilia Racing                   +25 pts
P2  ...
P3  Raúl Fernández         SuperFile Trackhouse MotoGP Team +16 pts

Each row has a status — FINISHED for classified riders, and values such as DNF or DNS otherwise. Show non-finishers below the classified riders.

Polling after a race

Results are added after each race weekend. If you send “results are in” notifications, poll the results endpoint for the latest round every 30–60 minutes after the race date instead of every few seconds: an empty list means “not yet”, and slow polling keeps you well inside your quota. A round that is not on the calendar returns 404.

def results_ready(year: int, round_number: int) -> bool:
    return len(results(year, round_number)) > 0

Moto2 and Moto3

Pass category="MOTO2" or category="MOTO3" to any of the functions above. Round numbers and dates follow the same calendar, so a combined weekend view is a merge on round_number.

Next guide → Score a fantasy MotoGP league