{"openapi":"3.1.0","info":{"title":"Formula-API.com","version":"1.0.0","description":"A standardized Formula 1 API. Data is normalized from multiple upstream providers into one consistent schema"},"servers":[{"url":"https://api.formula-api.com/v1"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key, issued on request."}}},"paths":{"/drivers":{"get":{"summary":"List drivers","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}]}},"/drivers/{id}":{"get":{"summary":"Get a driver by id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}]}},"/drivers/{id}/career":{"get":{"summary":"Pre-aggregated career stats for a driver — one call instead of folding per-season requests","description":"Wins/podiums/points/DNFs/DNS/DSQ/completion rate/pole positions/championships, plus a separate sprint-specific breakdown (sprintsEntered/sprintWins/sprintPodiums/sprintPoints) rather than folding sprint results into the race numbers. Computed live from `results` on every request, same as standings — nothing to keep in sync, the next ingest is just included next time this is called.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}]}},"/constructors":{"get":{"summary":"List constructors (teams)","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}]}},"/circuits":{"get":{"summary":"List circuits","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":200}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}]}},"/seasons":{"get":{"summary":"List seasons with data"}},"/seasons/{year}/races":{"get":{"summary":"List race weekends (meetings) in a season"}},"/races/{meetingId}/sessions":{"get":{"summary":"List sessions in a race weekend","description":"Each session includes `status` (scheduled/live/completed) — computed live from startTime/endTime vs. the current time on every request, never stored, so it's always correct — and `resultsStatus` (provisional/final/null) for qualifying and race sessions: results are visible immediately as \"provisional\" and flip to \"final\" once the post-session re-check Workflow has confirmed no grid penalty, DSQ, or points correction landed after initial ingest. Never held back — always the latest data we have, labeled."}},"/next-race":{"get":{"summary":"The next (or currently in-progress) race weekend, with venue and full session schedule","description":"One call instead of joining meeting + circuit + sessions yourself. Returns the earliest meeting that still has at least one non-completed session — each session includes `status` the same way /races/{meetingId}/sessions does. `data: null` when nothing in the ingested data qualifies (e.g. only fully-historical seasons are loaded) — an honest empty result, not stale data. Requires an API key: this is inherently current/future data, not historical."}},"/sessions/{id}/results":{"get":{"summary":"Get the final classification for a session"}},"/sessions/{id}/laps":{"get":{"summary":"Get lap times for a session","parameters":[{"name":"driverId","in":"query","schema":{"type":"string"}}]}},"/sessions/{id}/weather":{"get":{"summary":"Get weather readings for a session (one roughly every minute)"}},"/sessions/{id}/pitstops":{"get":{"summary":"Get pit stops for a session"}},"/sessions/{id}/race-control":{"get":{"summary":"Get the race control feed for a session, translated into structured events","description":"Each event keeps the original provider message plus a translated category (flag, safety_car, penalty, investigation, time_deleted, ...), a human-readable summary, and resolved driver ids for any car(s) referenced."}},"/sessions/{id}/snapshot":{"get":{"summary":"Everything about one session in a single call","description":"Session + meeting + circuit info (with status), classification (results, including gapSeconds to the leader for race/sprint), pit stops, weather, translated race control, and season-scoped previous/next meeting navigation — one call instead of the 6 separate fetches a client would otherwise need. Deliberately excludes lap-by-lap data (use /sessions/{id}/laps directly — a full race is 800+ rows) and stints/tire strategy (not ingested from either provider yet)."}},"/standings/drivers":{"get":{"summary":"Driver championship standings, plus season stats and each driver's team standing","description":"Each entry includes points/wins/podiums/DNFs/DNS/races entered (all race-session-scoped, not sprint — the standard convention) and `constructorStanding` — the driver's most recent team this season and that team's own position/points, in the same call. The response also carries `seasonStatus` once at the top level (e.g. \"Round 13/24\" or \"Complete\"), shared across every entry rather than repeated per row.","parameters":[{"name":"season","in":"query","schema":{"type":"integer"},"description":"Defaults to the current year."}]}},"/standings/constructors":{"get":{"summary":"Constructor championship standings, plus season stats","description":"Same shape as /standings/drivers, minus constructorStanding (which only applies to driver entries).","parameters":[{"name":"season","in":"query","schema":{"type":"integer"},"description":"Defaults to the current year."}]}},"/meta/providers":{"get":{"summary":"Which upstream provider(s) back each entity type"}},"/webhooks/blacktop":{"post":{"summary":"Receiver for Blacktop's live-data webhooks (internal — not for API consumers)","description":"Verifies the `X-Blacktop-Signature` HMAC-SHA256 header against `BLACKTOP_WEBHOOK_SECRET` and archives every raw delivery to R2. No event-to-schema mapping yet — this only proves the receiver is secure and captures real payload shapes before that gets designed. Not authenticated the normal API-key way; the signature is the entire trust model."}},"/meta/whoami":{"get":{"summary":"Your API key's current rate-limit usage — does not itself count against your limits"}},"/live/tickets":{"post":{"summary":"Mint a short-lived WebSocket ticket","description":"Requires a normal API key (Authorization header). Returns a random, single-use, 60-second ticket to open with GET /live/sessions/{id} — a raw API key can never be sent on a WebSocket upgrade (browsers can't set custom headers there), so this keeps the long-lived key out of the connection URL entirely."}},"/live/sessions/{id}":{"get":{"summary":"Subscribe to live-translated race control events (WebSocket upgrade)","description":"Not a plain HTTP endpoint — send a WebSocket upgrade request with ?ticket=<value> from POST /live/tickets. Pushes the same translated RaceControlEvent shape as GET /sessions/{id}/race-control as new events arrive.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"ticket","in":"query","required":true,"schema":{"type":"string"}}]}}}}