## What this is F1 Relay connects to Formula 1's live timing feed and re-broadcasts the parts that describe what is happening on track — flags, safety car, session phase — over one WebSocket. The relay absorbs upstream protocol changes so clients do not have to. This document is the complete client contract. It is served as HTML at `/` and as Markdown at `/llms.txt`. Paths below are relative to the origin serving it. ## Connect Open a WebSocket to `/ws`. Use `wss://` if this document was served over HTTPS, `ws://` otherwise. ```js const ws = new WebSocket('wss://relay.example/ws'); ws.onmessage = (e) => { const s = JSON.parse(e.data); console.log(s.display, s.session, s.name); }; ``` You receive the full current state immediately on connect, and a new message every time it changes. No subscription handshake, no client-to-server messages — the relay ignores anything you send, and closes the connection if a single frame you send exceeds 1 KB. If a WebSocket is impractical, poll `GET /state` instead. It returns the exact same object. ``` curl https://relay.example/state ``` ## Message shape Every message is the complete state. There are no diffs and no partial updates, so applying one is a straight overwrite with nothing to reconcile. ```json {"v":1,"display":"SC","track":"SC","session":"live","type":"Race", "name":"Hungarian Grand Prix","stale":false,"override":false, "ts":"2026-07-26T13:40:00Z"} ``` | Field | Type | Meaning | |-------|------|---------| | `v` | number | Protocol version. Increments only on breaking changes. | | `display` | string | Track status and session phase resolved into one actionable value. **If your client shows a single state, use this field alone.** | | `track` | string or null | Raw track status, unresolved. | | `session` | string or null | Session phase. | | `type` | string or null | Session label, e.g. `Race`, `Qualifying`, `Practice 2`. | | `name` | string or null | Meeting name, e.g. `Hungarian Grand Prix`. | | `stale` | boolean | `true` while the relay's link to F1 is down. The values shown are last-known, not current. | | `override` | boolean | `true` while an operator test broadcast is active. Not a real session event. | | `ts` | string | Message timestamp, ISO 8601. | Clients **must** ignore unknown fields. New fields can be added within `v: 1`. ## display The resolved value, and the only field most clients need. Exactly one of: | Value | Meaning | |-------|---------| | `RED` | Red flag. Session stopped. | | `SC` | Safety car deployed. | | `VSC` | Virtual safety car. | | `YELLOW` | Yellow flag. | | `CLEAR` | Green. Session running, track clear. | | `CHEQ` | Chequered flag. Race or sprint finished. | | `IDLE` | Nothing to show. No session running, or a session type with no track state worth displaying. | `IDLE` is the normal resting value — F1 runs a few hours a fortnight, so a client will sit in `IDLE` the overwhelming majority of the time. Design for it as the default, not as an error. Resolution happens in priority order: a red flag during a live or suspended session wins over everything; then during a live session the track status maps straight through; then a finished or finalised race or sprint gives `CHEQ`; otherwise `IDLE`. Practice and qualifying never produce `CHEQ`. ## track The raw track status, before resolution. Useful if you want to distinguish conditions that `display` collapses. `null` until the relay has seen one. | Value | Meaning | |-------|---------| | `CLEAR` | Track clear. | | `YELLOW` | Yellow or double yellow. | | `SC` | Safety car. | | `VSC` | Virtual safety car, including the "ending" phase. | | `RED` | Red flag. | ## session The session phase. `null` until the relay has seen one. | Value | Meaning | |-------|---------| | `pre` | Before the session starts. | | `live` | Session running. | | `suspended` | Stopped under a red flag, expected to resume. | | `finished` | Chequered flag shown. | | `finalised` | Results confirmed. | | `ended` | Broadcast wrapped up. | | `break` | Between sessions of the same meeting. | ## Staleness When the relay loses its upstream link, it keeps serving the last-known state with `stale: true` rather than disconnecting you or sending nulls. Treat a stale message as "this was true recently" — how long you keep trusting it is your call. `ts` tells you when the message was sent, not when the state last genuinely changed. The relay sends a message when `stale` flips, so you will be told promptly in both directions. ## Reconnecting Reconnect with backoff on close. Because every message is full state, there is nothing to resync — connect, take the first message, carry on. The relay pings every 30 seconds and drops clients that miss two consecutive pongs. Browser WebSocket clients answer automatically. If you are writing a native or embedded client, make sure your library replies to pings. A close with code `1013` means the relay is at its subscriber limit. Back off further and retry. ## Other endpoints | Endpoint | Purpose | |----------|---------| | `GET /ws` | The subscriber WebSocket. | | `GET /state` | Current state as JSON. Same object as a WebSocket message. | | `GET /healthz` | Liveness: `{ok, f1, uptimeS}`. 200 while the process is up, regardless of upstream. | | `GET /llms.txt` | This document, as Markdown. | `/state` and `/healthz` send `Access-Control-Allow-Origin: *`, so browser apps can call them directly.