REST + WSS · v1 · snapshot 2026

FormulaAPI reference

FormulaAPI is a single REST + WebSocket surface over Formula 1, Formula 2, Formula 3, and F1 Academy. The same response shapes are used across all four series — the only thing that changes is the series slug in the URL.

Overview

All REST endpoints are mounted under /v1. Series are addressed by lowercase slug: f1, f2, f3, f1a. Every resource has a stable ULID-style publicId.

GET/v1/healthLiveness + dependency probe (Postgres, Redis, ingest freshness)
GET/v1/seriesList all active series
GET/v1/series/:slugRetrieve one series
GET/v1/series/:slug/seasonsSeasons for a series
GET/v1/series/:slug/seasons/:year/calendarRounds & sessions
GET/v1/series/:slug/seasons/:year/standings/driversDriver standings
GET/v1/series/:slug/seasons/:year/standings/teamsTeam standings
GET/v1/sessions/:publicIdRetrieve a session
GET/v1/sessions/:publicId/resultsPer-session results
GET/v1/sessions/:publicId/lapsPer-driver lap times
GET/v1/sessions/:publicId/pit-stopsPit stops (F1 historic via f1db)
GET/v1/drivers/:publicIdDriver profile
GET/v1/drivers/:publicId/resultsCross-series driver career
GET/v1/teams/:publicIdTeam profile
GET/v1/rounds/:publicIdRound + its sessions
GET/v1/circuits/:publicIdCircuit metadata
WSS/v1/liveSubscribe to live timing

Quickstart

Request a key, set the Authorization header, you're done — no SDK install, no setup. The same key works for REST and WebSocket.

Shell
JavaScript
first 60 seconds
# 1. Request an API key
#    email hello@codai.app with your project name + intended use

# 2. Verify it's live
curl https://api.formulaapi.codai.app/v1/health

# 3. First call — F2 2026 driver standings
curl -H "Authorization: Bearer $FORMULA_API_KEY" \
  "https://api.formulaapi.codai.app/v1/series/f2/seasons/2026/standings/drivers" | jq '.standings[:3]'

# 4. Cross-series career for a single driver
curl -H "Authorization: Bearer $FORMULA_API_KEY" \
  "https://api.formulaapi.codai.app/v1/drivers/<driverPublicId>/results" | jq

# 5. Subscribe to live timing (Indie plan and up)
wscat -c "wss://api.formulaapi.codai.app/v1/live?key=$FORMULA_API_KEY"
> {"subscribe":"session:f2:<sessionPublicId>"}

Authentication

REST requests require Authorization: Bearer <your_api_key>. The WebSocket endpoint accepts the same key via a ?key= query parameter on the upgrade request. Live timing is available on all paid plans. Email hello@codai.app to request a key.

cURL
Response
Authenticated REST request
curl https://api.formulaapi.codai.app/v1/series \
  -H "Authorization: Bearer $FORMULA_API_KEY"

Plans & rate limits

PlanRESTLive timingSupportPrice
Indie500k req / month · 600 req/min100 session-hours / monthemail · 48h response€29 / mo
Pro5M req / month · 3000 req/min2000 session-hours / monthemail · 24h response€149 / mo
Enterprisecustom, dedicated poolunlimitedshared Slack · 99.9% SLAcontact

Rate limits are sliding-window per minute and rolling per month. Hit either and you'll get HTTP 429 with error: rate_limit_exceeded and aRetry-After header. Email hello@codai.app to request a key.

Public IDs

Every entity in the API is addressed by a stable publicId — a lowercase ULID (26 base32 characters). They are URL-safe, k-sortable by creation time, and never change. Use them to identify drivers, teams, rounds, sessions and circuits across calls.

Versioning

The current version is v1; all endpoints are mounted under that prefix. Breaking changes ship a new version prefix (/v2, …) — /v1 is supported for at least 12 months after a successor lands. Additive changes (new optional fields, new endpoints, new frame kinds) ship in-place under /v1.

Series

GET/v1/seriesList all active series, ordered by tier
GET/v1/series/:slugRetrieve a single series

The 4 series currently served: f1, f2, f3, f1a.

Seasons & calendar

GET/v1/series/:slug/seasonsEvery season for a series
GET/v1/series/:slug/seasons/:yearOne season
GET/v1/series/:slug/seasons/:year/calendarRounds and their sessions

The calendar response groups sessions under their parent round and includes circuit metadata where available. Excerpt from a real F2 2026 round:

Response (1 round shown)
GET https://api.formulaapi.codai.app/v1/series/f2/seasons/2026/calendar
{
  "rounds": [
    {
      "publicId": "01krvntynhrz6wcp1a27d23p9h",
      "roundNumber": 1,
      "name": "Melbourne",
      "startDate": "2026-03-06",
      "endDate": "2026-03-08",
      "circuit": {
        "name": "Melbourne",
        "country": "AU"
      },
      "sessions": [
        {
          "publicId": "01krvntzq4e47ttxa7ctmsf5p5",
          "type": "practice",
          "scheduledAt": "2026-03-05T23:00:00.000Z",
          "status": "finished"
        },
        {
          "publicId": "01krvntzy1me92vr4mg6kjpe00",
          "type": "qualifying",
          "scheduledAt": "2026-03-06T03:55:00.000Z",
          "status": "finished"
        },
        {
          "publicId": "01krvntynrbydcb4dksq4sqkgr",
          "type": "sprint",
          "scheduledAt": "2026-03-07T03:30:00.000Z",
          "status": "finished"
        },
        {
          "publicId": "01krvntyny78e3tc9fzn8xd52c",
          "type": "feature_race",
          "scheduledAt": "2026-03-08T00:25:00.000Z",
          "status": "finished"
        }
      ]
    }
  ]
}

Session types

The type field on a session is one of: practice, fp1,fp2, fp3, qualifying, sprint_qualifying,sprint, race, feature_race,pre_qualifying, warm_up.

Session status

scheduled, live, finished, cancelled, postponed.

Standings

GET/v1/series/:slug/seasons/:year/standings/driversDriver championship
GET/v1/series/:slug/seasons/:year/standings/teamsConstructor championship
Response (leader only)
GET https://api.formulaapi.codai.app/v1/series/f2/seasons/2026/standings/drivers
{
  "standings": [
    {
      "position": 1,
      "points": "35.00",
      "wins": 0,
      "podiums": 0,
      "driver": {
        "publicId": "01krvntz0xa2xqbr4h68m0kyza",
        "firstName": "Nikola",
        "lastName": "Tsolov",
        "abbreviation": "TSO",
        "nationality": null
      },
      "team": {
        "publicId": "01krvntz16gspmn70dwgzbvze6",
        "name": "Campos Racing",
        "color": null
      }
    }
  ]
}

Sessions

A session is the atomic unit — a single on-track activity (practice, qualifying, sprint, race, etc.). All sessions are addressed by their publicId, independent of series.

GET/v1/sessions/:publicIdSession metadata
GET/v1/sessions/:publicId/resultsFinal classification
GET/v1/sessions/:publicId/lapsEvery lap of every driver (live + historic where collected)
GET/v1/sessions/:publicId/pit-stopsPit stops (currently F1 historic from f1db)

Result status

Each result row has a status: finished, dnf,dns, dsq, nc, or lapped. Results may be marked isProvisional: true while a session is in progress — once stewards finalise the classification, the row is rewritten withisProvisional: false.

Response (winner only)
GET https://api.formulaapi.codai.app/v1/sessions/01krvntypycbqew4vs9rs3qhm2/results
{
  "results": [
    {
      "position": 1,
      "gridPosition": null,
      "points": "0.00",
      "lapsCompleted": 25,
      "fastestLap": false,
      "fastestLapTimeMs": 116041,
      "gapToLeaderMs": null,
      "status": "finished",
      "isProvisional": false,
      "driver": {
        "publicId": "01krvntz2c41tc6jv9rnp3tdm7",
        "firstName": "Gabriele",
        "lastName": "Minì",
        "abbreviation": "MIN"
      },
      "team": {
        "publicId": "01krvntz2ma833ga4exp8bwj8j",
        "name": "MP Motorsport",
        "color": null
      }
    }
  ]
}

Drivers & teams

GET/v1/drivers/:publicIdDriver profile
GET/v1/drivers/:publicId/resultsCross-series career — every session_result, latest first
GET/v1/teams/:publicIdTeam profile
GET/v1/rounds/:publicIdRound + its sessions
GET/v1/circuits/:publicIdCircuit metadata

Driver entities span every series: a single publicId follows a driver from F3 to F2 to F1 and beyond. /v1/drivers/:publicId/resultsreturns up to 500 of the most recent session results across all series the driver has competed in.

WebSocket /v1/live

Connect to /v1/live, authenticate via ?key=<api_key>, then send subscribe messages for the sessions you want to follow. The server pushes one JSON frame per event with a millisecond timestamp; ordering within a session is guaranteed.

Connection

EndpointAuthPlans
wss://api.formulaapi.codai.app/v1/live?key=<api_key>Indie, Pro, Enterprise

Client messages

MessageEffect
{"subscribe":"session:<series>:<publicId>"}Subscribe to a session's live frames. The session:<series>: prefix is optional — bare publicId also works.
{"unsubscribe":"<publicId>"}Stop receiving frames for a session.

Server messages

Acknowledgements: {"subscribed":"<publicId>"} and {"unsubscribed":"<publicId>"}. Errors: {"error":"unknown_session","sessionId":"..."} or {"error":"invalid_json"}. Live frames follow the shape below.

JavaScript
Python
Raw WSS
live timing stream
const ws = new WebSocket(
  `wss://api.formulaapi.codai.app/v1/live?key=${process.env.FORMULA_API_KEY}`
);

ws.onopen = () => {
  // topic format: "session:<series>:<sessionPublicId>" — bare publicId also works
  ws.send(JSON.stringify({
    subscribe: 'session:f2:01krvntypycbqew4vs9rs3qhm2'
  }));
};

ws.onmessage = (event) => {
  const frame = JSON.parse(event.data);
  // { series, sessionPublicId, ts, kind, payload }
  if (frame.kind === 'timing') {
    // per-driver position, lap, sectors, gap
    console.log(frame.payload);
  } else if (frame.kind === 'race_control') {
    notifyBroadcast(frame.payload);
  }
};

Frame kinds

Every live frame on the stream has the same envelope —{ series, sessionPublicId, ts, kind, payload } — and thekind field determines what's inside payload.

KindFrequencyPayload
session_statuson changeStatus transitions (scheduled → live → finished).
session_clockperiodicServer-authoritative remaining-time / lap-of clock.
timingcontinuousPer-driver timing snapshot (position, current lap, last lap, sectors).
positionon changeDriver position + gap to leader.
lap_timeper lapCompleted lap with sector splits.
sectorper sectorSector split with purple/green/yellow flag.
piton eventPit-in / pit-out, tire change, stop duration.
weatherperiodicAir & track temp, humidity, wind, rain.
track_statuson changeGreen / yellow / SC / VSC / red.
race_controlon eventStewards notes, penalties, investigations.
team_radioon eventDriver↔pit radio transcripts (where licensed).

Errors

Error responses are JSON with a stable error code. Common codes:not_found, series_not_found, invalid_json,unknown_session, missing_api_key, invalid_api_key,rate_limit_exceeded.

404 not_found
{
  "error": "not_found"
}