# Cycling API complete reference

Cycling results, riders, jersey leaders and the race calendar since 1869, as one REST API.

- Base URL: `https://api.cyclingapi.com`
- Authentication: `Authorization: Bearer <key>` or `X-API-Key: <key>`
- Site: https://cyclingapi.com
- Endpoints: 62
- Generated: 2026-11-08 06:55 UTC
- This file: https://cyclingapi.com/documentation/manual.md

## Before anything else

- Factual data only: results, times, gaps, places, points, jersey leaders, riders and the calendar. No predictions, ratings, tips or betting advice on any endpoint.
- Not in the data, on any plan: rider power, heart rate or cadence, live timing gaps during a race, team radio, or odds.
- Every answer is `{"data": ..., "meta": {...}}`. `meta` carries counts and paging. Times are in seconds, countries are three-letter codes, timestamps are UTC.
- Ids are our own: `rc_` for races, `rd_` for riders.

## Plans

| Plan | Price | Reads | Rate | Allowance |
|---|---|---|---|---|
| Free key | Free | Today and yesterday, your usage and our coverage | 10 / min | 50 a day |
| Live | $29 a month, $290 a year | The current season, with results as they land | 60 / min | 20,000 a month |
| Archive | $79 a month, $790 a year | Every season we hold, up to yesterday | 60 / min | 50,000 a month |
| Complete | $149 a month, $1,490 a year | Every season, live results and everything else | 120 / min | 200,000 a month |

## Coverage

Counted from the data: 10,199,794 result rows, 46,313 races, 151 countries, from 1869 to today, across road, track, mountain bike, cyclocross, BMX and gravel.

## Endpoints

#### `GET /v1/today/races`

Every sanctioned competition running today.

Plans: every plan, the free key included

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| discipline | query | string | no | One of: road, track, mtb, cyclocross, bmx, gravel. |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter

Documentation: https://cyclingapi.com/documentation/races_today

MCP tool: `racing_today`

```json
{
    "data": [
        {
            "id": "rc_7qk3m2xa",
            "name": "Tour de France",
            "discipline": "road",
            "start_date": "2025-07-05",
            "end_date": "2025-07-27",
            "country": "FRA",
            "venue": "Lille"
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/today/results`

Results posted today, as they land.

Plans: every plan, the free key included

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter

Documentation: https://cyclingapi.com/documentation/results_today

MCP tool: `results_today`

```json
{
    "data": [
        {
            "race": {
                "id": "rc_7qk3m2xa",
                "name": "Tour de France"
            },
            "rank": 1,
            "rank_status": "ranked",
            "stage": 17,
            "stage_kind": "stage",
            "classification": "stage",
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "team": "Team name",
            "time_s": 17402,
            "gap_s": 0,
            "points": 50,
            "ranking_points": 210
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/today/jerseys`

Who leads each jersey in the stage races running today.

Plans: every plan, the free key included

Errors: unauthorized, rate_limited, quota_exceeded

Documentation: https://cyclingapi.com/documentation/jerseys_today

```json
{
    "data": {
        "race": {
            "id": "rc_7qk3m2xa",
            "name": "Tour de France"
        },
        "after_stage": 17,
        "jersey": "mountains",
        "rider": {
            "id": "rd_9xk2m4qa",
            "name": "Rider name",
            "nation": "SLO"
        },
        "team": "Team name",
        "value": "142"
    },
    "meta": {}
}
```

#### `GET /v1/yesterday/races`

Every sanctioned competition that ran yesterday.

Plans: every plan, the free key included

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| discipline | query | string | no | One of: road, track, mtb, cyclocross, bmx, gravel. |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter

Documentation: https://cyclingapi.com/documentation/races_yesterday

```json
{
    "data": [
        {
            "id": "rc_7qk3m2xa",
            "name": "Tour de France",
            "discipline": "road",
            "start_date": "2025-07-05",
            "end_date": "2025-07-27",
            "country": "FRA",
            "venue": "Lille"
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/results/yesterday`

Latest results of every race run yesterday.

Plans: every plan, the free key included

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter

Documentation: https://cyclingapi.com/documentation/results_yesterday

MCP tool: `yesterdays_results`

```json
{
    "data": [
        {
            "race": {
                "id": "rc_7qk3m2xa",
                "name": "Tour de France"
            },
            "rank": 1,
            "stage": 17,
            "classification": "stage",
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            }
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/usage`

Your plan, calls used and limits (never counted).

Plans: every plan, the free key included

Errors: unauthorized, rate_limited, quota_exceeded

Documentation: https://cyclingapi.com/documentation/usage

```json
{
    "data": {
        "plan": "live",
        "calls_today": 214,
        "calls_this_month": 3810,
        "limits": {},
        "window": {}
    },
    "meta": {}
}
```

#### `GET /v1/coverage`

How much the archive holds, by discipline.

Plans: every plan, the free key included

Errors: unauthorized, rate_limited, quota_exceeded

Documentation: https://cyclingapi.com/documentation/coverage

MCP tool: `coverage`

```json
{
    "data": {
        "result_rows": 4210000,
        "competitions": 61000,
        "countries": 142,
        "first_season": 1869,
        "disciplines": []
    },
    "meta": {}
}
```

#### `GET /v1/coverage/decades`

Editions and result rows held for each decade.

Plans: every plan, the free key included

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| discipline | query | string | no | One of: road, track, mtb, cyclocross, bmx, gravel. |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter

Documentation: https://cyclingapi.com/documentation/coverage_decades

```json
{
    "data": {
        "discipline": "road",
        "decade": 1990,
        "editions": 31,
        "result_rows": 4210000
    },
    "meta": {}
}
```

#### `GET /v1/coverage/seasons`

Editions and result rows held for each season, by discipline, gender, class and region.

Plans: every plan, the free key included

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| discipline | query | string | no | One of: road, track, mtb, cyclocross, bmx, gravel. |
| year | query | integer | no | One season |
| gender | query | string | no | One of: men, women. |
| category | query | string | no | Class, such as elite, u23 or junior |
| region | query | string | no | Where the races were held, such as Europe or South America |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter

Documentation: https://cyclingapi.com/documentation/coverage_seasons

```json
{
    "data": {
        "discipline": "road",
        "year": 2025,
        "gender": "men",
        "category": "elite",
        "region": "",
        "editions": 31,
        "result_rows": 4210000
    },
    "meta": {}
}
```

#### `GET /v1/coverage/races`

Which seasons of a race we hold, and how deep.

Plans: every plan, the free key included

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| q | query | string | yes | Part of the race name (three letters or more) |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter

Documentation: https://cyclingapi.com/documentation/coverage_races

```json
{
    "data": {
        "id": "rc_7qk3m2xa",
        "name": "Tour de France",
        "first_year": 2016,
        "last_year": 2025,
        "editions": 31,
        "result_rows": 4210000,
        "editions_with_stages": 30,
        "editions_with_jerseys": 28
    },
    "meta": {}
}
```

#### `GET /v1/calendar` (paid plans)

Sanctioned competitions between two dates.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| from | query | date | no | First day (defaults to today) |
| to | query | date | no | Last day (defaults to 30 days after from) |
| discipline | query | string | no | One of: road, track, mtb, cyclocross, bmx, gravel. |
| country | query | string | no | Three-letter country code |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/calendar

MCP tool: `race_calendar`

```json
{
    "data": [
        {
            "id": "rc_7qk3m2xa",
            "name": "Tour de France",
            "discipline": "road",
            "start_date": "2025-07-05",
            "end_date": "2025-07-27",
            "country": "FRA",
            "continent": "Europe",
            "venue": "Lille"
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/calendar/upcoming` (paid plans)

Competitions starting in the next days.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| days | query | integer | no | How many days ahead (default 30, at most 120) |
| discipline | query | string | no | One of: road, track, mtb, cyclocross, bmx, gravel. |
| country | query | string | no | Three-letter country code |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/calendar_upcoming

```json
{
    "data": [
        {
            "id": "rc_7qk3m2xa",
            "name": "Tour de France",
            "discipline": "road",
            "start_date": "2025-07-05",
            "end_date": "2025-07-27",
            "country": "FRA"
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/calendar/countries` (paid plans)

Competitions hosted by each country in a season.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (defaults to this year) |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/calendar_countries

```json
{
    "data": {
        "country": "FRA",
        "competitions": 61000,
        "first_date": "2025-01-21",
        "last_date": "2025-10-12"
    },
    "meta": {}
}
```

#### `GET /v1/races` (paid plans)

Race editions in a season, with dates, place and discipline.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (defaults to this year) |
| discipline | query | string | no | One of: road, track, mtb, cyclocross, bmx, gravel. |
| gender | query | string | no | One of: men, women. |
| category | query | string | no | One of: elite, u23, junior, amateur. |
| q | query | string | no | Part of the race name |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/races

MCP tool: `find_races`

```json
{
    "data": [
        {
            "id": "rc_7qk3m2xa",
            "name": "Tour de France",
            "year": 2025,
            "start_date": "2025-07-05",
            "end_date": "2025-07-27",
            "country": "FRA",
            "discipline": "road",
            "gender": "men",
            "category": "elite",
            "stages": 21
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/races/{id}` (paid plans)

One race edition: dates, stages and the agreed podium.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Race id (rc_...) |
| year | query | integer | no | Season (defaults to the latest held) |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/race

MCP tool: `race_edition`

```json
{
    "data": {
        "id": "rc_7qk3m2xa",
        "name": "Tour de France",
        "year": 2025,
        "stages": 21,
        "podium": [],
        "seasons": 9
    },
    "meta": {}
}
```

#### `GET /v1/races/{id}/history` (paid plans)

Every edition of a race with dates and podium.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/race_history

MCP tool: `race_history`

```json
{
    "data": [
        {
            "year": 2025,
            "start_date": "2025-07-05",
            "end_date": "2025-07-27",
            "stages": 21,
            "podium": []
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/races/{id}/stages` (paid plans)

Each stage: date, start and finish, distance, type and altitude.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| year | query | integer | yes | Season |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/race_stages

```json
{
    "data": {
        "number": 17,
        "date": "2025-07-23",
        "start": "Bourg-d'Oisans",
        "finish": "Alpe d'Huez",
        "distance_km": 172.5,
        "start_place": {},
        "finish_place": {},
        "height_gain_m": 1130
    },
    "meta": {}
}
```

#### `GET /v1/races/{id}/podium` (paid plans)

The agreed top three, with how many records agree.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| year | query | integer | yes | Season |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/race_podium

```json
{
    "data": {
        "place": 1,
        "rider": "",
        "records_agreeing": 3,
        "records_holding": 3
    },
    "meta": {}
}
```

#### `GET /v1/races/{id}/winners` (paid plans)

Every winner of a race, most wins first.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/race_winners

```json
{
    "data": [
        {
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "wins": 88,
            "years": []
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/results` (paid plans)

Results of a race edition, by stage and classification.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| race | query | string | yes | Race id (rc_...) |
| year | query | integer | yes | Season |
| stage | query | string | no | A stage number, prologue or final (all when left out) |
| classification | query | string | no | One of: general, stage, points, mountains, youth, team, combative, other. |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/results

MCP tool: `race_results`

```json
{
    "data": [
        {
            "rank": 1,
            "rank_status": "ranked",
            "stage": 17,
            "stage_kind": "stage",
            "classification": "stage",
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "team": "Team name",
            "time_s": 17402,
            "gap_s": 0,
            "points": 50,
            "ranking_points": 210,
            "stage_type": "mountain"
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/results/stage` (paid plans)

The finish order of one stage.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| race | query | string | yes | Race id (rc_...) |
| year | query | integer | yes | Season |
| stage | query | string | yes | A stage number or prologue |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/results_stage

```json
{
    "data": [
        {
            "rank": 1,
            "rank_status": "ranked",
            "stage": 17,
            "stage_kind": "stage",
            "classification": "stage",
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "team": "Team name",
            "time_s": 17402,
            "gap_s": 0,
            "points": 50,
            "ranking_points": 210
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/results/general` (paid plans)

Overall standings: final, or after a given stage.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| race | query | string | yes | Race id (rc_...) |
| year | query | integer | yes | Season |
| stage | query | integer | no | Standings after this stage (final when left out) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/results_general

MCP tool: `general_classification`

```json
{
    "data": [
        {
            "rank": 1,
            "rank_status": "ranked",
            "stage": 17,
            "stage_kind": "stage",
            "classification": "stage",
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "team": "Team name",
            "time_s": 17402,
            "gap_s": 0,
            "points": 50,
            "ranking_points": 210
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/results/points` (paid plans)

Points standings: final, or after a given stage.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| race | query | string | yes | Race id (rc_...) |
| year | query | integer | yes | Season |
| stage | query | integer | no | Standings after this stage |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/results_points

```json
{
    "data": [
        {
            "rank": 1,
            "rank_status": "ranked",
            "stage": 17,
            "stage_kind": "stage",
            "classification": "stage",
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "team": "Team name",
            "time_s": 17402,
            "gap_s": 0,
            "points": 50,
            "ranking_points": 210
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/results/mountains` (paid plans)

Climbers standings: final, or after a given stage.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| race | query | string | yes | Race id (rc_...) |
| year | query | integer | yes | Season |
| stage | query | integer | no | Standings after this stage |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/results_mountains

```json
{
    "data": [
        {
            "rank": 1,
            "rank_status": "ranked",
            "stage": 17,
            "stage_kind": "stage",
            "classification": "stage",
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "team": "Team name",
            "time_s": 17402,
            "gap_s": 0,
            "points": 50,
            "ranking_points": 210
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/results/youth` (paid plans)

Best young rider standings: final, or after a given stage.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| race | query | string | yes | Race id (rc_...) |
| year | query | integer | yes | Season |
| stage | query | integer | no | Standings after this stage |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/results_youth

```json
{
    "data": [
        {
            "rank": 1,
            "rank_status": "ranked",
            "stage": 17,
            "stage_kind": "stage",
            "classification": "stage",
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "team": "Team name",
            "time_s": 17402,
            "gap_s": 0,
            "points": 50,
            "ranking_points": 210
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/results/teams` (paid plans)

Team standings: final, or after a given stage.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| race | query | string | yes | Race id (rc_...) |
| year | query | integer | yes | Season |
| stage | query | integer | no | Standings after this stage |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/results_teams

```json
{
    "data": [
        {
            "rank": 1,
            "rank_status": "ranked",
            "stage": 17,
            "stage_kind": "stage",
            "classification": "stage",
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "team": "Team name",
            "time_s": 17402,
            "gap_s": 0,
            "points": 50,
            "ranking_points": 210
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/results/non-finishers` (paid plans)

Who left the race, on which stage and how.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| race | query | string | yes | Race id (rc_...) |
| year | query | integer | yes | Season |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/non_finishers

```json
{
    "data": {
        "stage": 17,
        "stage_kind": "stage",
        "status": "dnf",
        "rider": {
            "id": "rd_9xk2m4qa",
            "name": "Rider name",
            "nation": "SLO"
        },
        "team": "Team name"
    },
    "meta": {}
}
```

#### `GET /v1/classifications` (paid plans)

Jersey leaders after each stage of a race edition.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| race | query | string | yes | Race id (rc_...) |
| year | query | integer | yes | Season |
| jersey | query | string | no | One of: leader, points, mountains, youth. |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/classifications

MCP tool: `jersey_leaders`

```json
{
    "data": {
        "after_stage": 17,
        "jersey": "mountains",
        "rider": {
            "id": "rd_9xk2m4qa",
            "name": "Rider name",
            "nation": "SLO"
        },
        "team": "Team name",
        "value": "142"
    },
    "meta": {}
}
```

#### `GET /v1/jerseys/winners` (paid plans)

Who took each jersey home in every stage race of a season.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | yes | Season |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/jersey_winners

```json
{
    "data": {
        "race": {
            "id": "rc_7qk3m2xa",
            "name": "Tour de France"
        },
        "jersey": "mountains",
        "rider": {
            "id": "rd_9xk2m4qa",
            "name": "Rider name",
            "nation": "SLO"
        },
        "team": "Team name",
        "value": "142"
    },
    "meta": {}
}
```

#### `GET /v1/riders` (paid plans)

Find riders by name or nation.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| q | query | string | no | Part of the rider name (two letters or more) |
| nation | query | string | no | Three-letter nation code |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/riders

MCP tool: `find_rider`

```json
{
    "data": [
        {
            "id": "rc_7qk3m2xa",
            "name": "Tour de France",
            "nation": "FRA",
            "sex": "M",
            "born": "1998-09-21",
            "first_year": 2016,
            "last_year": 2025
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/riders/compare` (paid plans)

Two riders in the same results, and who finished ahead.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| a | query | string | yes | First rider id (rd_...) |
| b | query | string | yes | Second rider id (rd_...) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/riders_compare

```json
{
    "data": [
        {
            "race": {
                "id": "rc_7qk3m2xa",
                "name": "Tour de France"
            },
            "year": 2025,
            "stage": 17,
            "stage_kind": "stage",
            "classification": "stage",
            "rank_a": 1,
            "rank_b": 4
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/riders/{id}` (paid plans)

One rider: profile, career span, wins and podiums.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |

Errors: unauthorized, rate_limited, quota_exceeded, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/rider

MCP tool: `rider_profile`

```json
{
    "data": {
        "id": "rc_7qk3m2xa",
        "name": "Tour de France",
        "nation": "FRA",
        "born": "1998-09-21",
        "birth_place": "Town",
        "height_cm": 176,
        "weight_kg": 66,
        "starts": 612,
        "wins": 88,
        "podiums": 151
    },
    "meta": {}
}
```

#### `GET /v1/riders/{id}/results` (paid plans)

Every result of one rider, newest first.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| year | query | integer | no |  |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/rider_results

MCP tool: `rider_results`

```json
{
    "data": [
        {
            "race": {
                "id": "rc_7qk3m2xa",
                "name": "Tour de France"
            },
            "year": 2025,
            "rank": 1,
            "stage": 17,
            "classification": "stage"
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/riders/{id}/wins` (paid plans)

Every stage, classification and race one rider won.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| year | query | integer | no |  |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/rider_wins

MCP tool: `rider_wins`

```json
{
    "data": [
        {
            "race": {
                "id": "rc_7qk3m2xa",
                "name": "Tour de France"
            },
            "year": 2025,
            "stage": 17,
            "classification": "stage"
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/riders/{id}/seasons` (paid plans)

One line per season: race days, wins, podiums, top 10s and teams.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |

Errors: unauthorized, rate_limited, quota_exceeded, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/rider_seasons

```json
{
    "data": {
        "year": 2025,
        "race_days": 64,
        "races": 18,
        "wins": 88,
        "podiums": 151,
        "top_10": 22,
        "teams": []
    },
    "meta": {}
}
```

#### `GET /v1/riders/{id}/teams` (paid plans)

The teams a rider raced for, season by season.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |

Errors: unauthorized, rate_limited, quota_exceeded, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/rider_teams

```json
{
    "data": {
        "year": 2025,
        "team": "Team name",
        "race_days": 64
    },
    "meta": {}
}
```

#### `GET /v1/teams` (paid plans)

Registered teams in a season.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (defaults to this year) |
| discipline | query | string | no | One of: road, track, mtb, cyclocross, bmx, gravel. |
| country | query | string | no | Three-letter country code |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/teams

```json
{
    "data": [
        {
            "id": "rc_7qk3m2xa",
            "name": "Tour de France",
            "code": "ABC",
            "country": "FRA",
            "category": "elite"
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/teams/{id}` (paid plans)

One team: registration, category and its season of racing.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| year | query | integer | no | Season |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/team

```json
{
    "data": {
        "id": "rc_7qk3m2xa",
        "name": "Tour de France",
        "code": "ABC",
        "country": "FRA",
        "category": "elite",
        "discipline": "road",
        "year": 2025,
        "riders": []
    },
    "meta": {}
}
```

#### `GET /v1/stats/season` (paid plans)

A season at a glance: editions, stages, kilometres, winners, speed and abandons.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (defaults to this year) |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/stats_season

MCP tool: `season_stats`

```json
{
    "data": {
        "year": 2025,
        "editions": 31,
        "stages": 21,
        "distance_km": 172.5,
        "different_winners": 112,
        "winning_nations": 31,
        "winning_teams": 44,
        "stage_wins": 6,
        "avg_stage_winner_kmh": 42.31,
        "abandons": 1288
    },
    "meta": {}
}
```

#### `GET /v1/stats/leaders` (paid plans)

Riders ranked by wins, podiums or top 10s in a season.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (defaults to this year) |
| metric | query | string | no | One of: wins, podiums, top_10. |
| discipline | query | string | no | One of: road, track, mtb, cyclocross, bmx, gravel. |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/stats_leaders

MCP tool: `season_leaders`

```json
{
    "data": [
        {
            "position": 1,
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "team": "Team name",
            "wins": 88,
            "podiums": 151,
            "top_10": 22,
            "races": 18
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/stats/teams` (paid plans)

Teams ranked by wins and podiums in a season.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (defaults to this year) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/stats_teams

```json
{
    "data": [
        {
            "team": "Team name",
            "wins": 88,
            "podiums": 151,
            "top_10": 22,
            "riders_placing": 9,
            "races": 18
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/stats/closest-finishes` (paid plans)

Stage races won by the smallest overall margins.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (every season your plan reads when left out) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/stats_closest

```json
{
    "data": [
        {
            "race": {
                "id": "rc_7qk3m2xa",
                "name": "Tour de France"
            },
            "year": 2025,
            "winner": "",
            "second": "Rider name",
            "margin_s": 59
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/stats/fastest-stages` (paid plans)

Stages ranked by the winner's average speed.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (defaults to this year) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/stats_fastest

```json
{
    "data": [
        {
            "race": {
                "id": "rc_7qk3m2xa",
                "name": "Tour de France"
            },
            "stage": 17,
            "date": "2025-07-23",
            "distance_km": 172.5,
            "time_s": 17402,
            "avg_kmh": 41.79,
            "stage_type": "mountain",
            "winner": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            }
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/stats/stage-types` (paid plans)

Flat, hilly, mountain and time trial stages: how many, how long, how fast.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (defaults to this year) |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/stats_stage_types

```json
{
    "data": {
        "stage_type": "mountain",
        "stages": 21,
        "avg_distance_km": 168.4,
        "avg_winner_kmh": 41.2,
        "different_winners": 112,
        "top_nation": "BEL"
    },
    "meta": {}
}
```

#### `GET /v1/stats/winner-ages` (paid plans)

Every win of a season by the winner's age on the day.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (defaults to this year) |
| order | query | string | no | One of: youngest, oldest. |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/stats_youngest

```json
{
    "data": [
        {
            "age": 26.8,
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "race": {
                "id": "rc_7qk3m2xa",
                "name": "Tour de France"
            },
            "stage": 17,
            "win": "stage",
            "date": "2025-07-23"
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/races/{id}/records` (paid plans)

Most wins, top nations, youngest and oldest winner, fastest edition, closest margin.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |

Errors: unauthorized, rate_limited, quota_exceeded, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/race_records

MCP tool: `race_records`

```json
{
    "data": {
        "race": {
            "id": "rc_7qk3m2xa",
            "name": "Tour de France"
        },
        "editions": 31,
        "most_wins": [],
        "top_nations": [],
        "youngest_winner": {},
        "oldest_winner": {},
        "fastest_edition_kmh": {},
        "closest_margin_s": {}
    },
    "meta": {}
}
```

#### `GET /v1/races/{id}/margins` (paid plans)

The gap from winner to second in every edition.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/race_margins

```json
{
    "data": [
        {
            "year": 2025,
            "winner": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "second": "Rider name",
            "margin_s": 59,
            "winner_time_s": 301122
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/races/{id}/speeds` (paid plans)

Distance, winning time and average speed of every edition.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/race_speeds

```json
{
    "data": [
        {
            "year": 2025,
            "distance_km": 172.5,
            "winner_time_s": 301122,
            "avg_kmh": 41.79,
            "winner": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            }
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/races/{id}/winner-ages` (paid plans)

How old every winner of a race was on the day.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/race_winner_ages

```json
{
    "data": [
        {
            "year": 2025,
            "winner": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "age": 26.8
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/races/{id}/attrition` (paid plans)

Starters, finishers and the finish rate of every edition.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/race_attrition

```json
{
    "data": [
        {
            "year": 2025,
            "starters": 184,
            "finishers": 141,
            "non_finishers": 43,
            "finish_rate": 0.766
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/riders/{id}/stats` (paid plans)

Win, podium and top 10 rates, average rank and a split by kind of result.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |

Errors: unauthorized, rate_limited, quota_exceeded, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/rider_stats

MCP tool: `rider_stats`

```json
{
    "data": {
        "rider": {
            "id": "rd_9xk2m4qa",
            "name": "Rider name",
            "nation": "SLO"
        },
        "seasons": 9,
        "races": 18,
        "race_days": 64,
        "wins": 88,
        "podiums": 151,
        "top_10": 22,
        "win_rate": 0.142,
        "podium_rate": 0.281,
        "top_10_rate": 0.553,
        "avg_rank": 14.2,
        "best_rank": 1,
        "wins_per_season": 9.8,
        "by_kind": []
    },
    "meta": {}
}
```

#### `GET /v1/riders/{id}/terrain` (paid plans)

A rider's results split by stage type: flat, hilly, mountain, time trial.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |

Errors: unauthorized, rate_limited, quota_exceeded, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/rider_terrain

```json
{
    "data": {
        "stage_type": "mountain",
        "race_days": 64,
        "wins": 88,
        "podiums": 151,
        "top_10": 22,
        "win_rate": 0.142,
        "avg_rank": 14.2
    },
    "meta": {}
}
```

#### `GET /v1/nations/{id}/history` (paid plans)

One nation's wins, stage wins and podiums season by season.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/nation_history

```json
{
    "data": [
        {
            "year": 2025,
            "wins": 88,
            "stage_wins": 6,
            "podiums": 151,
            "different_winners": 112
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/races/{id}/weather` (paid plans)

The weather at the start and finish of every stage.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| year | query | integer | yes | Season |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/race_weather

MCP tool: `race_weather`

```json
{
    "data": {
        "stage": 17,
        "date": "2025-07-23",
        "start": "Bourg-d'Oisans",
        "finish": "Alpe d'Huez",
        "distance_km": 172.5,
        "start_weather": {
            "date": "2025-07-23",
            "conditions": "light rain",
            "temp_max_c": 21.4,
            "temp_min_c": 12.9,
            "precip_mm": 6.2,
            "rain_mm": 6.2,
            "snow_cm": 0,
            "wind_max_kmh": 24,
            "gust_max_kmh": 47,
            "wind_from": "SW",
            "sunshine_h": 3.1
        },
        "finish_weather": {
            "date": "2025-07-23",
            "conditions": "light rain",
            "temp_max_c": 21.4,
            "temp_min_c": 12.9,
            "precip_mm": 6.2,
            "rain_mm": 6.2,
            "snow_cm": 0,
            "wind_max_kmh": 24,
            "gust_max_kmh": 47,
            "wind_from": "SW",
            "sunshine_h": 3.1
        }
    },
    "meta": {}
}
```

#### `GET /v1/weather/stage` (paid plans)

One stage: the weather at both ends and the top 10 who raced in it.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| race | query | string | yes | Race id (rc_...) |
| year | query | integer | yes | Season |
| stage | query | string | yes | A stage number or prologue |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/weather_stage

```json
{
    "data": {
        "race": {
            "id": "rc_7qk3m2xa",
            "name": "Tour de France"
        },
        "stage": 17,
        "date": "2025-07-23",
        "start": "Bourg-d'Oisans",
        "finish": "Alpe d'Huez",
        "start_weather": {
            "date": "2025-07-23",
            "conditions": "light rain",
            "temp_max_c": 21.4,
            "temp_min_c": 12.9,
            "precip_mm": 6.2,
            "rain_mm": 6.2,
            "snow_cm": 0,
            "wind_max_kmh": 24,
            "gust_max_kmh": 47,
            "wind_from": "SW",
            "sunshine_h": 3.1
        },
        "finish_weather": {
            "date": "2025-07-23",
            "conditions": "light rain",
            "temp_max_c": 21.4,
            "temp_min_c": 12.9,
            "precip_mm": 6.2,
            "rain_mm": 6.2,
            "snow_cm": 0,
            "wind_max_kmh": 24,
            "gust_max_kmh": 47,
            "wind_from": "SW",
            "sunshine_h": 3.1
        },
        "top_10": 22
    },
    "meta": {}
}
```

#### `GET /v1/weather/extremes` (paid plans)

The hottest, coldest, wettest, windiest and snowiest stages, with their winners.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| metric | query | string | no | One of: hottest, coldest, wettest, windiest, snowiest. |
| year | query | integer | no | Season (every season your plan reads when left out) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/weather_extremes

MCP tool: `weather_extremes`

```json
{
    "data": [
        {
            "race": {
                "id": "rc_7qk3m2xa",
                "name": "Tour de France"
            },
            "year": 2025,
            "stage": 17,
            "finish": "Alpe d'Huez",
            "distance_km": 172.5,
            "winner": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "weather": {
                "date": "2025-07-23",
                "conditions": "light rain",
                "temp_max_c": 21.4,
                "temp_min_c": 12.9,
                "precip_mm": 6.2,
                "rain_mm": 6.2,
                "snow_cm": 0,
                "wind_max_kmh": 24,
                "gust_max_kmh": 47,
                "wind_from": "SW",
                "sunshine_h": 3.1
            }
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/races/{id}/weather-history` (paid plans)

Each edition's heat, rain and wind, side by side.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/weather_history

```json
{
    "data": [
        {
            "year": 2025,
            "stages_with_weather": 21,
            "avg_temp_max_c": 24.6,
            "hottest_c": 34.1,
            "coldest_c": 9.8,
            "total_precip_mm": 38.2,
            "wet_stages": 5,
            "max_gust_kmh": 61
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/riders/{id}/weather` (paid plans)

A rider's win rate and average rank in rain, heat, cold and wind.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string | yes | Rider id (rd_...) |

Errors: unauthorized, rate_limited, quota_exceeded, upgrade_required, not_found

Documentation: https://cyclingapi.com/documentation/rider_weather

MCP tool: `rider_weather`

```json
{
    "data": {
        "conditions": "wet",
        "race_days": 64,
        "wins": 88,
        "podiums": 151,
        "top_10": 22,
        "win_rate": 0.142,
        "top_10_rate": 0.553,
        "avg_rank": 14.2
    },
    "meta": {}
}
```

#### `GET /v1/weather/specialists` (paid plans)

Riders with the most stage wins in rain, heat, cold, wind or snow.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| condition | query | string | no | One of: rain, heat, cold, wind, snow. |
| year | query | integer | no | Season (every season your plan reads when left out) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/weather_specialists

```json
{
    "data": [
        {
            "rider": {
                "id": "rd_9xk2m4qa",
                "name": "Rider name",
                "nation": "SLO"
            },
            "stage_wins": 6,
            "first_year": 2016,
            "last_year": 2025
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/places` (paid plans)

Start and finish towns with coordinates and altitude.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| q | query | string | yes | Part of the town name |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/places

```json
{
    "data": {
        "name": "Tour de France",
        "country": "FRA",
        "lat": 45.0917,
        "lon": 6.0703,
        "altitude_m": 1850,
        "stage_starts": 4,
        "stage_finishes": 31
    },
    "meta": {}
}
```

#### `GET /v1/stages/summit-finishes` (paid plans)

Stages that finished high, with the height gained.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (all when left out) |
| min_altitude | query | integer | no | Lowest finish altitude in metres (default 1000) |
| page | query | integer | no | Page number, from 1 |
| per_page | query | integer | no | Rows per page (default 50, at most 200) |
| cursor | query | string | no | The next_cursor from the previous answer |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/summit_finishes

```json
{
    "data": [
        {
            "race": {
                "id": "rc_7qk3m2xa",
                "name": "Tour de France"
            },
            "stage": 17,
            "date": "2025-07-23",
            "start": "Bourg-d'Oisans",
            "finish": "Alpe d'Huez",
            "distance_km": 172.5,
            "finish_altitude_m": 1850,
            "height_gain_m": 1130
        }
    ],
    "meta": {
        "count": 1,
        "next_cursor": null,
        "page": 1,
        "per_page": 50
    }
}
```

#### `GET /v1/nations` (paid plans)

Wins and podiums by nation in a season.

Plans: Live, Archive and Complete

| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
| year | query | integer | no | Season (defaults to this year) |

Errors: unauthorized, rate_limited, quota_exceeded, bad_request, unknown_parameter, upgrade_required

Documentation: https://cyclingapi.com/documentation/nations

```json
{
    "data": {
        "nation": "FRA",
        "wins": 88,
        "podiums": 151
    },
    "meta": {}
}
```

## Guides

### Authentication

Send your key as a Bearer token. https://cyclingapi.com/documentation/authentication

Keys start with `cya_`. Send the key in a header on every call: `Authorization: Bearer cya_...` or `X-API-Key: cya_...`. A key in the address is refused. Keys are shown once when made; keep them on your server. A missing or unknown key answers `401 unauthorized`.

### Plans and windows

Each plan reads a stretch of the archive. https://cyclingapi.com/documentation/plans

| Plan | Reads | Rate | Allowance |
|---|---|---|---|
| Free key | Today and yesterday, your usage and our coverage | 10 / min | 50 a day |
| Live | The current season, with results as they land | 60 / min | 20,000 a month |
| Archive | Every season we hold, up to yesterday | 60 / min | 50,000 a month |
| Complete | Every season, live results and everything else | 120 / min | 200,000 a month |

A call outside your window answers `403 upgrade_required` and names the plan that can read it. `GET /v1/usage` shows your plan and calls used, and is never counted.

### Paging

Lists come a page at a time. https://cyclingapi.com/documentation/paging

List endpoints return up to `per_page` rows (default 50, at most 200) with a `meta` block. Pass `page`, or pass `cursor` with the `next_cursor` from the last answer until it comes back empty. An empty result is a 200 with `meta.count` 0.

### Webhooks

New results pushed to your server the moment they land. https://cyclingapi.com/documentation/webhooks

Paid plans add up to 10 https endpoints in the account. Event `result.published`: `{"id": "ev_...", "type": "result.published", "created": "...", "data": {"race": {"id", "name"}, "year", "stages", "rows", "url"}}`. Signature: `X-Cycling-Signature: t=<unix>,v1=<HMAC-SHA256 of t.body with the endpoint secret>`. Answer 2xx within 10 seconds; 8 tries over about 3 days; handle each event id once.

### Errors

Every error has the same shape. https://cyclingapi.com/documentation/errors

Errors answer `{"error": {"code": "...", "message": "...", "docs": "..."}}`.

| Status | Code | When |
|---|---|---|
| 400 | `bad_request` | A parameter is missing or not valid |
| 401 | `unauthorized` | No key, or a key we do not know |
| 403 | `upgrade_required` | Outside your plan window; names the plan that can read it |
| 404 | `not_found` | No race, rider or edition with that id |
| 422 | `unknown_parameter` | A parameter the endpoint does not take |
| 429 | `rate_limited` | Too many calls this minute; wait for Retry-After seconds |
| 429 | `quota_exceeded` | The daily or monthly allowance is used up |
| 500 | `server_error` | Our fault; it is logged |

### MCP for assistants

The API as tools an assistant can call. https://cyclingapi.com/documentation/mcp

The MCP server is `https://cyclingapi.com/mcp` (JSON-RPC over HTTP). Send your key as `Authorization: Bearer <key>`. Each tool runs the matching endpoint with your key and plan. Details and setup: https://cyclingapi.com/mcp

## About this document

Generated from the endpoint registry, the guides and the plan settings; the date above tells a stale copy. Machine-readable: https://cyclingapi.com/openapi.json, the MCP server at https://cyclingapi.com/mcp, and the agent skill at https://cyclingapi.com/skill.md.
