Data files
The JSON files behind every page: the 2026 race calendars, Grand Tour stages, results, rankings, FTP levels and event cut-offs. Each one with what it holds, its fields and types, and when it changes.
Licence: not decided yet. Ask before reusing: https://pavel-f.com.
When each file last changed
- Race calendarNot dated in the file
- Grand tour stagesNot dated in the file
- Results4 Oct, stopped
- Rider ranking4 Oct, stopped
- Team standings4 Oct, stopped
- Points over the season4 Oct, stopped
- Start lists3 Mar
- FTP levels27 Oct
- Event cut-offs26 Oct
The results files stopped on 4 Oct 2026. The newest race in them ended on 13 Sep 2026 (men) and 29 Aug 2026 (women); 5 men's and 7 women's races that have finished since are not in them.
Fetch a file
Every file is at https://cyclinggrid.com/data/v1/<path>. The same file stays at https://cyclinggrid.com/data/<path>, so older links keep working.
curl https://cyclinggrid.com/data/v1/races-2026.json
Each .json file is sent with Access-Control-Allow-Origin: *, so a page on any site can fetch it, and with Link: <https://cyclinggrid.com/data>; rel="describedby", which points here. Caches keep a file for 5 minutes in a browser and 1 hour at the edge. This page is also at /data/README.md.
Every file
| File | Holds | How often | Last changed |
|---|---|---|---|
races-2026.json | Race calendar | By hand, when the calendar changes | Not dated in the file |
women/races-2026.json | Race calendar (women) | By hand, when the calendar changes | Not dated in the file |
grand-tours/giro-2026.json | Grand tour stages | By hand, when a route changes | Not dated in the file |
grand-tours/tour-2026.json | Grand tour stages | By hand, when a route changes | Not dated in the file |
grand-tours/vuelta-2026.json | Grand tour stages | By hand, when a route changes | Not dated in the file |
women/grand-tours/giro-2026.json | Grand tour stages (women) | By hand, when a route changes | Not dated in the file |
women/grand-tours/tour-2026.json | Grand tour stages (women) | By hand, when a route changes | Not dated in the file |
women/grand-tours/vuelta-2026.json | Grand tour stages (women) | By hand, when a route changes | Not dated in the file |
results/recent-results.json | Results | Every night at 20:00 UTC, when a race has finished | |
women/results/recent-results.json | Results (women) | Every night at 20:00 UTC, when a race has finished | |
results/rider-rankings.json | Rider ranking | Every night at 20:00 UTC, when a race has finished | |
women/results/rider-rankings.json | Rider ranking (women) | Every night at 20:00 UTC, when a race has finished | |
results/team-standings.json | Team standings | Every night at 20:00 UTC, when a race has finished | |
women/results/team-standings.json | Team standings (women) | Every night at 20:00 UTC, when a race has finished | |
results/points-timeline.json | Points over the season | Every night at 20:00 UTC, when a race has finished | |
women/results/points-timeline.json | Points over the season (women) | Every night at 20:00 UTC, when a race has finished | |
startlists.json | Start lists | By hand | |
ftp-levels.json | FTP levels | By hand, when the source changes | |
events/etape-du-tour-2027.json | Event cut-offs | By hand, when the organiser publishes new figures | |
events/unbound-200.json | Event cut-offs | By hand, when the organiser publishes new figures |
What each file holds
Race calendar
Every race of the 2026 season: the men's WorldTour, or the Women's WorldTour in women/. Dates, country, type, stages and distance.
Changes: By hand, when the calendar changes.
races-2026.json: not dated in the filewomen/races-2026.json: not dated in the file
curl https://cyclinggrid.com/data/v1/races-2026.json
curl https://cyclinggrid.com/data/v1/women/races-2026.json
Fields and types
| Field | Type | Notes |
|---|---|---|
season | number | |
races | array of objects | |
races[].id | string | The race page is /race/<id> |
races[].name | string | |
races[].startDate | string (YYYY-MM-DD) | |
races[].endDate | string (YYYY-MM-DD) | |
races[].country | string | |
races[].countryCode | string | |
races[].category | string | One of: classic, grand-tour, monument, one-day, stage-race |
races[].classification | string | One of: 1.UWT, 1.WWT, 2.UWT, 2.WWT |
races[].stages | number | 1 for a one-day race |
races[].distance | number | Kilometres, all stages together |
races[].importance | number | |
tour | string | Not in every file |
Grand tour stages
The stages of one 2026 Grand Tour (Giro, Tour, Vuelta), men's or women's: date, start, finish, distance, type and profile.
Changes: By hand, when a route changes.
grand-tours/giro-2026.json: not dated in the filegrand-tours/tour-2026.json: not dated in the filegrand-tours/vuelta-2026.json: not dated in the filewomen/grand-tours/giro-2026.json: not dated in the filewomen/grand-tours/tour-2026.json: not dated in the filewomen/grand-tours/vuelta-2026.json: not dated in the file
curl https://cyclinggrid.com/data/v1/grand-tours/giro-2026.json
curl https://cyclinggrid.com/data/v1/grand-tours/tour-2026.json
curl https://cyclinggrid.com/data/v1/grand-tours/vuelta-2026.json
curl https://cyclinggrid.com/data/v1/women/grand-tours/giro-2026.json
curl https://cyclinggrid.com/data/v1/women/grand-tours/tour-2026.json
curl https://cyclinggrid.com/data/v1/women/grand-tours/vuelta-2026.json
Fields and types
| Field | Type | Notes |
|---|---|---|
name | string | |
year | number | |
startDate | string (YYYY-MM-DD) | |
endDate | string (YYYY-MM-DD) | |
totalDistance | number | Kilometres |
stages | array of objects | |
stages[].stage | number | Not in every item |
stages[].date | string (YYYY-MM-DD) | |
stages[].start | string | Not in every item |
stages[].end | string | Not in every item |
stages[].distance | number | Kilometres. Not in every item |
stages[].type | string | One of: itt, road. Not in every item |
stages[].profile | string | One of: flat, hilly, mountain. Not in every item |
stages[].rest | boolean | Not in every item |
Results
The podium (first three) of finished 2026 races, and of each stage of a stage race. Not the full classification.
Changes: Every night at 20:00 UTC, when a race has finished. Each file dates itself in lastUpdated.
results/recent-results.json: last changedwomen/results/recent-results.json: last changed
Missing (men): Giro dell'Emilia, Tre Valli Varesine, Milano–Torino, Giro di Lombardia, Tour of Guangxi.
Missing (women): Vuelta España Femenina, Simac Ladies Tour, GP de Plouay–Bretagne Women, Ceratizit Challenge Madrid, Giro dell'Emilia Women, Tour of Guangxi Women, Japan Cup Women.
curl https://cyclinggrid.com/data/v1/results/recent-results.json
curl https://cyclinggrid.com/data/v1/women/results/recent-results.json
Fields and types
| Field | Type | Notes |
|---|---|---|
lastUpdated | string (YYYY-MM-DD) | |
results | array of objects | |
results[].raceId | string | The id in the race calendar |
results[].raceName | string | |
results[].date | string (YYYY-MM-DD) | |
results[].country | string | |
results[].countryCode | string | |
results[].category | string | One of: grand-tour, one-day, stage-race |
results[].classification | string | One of: 1.1, 2.Pro, GT, ProSeries, UWT, WWT |
results[].podium | array of objects | |
results[].podium[].position | number | |
results[].podium[].rider | string | |
results[].podium[].team | string | |
results[].podium[].time | string | A time, or a gap such as +0:05; can be empty |
results[].endDate | string (YYYY-MM-DD) | Not in every item |
results[].stages | array of objects | Not in every item |
results[].stages[].stage | number | |
results[].stages[].podium | array of objects | |
results[].stages[].podium[].position | number | |
results[].stages[].podium[].rider | string | |
results[].stages[].podium[].team | string | |
results[].stages[].podium[].time | string | A time, or a gap such as +0:05; can be empty |
Rider ranking
The top 30 riders by points, added up from the podiums with the UCI scale. Not the official ranking.
Changes: Every night at 20:00 UTC, when a race has finished. Each file dates itself in lastUpdated.
results/rider-rankings.json: last changedwomen/results/rider-rankings.json: last changed
curl https://cyclinggrid.com/data/v1/results/rider-rankings.json
curl https://cyclinggrid.com/data/v1/women/results/rider-rankings.json
Fields and types
| Field | Type | Notes |
|---|---|---|
lastUpdated | string (YYYY-MM-DD) | |
season | number | |
rankings | array of objects | |
rankings[].rank | number | |
rankings[].rider | string | |
rankings[].team | string | |
rankings[].points | number |
Team standings
The teams by points, added up from their riders’ podiums, each with its three highest-scoring riders.
Changes: Every night at 20:00 UTC, when a race has finished. Each file dates itself in lastUpdated.
results/team-standings.json: last changedwomen/results/team-standings.json: last changed
curl https://cyclinggrid.com/data/v1/results/team-standings.json
curl https://cyclinggrid.com/data/v1/women/results/team-standings.json
Fields and types
| Field | Type | Notes |
|---|---|---|
lastUpdated | string (YYYY-MM-DD) | |
season | number | |
teams | array of objects | |
teams[].team | string | |
teams[].points | number | |
teams[].topRiders | array of objects | |
teams[].topRiders[].rider | string | |
teams[].topRiders[].points | number | |
teams[].rank | number |
Points over the season
For the top 5 riders, the points of each race they scored in, and their total after it.
Changes: Every night at 20:00 UTC, when a race has finished. Each file dates itself in lastUpdated.
results/points-timeline.json: last changedwomen/results/points-timeline.json: last changed
curl https://cyclinggrid.com/data/v1/results/points-timeline.json
curl https://cyclinggrid.com/data/v1/women/results/points-timeline.json
Fields and types
| Field | Type | Notes |
|---|---|---|
lastUpdated | string (YYYY-MM-DD) | |
season | number | |
riders | array of objects | |
riders[].rider | string | |
riders[].team | string | |
riders[].timeline | array of objects | |
riders[].timeline[].date | string (YYYY-MM-DD) | |
riders[].timeline[].race | string | |
riders[].timeline[].points | number | |
riders[].timeline[].cumulative | number | Points so far this season, after this race |
Start lists
The star riders on the start lists of some men's races, by race id.
Changes: By hand. Each file dates itself in lastUpdated.
startlists.json: last changed
curl https://cyclinggrid.com/data/v1/startlists.json
Fields and types
| Field | Type | Notes |
|---|---|---|
lastUpdated | string (YYYY-MM-DD) | |
startlists | object | |
startlists.<race id> | object | |
startlists.<race id>.stars | array of objects | |
startlists.<race id>.stars[].rider | string | |
startlists.<race id>.stars[].team | string | |
startlists.<race id>.stars[].uciRank | number |
FTP levels
FTP levels in W/kg for men and women (Garmin FTP ratings, after Allen and Coggan), with their source.
Changes: By hand, when the source changes. Each file dates itself in asOf.
ftp-levels.json: last changed
curl https://cyclinggrid.com/data/v1/ftp-levels.json
Fields and types
| Field | Type | Notes |
|---|---|---|
source | string | |
sourceUrl | string | |
attribution | string | |
asOf | string (YYYY-MM-DD) | |
unit | string | |
note | string | |
levels | array of objects | |
levels[].name | string | |
levels[].men | array of numbers or no value | [from, to] in W/kg; an open end has no value |
levels[].women | array of numbers or no value | [from, to] in W/kg; an open end has no value |
Event cut-offs
One amateur event: its date and distance, and its start time, checkpoints and cut-offs; while the organiser has not published them, time_limits has no value and the file holds the route and its climbs. Every figure carries its source, source name and a licence tag.
Changes: By hand, when the organiser publishes new figures. Each file dates itself in lastUpdated.
events/etape-du-tour-2027.json: last changedevents/unbound-200.json: last changed
curl https://cyclinggrid.com/data/v1/events/etape-du-tour-2027.json
curl https://cyclinggrid.com/data/v1/events/unbound-200.json
Fields and types
| Field | Type | Notes |
|---|---|---|
lastUpdated | string (YYYY-MM-DD) | |
$comment | string | |
slug | object | |
slug.value | string | |
slug.source | string | |
slug.licence | string | |
name | object | |
name.value | string | |
name.source | string | |
name.source_name | string | |
name.licence | string | |
name.asOf | string (YYYY-MM-DD) | Not in every item |
short_name | object | |
short_name.value | string | |
short_name.source | string | |
short_name.source_name | string | |
short_name.licence | string | |
short_name.asOf | string (YYYY-MM-DD) | Not in every item |
year | object | Not in every file |
year.value | number | |
year.source | string | |
year.source_name | string | |
year.licence | string | |
year.asOf | string (YYYY-MM-DD) | |
edition | object | Not in every file |
edition.value | number | |
edition.source | string | |
edition.source_name | string | |
edition.licence | string | |
edition.asOf | string (YYYY-MM-DD) | |
date | object | |
date.value | string (YYYY-MM-DD) | |
date.label | string | |
date.source | string | |
date.source_name | string | |
date.licence | string | |
date.asOf | string (YYYY-MM-DD) | Not in every item |
start | object | Not in every file |
start.value | object | |
start.value.name | string | |
start.value.region | string | |
start.source | string | |
start.source_name | string | |
start.licence | string | |
start.asOf | string (YYYY-MM-DD) | |
finish | object | Not in every file |
finish.value | object | |
finish.value.name | string | |
finish.value.region | string | |
finish.source | string | |
finish.source_name | string | |
finish.licence | string | |
finish.asOf | string (YYYY-MM-DD) | |
distance_km | object | |
distance_km.value | number | Kilometres |
distance_km.source | string | |
distance_km.source_name | string | |
distance_km.licence | string | |
distance_km.asOf | string (YYYY-MM-DD) | Not in every item |
elevation_m | object | |
elevation_m.value | number | |
elevation_m.source | string | |
elevation_m.source_name | string | |
elevation_m.licence | string | |
elevation_m.asOf | string (YYYY-MM-DD) | |
major_climbs | object | Not in every file |
major_climbs.value | number | |
major_climbs.source | string | |
major_climbs.source_name | string | |
major_climbs.licence | string | |
major_climbs.asOf | string (YYYY-MM-DD) | |
climbs | array of objects | Not in every file |
climbs[].name | string | |
climbs[].length_km | number or no value | Kilometres; no value while the organiser has not published it |
climbs[].gradient_pct | number or no value | Average gradient in %; no value while the organiser has not published it |
climbs[].source | string | |
climbs[].source_name | string | |
climbs[].licence | string | One of: public-fact |
climbs[].asOf | string (YYYY-MM-DD) | |
time_limits | no value | Not in every file |
reference_2026 | object | Not in every file |
reference_2026.value | object | |
reference_2026.value.year | number | |
reference_2026.value.finish | object | |
reference_2026.value.finish.name | string | |
reference_2026.value.max_time | string | |
reference_2026.value.segments | array of objects | |
reference_2026.value.segments[].from | object | |
reference_2026.value.segments[].from.name | string | |
reference_2026.value.segments[].from.km | number | |
reference_2026.value.segments[].to | object | |
reference_2026.value.segments[].to.name | string | |
reference_2026.value.segments[].to.km | number | |
reference_2026.value.segments[].km | number | |
reference_2026.value.segments[].clock_from | string | |
reference_2026.value.segments[].clock_to | string | |
reference_2026.source | string | |
reference_2026.source_name | string | |
reference_2026.licence | string | |
reference_2026.asOf | string (YYYY-MM-DD) | |
registration | object | Not in every file |
registration.value | string | |
registration.label | string | |
registration.timezone | string | |
registration.source | string | |
registration.source_name | string | |
registration.licence | string | |
registration.asOf | string (YYYY-MM-DD) | |
distance_km.confirmed | boolean | Not in every item |
start_time | object | Not in every file |
start_time.value | string | |
start_time.label | string | |
start_time.source | string | |
start_time.source_name | string | |
start_time.licence | string | |
start_time.confirmed | boolean | |
surface | object | Not in every file |
surface.value | string | |
surface.source | string | |
surface.source_name | string | |
surface.licence | string | |
checkpoints | array of objects | Not in every file |
checkpoints[].name | string | |
checkpoints[].km | number | Kilometres from the start |
checkpoints[].cutoff_local | string | Local clock time, HH:MM |
checkpoints[].source | string | |
checkpoints[].source_name | string | |
checkpoints[].licence | string | One of: public-fact |
checkpoints[].confirmed | boolean | |
finish_cutoff_local | object | Not in every file |
finish_cutoff_local.value | string | Local clock time, HH:MM; +1 is the next day |
finish_cutoff_local.label | string | |
finish_cutoff_local.source | string | |
finish_cutoff_local.source_name | string | |
finish_cutoff_local.licence | string | |
finish_cutoff_local.confirmed | boolean | |
cutoff_year | object | Not in every file |
cutoff_year.value | number | |
cutoff_year.source | string | |
cutoff_year.source_name | string | |
cutoff_year.licence | string | |
location | object | Not in every file |
location.value | string | |
location.source | string | |
location.source_name | string | |
location.licence | string | |
lottery | object | Not in every file |
lottery.value | array of objects | |
lottery.value[].label | string | |
lottery.value[].date | string (YYYY-MM-DD) | |
lottery.source | string | |
lottery.source_name | string | |
lottery.licence | string |