← All guides

Conventions: envelope, categories, paging and errors

The rules every MotoGP API endpoint follows — response envelope, Moto2/Moto3 categories, pagination, identifiers, error bodies and caching.

6 min read

Learn these rules once and every endpoint behaves the way you expect.

The response envelope

Every successful response is a JSON object with two keys:

KeyContents
dataAn object (single resource) or an array (list)
metatotal, page, size, total_pages — paging fields are null when the endpoint is not paged
{
  "data": { "year": 2026, "status": "ACTIVE" },
  "meta": { "total": 1, "page": null, "size": null, "total_pages": null }
}

Field names are snake_case throughout (rider_number, total_points, season_year).

Categories: MotoGP, Moto2 and Moto3

Season-scoped endpoints accept a category query parameter with one of MOTOGP, MOTO2 or MOTO3. class is an alias with the same values — send one or the other, not both.

GET /standings/seasons/2026/riders?category=MOTO2
GET /races/seasons/2026?category=MOTO3

Pass category explicitly so the result never depends on a default.

Paging

The “get all” endpoints — /riders, /teams, /races and /standings — are paged with page (from 1) and size (1–200, default 20):

GET /riders?page=2&size=50
"meta": { "total": 118, "page": 2, "size": 50, "total_pages": 3 }

Loop until page reaches total_pages:

async function getAll(path) {
  const rows = [];
  for (let page = 1; ; page++) {
    const { data, meta } = await getEnvelope(`${path}?page=${page}&size=200`);
    rows.push(...data);
    if (!meta.total_pages || page >= meta.total_pages) return rows;
  }
}

Season-scoped lists such as /riders/seasons/{year} return the whole season in one response.

Ordering

Standings come back ordered by position. Calendars and race results are not guaranteed to be ordered — sort them yourself:

races.sort((a, b) => a.round_number - b.round_number);
results.sort((a, b) => a.position - b.position);

Identifiers

ResourceLook it up by
RiderRace number: /riders/{rider_number} (e.g. 93)
TeamOpaque ID from any team list: /teams/{id}, or by name: /teams/name/{name}
RaceSeason + round: /races/seasons/{year}/round/{round}, or opaque ID: /races/{race_id}/results
SeasonYear: /seasons/{year}

Take opaque IDs ("82MQRdM5q7ub4OSm6Jhx") from a list response instead of hard-coding them, and match teams by ID rather than by name — sponsor names change from season to season.

Standing types

Besides the dedicated routes (/riders, /teams, /manufacturers, /bmw-m), you can request a standing table by type:

GET /standings/seasons/2026/type/MANUFACTURER?category=MOTOGP

Types are RIDER, TEAM, MANUFACTURER and BMW_M.

Errors

Errors use standard HTTP status codes and a JSON body:

{
  "timestamp": "2026-01-27T19:18:29",
  "status": 404,
  "error": "Not Found",
  "message": "Rider not found with number: 99",
  "path": "/riders/99"
}
StatusMeaningWhat to do
400Invalid parameter (bad year, unknown category)Fix the request; validation_errors lists the problems when present
401 / 403Missing or invalid RapidAPI key, or no subscriptionCheck both headers and your plan
404The rider, team, race or season doesn’t existShow a “not found” state
429Rate limit or quota reached on your planBack off and retry later, or upgrade the plan
5xxTemporary server problemRetry with exponential backoff; check the status page

Caching

Results and standings change after each race weekend, and profiles change between seasons. Cache aggressively to save quota and keep pages fast:

DataSuggested cache time
Seasons, riders, teams, career stats24 hours
Calendar12 hours
Results and standings1 hour on race weekends, 12 hours otherwise

Keep your key on the server

Your RapidAPI key is a billing credential. Call the API from your backend, a serverless function or a build step — never from browser or mobile code that ships the key to users. The standings table guide shows a small proxy with caching.

Next guide → Build a live championship standings table