# CyclingGrid 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.

Updated 27 Oct 2026. This file is a copy of https://cyclinggrid.com/data.

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.

```sh
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.

## 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 | 4 Oct 2026 |
| `women/results/recent-results.json` | Results (women) | Every night at 20:00 UTC, when a race has finished | 4 Oct 2026 |
| `results/rider-rankings.json` | Rider ranking | Every night at 20:00 UTC, when a race has finished | 4 Oct 2026 |
| `women/results/rider-rankings.json` | Rider ranking (women) | Every night at 20:00 UTC, when a race has finished | 4 Oct 2026 |
| `results/team-standings.json` | Team standings | Every night at 20:00 UTC, when a race has finished | 4 Oct 2026 |
| `women/results/team-standings.json` | Team standings (women) | Every night at 20:00 UTC, when a race has finished | 4 Oct 2026 |
| `results/points-timeline.json` | Points over the season | Every night at 20:00 UTC, when a race has finished | 4 Oct 2026 |
| `women/results/points-timeline.json` | Points over the season (women) | Every night at 20:00 UTC, when a race has finished | 4 Oct 2026 |
| `startlists.json` | Start lists | By hand | 3 Mar 2026 |
| `ftp-levels.json` | FTP levels | By hand, when the source changes | 27 Oct 2026 |
| `events/etape-du-tour-2027.json` | Event cut-offs | By hand, when the organiser publishes new figures | 26 Oct 2026 |
| `events/unbound-200.json` | Event cut-offs | By hand, when the organiser publishes new figures | 24 Oct 2026 |

## 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 file
- `women/races-2026.json`: not dated in the file

```sh
curl https://cyclinggrid.com/data/v1/races-2026.json
curl https://cyclinggrid.com/data/v1/women/races-2026.json
```

| 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 file
- `grand-tours/tour-2026.json`: not dated in the file
- `grand-tours/vuelta-2026.json`: not dated in the file
- `women/grand-tours/giro-2026.json`: not dated in the file
- `women/grand-tours/tour-2026.json`: not dated in the file
- `women/grand-tours/vuelta-2026.json`: not dated in the file

```sh
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
```

| 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 changed 4 Oct 2026
- `women/results/recent-results.json`: last changed 4 Oct 2026

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.

```sh
curl https://cyclinggrid.com/data/v1/results/recent-results.json
curl https://cyclinggrid.com/data/v1/women/results/recent-results.json
```

| 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 changed 4 Oct 2026
- `women/results/rider-rankings.json`: last changed 4 Oct 2026

```sh
curl https://cyclinggrid.com/data/v1/results/rider-rankings.json
curl https://cyclinggrid.com/data/v1/women/results/rider-rankings.json
```

| 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 changed 4 Oct 2026
- `women/results/team-standings.json`: last changed 4 Oct 2026

```sh
curl https://cyclinggrid.com/data/v1/results/team-standings.json
curl https://cyclinggrid.com/data/v1/women/results/team-standings.json
```

| 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 changed 4 Oct 2026
- `women/results/points-timeline.json`: last changed 4 Oct 2026

```sh
curl https://cyclinggrid.com/data/v1/results/points-timeline.json
curl https://cyclinggrid.com/data/v1/women/results/points-timeline.json
```

| 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 3 Mar 2026

```sh
curl https://cyclinggrid.com/data/v1/startlists.json
```

| 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 27 Oct 2026

```sh
curl https://cyclinggrid.com/data/v1/ftp-levels.json
```

| 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 changed 26 Oct 2026
- `events/unbound-200.json`: last changed 24 Oct 2026

```sh
curl https://cyclinggrid.com/data/v1/events/etape-du-tour-2027.json
curl https://cyclinggrid.com/data/v1/events/unbound-200.json
```

| 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 |  |
