F1 Race Weekend Schedule
Purpose
Retrieve the complete session schedule for a single Formula 1 Grand Prix from formula1.com — every Free Practice, Qualifying, Sprint Qualifying, Sprint and the Race — each with its date, start time, end time, timezone and GMT offset. This is a read-only lookup. The recommended path is a plain HTTP fetch of the canonical race page, which server-renders the full schedule as structured JSON; no scripted browsing is required.
When to Use
- A user asks "what time is qualifying / the race / FP1 for the {country} Grand Prix?"
- A user wants the full weekend running order and timings for an upcoming or past race.
- You have an F1 news/article URL (e.g.
.../article/formula-1-lenovo-austrian-grand-prix-2026...) and need to resolve it to the actual session timetable. - You need machine-readable session times (with explicit timezone/offset) to build a calendar, countdown, or reminder.
Workflow
The schedule is embedded twice in the server-rendered HTML of every race page. Fetch the page once and parse the embedded JSON — do not drive a browser.
The canonical race page URL is:
https://www.formula1.com/en/racing/{year}/{race-slug}
-
Resolve the race slug. Slugs are country/location based and not always obvious (e.g. Monza →
italy, Imola →emilia-romagna, Abu Dhabi →united-arab-emirates, Las Vegas →las-vegas, Spielberg/Red Bull Ring →austria). To map a race name reliably, fetch the season index and read the/en/racing/{year}/{slug}links (each card carries the round number and country name):browse cloud fetch "https://www.formula1.com/en/racing/2026" \ | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const h=(JSON.parse(s).content||"").replace(/\\"/g,String.fromCharCode(34));console.log([...new Set(h.match(/\/en\/racing\/2026\/[a-z0-9-]+/g))].join("\n"));})'If you were handed an article URL instead of a race name, that article page also links directly to its
/en/racing/{year}/{slug}race page — fetch the article and extract the link. -
Fetch the race page (works bare — HTTP 200, no proxies/verified needed, no anti-bot):
browse cloud fetch "https://www.formula1.com/en/racing/2026/austria" > /tmp/race.json -
Parse the embedded
race.meetingSessionsarray — this is the preferred source because it gives track-local times directly plus an explicittimezoneandgmtOffset(no UTC conversion needed):node -e ' const fs=require("fs"); const html=JSON.parse(fs.readFileSync("/tmp/race.json","utf8")).content .replace(/\\"/g,String.fromCharCode(34)).replace(/\\\\/g,String.fromCharCode(92)); const i=html.indexOf("\"meetingSessions\":["); const start=html.indexOf("[",i); let d=0,end=-1; for(let k=start;k<html.length;k++){const c=html[k];if(c==="[")d++;else if(c==="]"){d--;if(!d){end=k;break;}}} const arr=JSON.parse(html.slice(start,end+1)); for(const x of arr) console.log(x.shortName, x.description, x.startTime, x.gmtOffset, x.timezone, x.state); 'Each item has:
session,shortName(FP1/FP2/FP3/Sprint Q/Sprint/Qualifying/Race),description,startTime,endTime(both track-local, no offset suffix),gmtOffset(e.g.+02:00),timezone(IANA, e.g.Europe/Vienna),state(upcoming/completed), andsessionType. Compose an ISO-8601 instant by appendinggmtOffsettostartTime/endTime.Race-level metadata (also in the HTML) gives the official name and location:
meetingOfficialName,meetingName,meetingLocation,meetingCountryCode,meetingStartDate. -
Return all sessions. A conventional weekend has 5 (FP1, FP2, FP3, Qualifying, Race); a Sprint weekend has 5 with a different mix (FP1, Sprint Q, Sprint, Qualifying, Race — only one practice).
Browser fallback
Only if you cannot run an HTTP fetch. Open https://www.formula1.com/en/racing/{year}/{slug} in a stealth-capable session, then:
- Dismiss the OneTrust cookie consent modal — it renders inside an
iframe, so a top-levelgetElementById('onetrust-accept-btn-handler')will not find it. Either click the button inside the iframe via a frame-aware click, or remove the overlay nodes ([id*=onetrust],[class*=ot-sdk], the consentiframe) before interacting. - Click the "Track time" toggle (top-right of the schedule block). By default the page shows "My time" — the viewer's timezone — so without toggling you'll capture the wrong times. The remote browser's clock is typically UTC, so "My time" shows times 2h off from track-local.
- The schedule list sits in a scroll container ~
y=910px;window.scrollTo(0, 815)brings it into the viewport for a clean screenshot/extraction.
Even in the browser, prefer reading the embedded JSON (browse get text body exposes the JSON-LD; the SSR HTML still contains meetingSessions) over scraping the rendered DOM rows.
Site-Specific Gotchas
- The race page only lists the main F1 sessions. Supporting-series timings (F2, F3, Porsche Supercup) are NOT on the F1.com race page — the page merely links out to
fiaformula2.com/fiaformula3.com. If a user explicitly needs support-race times, state that F1.com does not publish them and point to those external sites. Don't hunt for an F2/F3 session array in the payload — it isn't there. - Two embedded data sources, different timezones.
race.meetingSessionsgives track-localstartTime/endTime+gmtOffset+ IANAtimezone. The schema.org JSON-LDsubEvent[](under@type:"SportsEvent") givesstartDate/endDatein UTC (...Z) and needs converting. PrefermeetingSessionsto avoid conversion bugs. sessionTypeis mislabeled on Sprint weekends. The field is offset by one: "Sprint Q" carriessessionType:"Sprint Shootout"and "Sprint" carriessessionType:"Sprint Qualifying". TrustshortName/description, notsessionType.- "My time" vs "Track time" toggle (browser path only). Rendered times default to the viewer's timezone. Confirmed: with a UTC browser, Practice 1 shows
04:30under "My time" but the true track-local time is13:30(Europe/Vienna). The embedded JSON is always track-local — another reason to prefer fetch. - Slugs are not the GP common name. Use the season index (
/en/racing/{year}) to map a race to its slug; don't guess (Monza→italy, Imola→emilia-romagna, Abu Dhabi→united-arab-emirates, Barcelona→barcelona-catalunya). Pre-season tests appear aspre-season-testing-1/-2. - No anti-bot.
browse cloud fetchreturns HTTP 200 on the homepage, season index, and race pages without--proxiesor--verified. The site is a Next.js/CloudFront app (s-maxage=60edge cache). Stealth flags are unnecessary for this read-only task. statereflects completion. Past sessions showstate:"completed", future onesstate:"upcoming"— useful for "what's next" queries.
Expected Output
Conventional weekend (5 sessions). Times are track-local; gmtOffset appended to form an absolute instant:
{
"success": true,
"race_name": "FORMULA 1 LENOVO AUSTRIAN GRAND PRIX 2026",
"circuit_location": "Spielberg",
"country_code": "AUT",
"timezone": "Europe/Vienna",
"gmt_offset": "+02:00",
"sessions": [
{ "short_name": "FP1", "description": "Practice 1", "type": "Practice", "start": "2026-06-26T13:30:00+02:00", "end": "2026-06-26T14:30:00+02:00", "state": "completed" },
{ "short_name": "FP2", "description": "Practice 2", "type": "Practice", "start": "2026-06-26T17:00:00+02:00", "end": "2026-06-26T18:00:00+02:00", "state": "completed" },
{ "short_name": "FP3", "description": "Practice 3", "type": "Practice", "start": "2026-06-27T12:30:00+02:00", "end": "2026-06-27T13:30:00+02:00", "state": "completed" },
{ "short_name": "Qualifying", "description": "Qualifying", "type": "Qualifying", "start": "2026-06-27T16:00:00+02:00", "end": "2026-06-27T17:00:00+02:00", "state": "completed" },
{ "short_name": "Race", "description": "Race", "type": "Race", "start": "2026-06-28T15:00:00+02:00", "end": null, "state": "completed" }
]
}
Sprint weekend (e.g. China, Miami) — same shape, different session set (single practice, plus Sprint Qualifying + Sprint):
{
"success": true,
"race_name": "FORMULA 1 ... CHINESE GRAND PRIX 2026",
"circuit_location": "Shanghai",
"timezone": "Asia/Shanghai",
"gmt_offset": "+08:00",
"sessions": [
{ "short_name": "FP1", "description": "Practice 1", "type": "Practice", "start": "2026-03-13T11:30:00+08:00", "end": "2026-03-13T12:30:00+08:00" },
{ "short_name": "Sprint Q", "description": "Sprint Qualifying", "type": "Sprint", "start": "2026-03-13T15:30:00+08:00", "end": "2026-03-13T16:14:00+08:00" },
{ "short_name": "Sprint", "description": "Sprint", "type": "Sprint", "start": "2026-03-14T11:00:00+08:00", "end": "2026-03-14T12:00:00+08:00" },
{ "short_name": "Qualifying", "description": "Qualifying", "type": "Qualifying", "start": "2026-03-14T15:00:00+08:00", "end": "2026-03-14T16:00:00+08:00" },
{ "short_name": "Race", "description": "Race", "type": "Race", "start": "2026-03-15T15:00:00+08:00", "end": null }
]
}
Failure (slug not found / no schedule published yet):
{
"success": false,
"race_name": null,
"sessions": [],
"error_reasoning": "No /en/racing/{year}/{slug} match for the requested race, or the page carries no race.meetingSessions array (schedule not yet published)."
}