# GET /races

> Version générique : tous les champs sont listés avec la formule minimale qui y donne accès (`FREE`, `PRO`, `PARTNER`). Envoyez votre clé API (`Authorization: Bearer th_live_…`) ou connectez-vous à votre espace développeur pour obtenir la version personnalisée.

`GET https://dev.trailhorizon.fr/api/v1/races`

Liste des courses publiées, paginée par curseur. Tri sur `id`, pagination par curseur.

## Paramètres

| Paramètre | Emplacement | Type | Requis | Description |
| --- | --- | --- | --- | --- |
| `cursor` | query | string | non | Curseur renvoyé par la page précédente (`nextCursor`). |
| `limit` | query | integer | non | Taille de page, de 1 à la taille maximale de votre formule. Défaut : 50. |
| `country` | query | string | non | Slug du pays (ex. `france`). |
| `region` | query | string | non | Slug de la région (ex. `auvergne-rhone-alpes`). |
| `nextEditionFrom` | query | date ISO 8601 | non | Prochaine édition à partir de cette date (incluse). |
| `nextEditionTo` | query | date ISO 8601 | non | Prochaine édition jusqu'à cette date. Une date seule (`2026-12-31`) inclut toute la journée. |
| `category` | query | string | non | Catégorie de format : `10K`, `20K`, `50K`, `50M`, `100K`, `100M`, `Ultra Endurance`, `Montée sèche`, `Temps fixe`, `Course enfant`, `Marche/Rando`. La course est renvoyée entière. |

Un paramètre inconnu est ignoré. Une valeur invalide renvoie `422 invalid_parameter` (ex. `limit` au-delà de la taille de page maximale, `nextEditionFrom` postérieur à `nextEditionTo`).

## Pagination

La réponse contient `nextCursor` et `hasMore`. Tant que `hasMore` vaut `true`, rappelez l'endpoint avec `cursor=<nextCursor>`. Le curseur est l'`id` de la dernière course de la page.

## Réponse `200`

```json
{
  "data": [
    {
      "id": "clx1a2b3c0000abcd1234efgh",
      "slug": "trail-des-cretes-fictives",
      "name": "Trail des Crêtes Fictives",
      "url": "https://dev.trailhorizon.fr/races/europe/france/auvergne-rhone-alpes/trail-des-cretes-fictives",
      "nextEditionDate": "2027-06-12T00:00:00.000Z",
      "lastEditionDate": "2026-06-13T00:00:00.000Z",
      "firstEditionDate": "2019-06-15T00:00:00.000Z",
      "updatedAt": "2026-10-20T08:30:00.000Z",
      "status": "ACTIVE",
      "verifiedAt": "2026-10-15T10:00:00.000Z",
      "averageRating": 4.5,
      "location": {
        "city": "Exempleville",
        "lat": 45.1234,
        "lon": 6.5678,
        "region": "Auvergne-Rhône-Alpes",
        "country": "France",
        "continent": "Europe"
      },
      "links": {
        "website": "https://exemple.org/trail",
        "registration": "https://exemple.org/trail/inscriptions",
        "facebook": "https://facebook.com/exemple-trail",
        "instagram": "https://instagram.com/exemple_trail",
        "youtube": null
      },
      "images": {
        "logo": "https://files.example.com/trail-des-cretes/logo.png",
        "card": "https://files.example.com/trail-des-cretes/card.jpg",
        "coverDesktop": "https://files.example.com/trail-des-cretes/cover-desktop.jpg",
        "coverPhone": "https://files.example.com/trail-des-cretes/cover-phone.jpg"
      },
      "formats": [
        {
          "id": "clx9z8y7x0001abcd5678ijkl",
          "name": "Le Grand Tour",
          "type": "STANDARD",
          "distance": 52.4,
          "elevation": 3100,
          "category": "50K",
          "price": 85,
          "currency": "EUR",
          "startAt": "2027-06-12T05:00:00.000Z",
          "hasStartTime": true,
          "timezone": "Europe/Paris",
          "duration": null,
          "traceUrl": "https://tracedetrail.fr/trace/0000",
          "registrationStatus": "NOT_YET_OPEN",
          "registrationOpensAt": "2027-01-15T08:00:00.000Z",
          "maxParticipants": 800,
          "practicalInfo": "Matériel obligatoire : Veste imperméable, EMERGENCY_BLANKET\nDépart : Place du village\nArrivée : Place du village\nTemps limite : 14 h 0 min\nRavitaillements : Col des Exemples (km 18)",
          "practicalInfoStructured": {
            "requiredGear": [
              {
                "code": "WATERPROOF_JACKET",
                "label": null,
                "quantity": null
              },
              {
                "code": "EMERGENCY_BLANKET",
                "label": null,
                "quantity": null
              }
            ],
            "recommendedGear": [],
            "start": {
              "location": "Place du village",
              "details": null
            },
            "finish": {
              "location": "Place du village",
              "details": null
            },
            "access": null,
            "timeLimit": {
              "totalMinutes": 840,
              "cutoffs": []
            },
            "aidStations": [
              {
                "name": "Col des Exemples",
                "km": 18,
                "kind": "FULL"
              }
            ],
            "kidsAgeRange": null,
            "relay": null,
            "stageRace": null,
            "notes": null
          },
          "rankingTypes": [
            "ITRA"
          ],
          "tags": [
            "panoramique",
            "technique"
          ]
        },
        {
          "id": "clx9z8y7x0002abcd5678mnop",
          "name": "La Petite Boucle",
          "type": "STANDARD",
          "distance": 21.2,
          "elevation": 1100,
          "category": "20K",
          "price": 38,
          "currency": "EUR",
          "startAt": "2027-06-12T08:30:00.000Z",
          "hasStartTime": true,
          "timezone": "Europe/Paris",
          "duration": null,
          "traceUrl": null,
          "registrationStatus": "NOT_YET_OPEN",
          "registrationOpensAt": "2027-01-15T08:00:00.000Z",
          "maxParticipants": 400,
          "practicalInfo": null,
          "practicalInfoStructured": null,
          "rankingTypes": [],
          "tags": []
        }
      ]
    }
  ],
  "nextCursor": "clx1a2b3c0000abcd1234efgh",
  "hasMore": true
}
```

Les courses `DISCONTINUED` sont renvoyées avec leur `status`.

## Exemple

```bash
curl -H "Authorization: Bearer th_live_VOTRE_CLE" \
  "https://dev.trailhorizon.fr/api/v1/races?country=france&limit=20"
```

## Erreurs

| Statut | Code | Signification |
| --- | --- | --- |
| 401 | `missing_api_key` | Header `Authorization` absent. |
| 401 | `invalid_api_key` | Clé mal formée ou inconnue. |
| 401 | `api_key_revoked` | Clé révoquée. |
| 403 | `account_suspended` / `account_revoked` | Compte suspendu ou révoqué. |
| 403 | `tos_not_accepted` / `tos_outdated` | Conditions d'utilisation de l'API non acceptées, ou à accepter de nouveau. |
| 403 | `field_not_allowed` | Filtre `category` demandé sans accès aux champs des formats. |
| 422 | `invalid_parameter` | Paramètre invalide. |
| 429 | `rate_limit_exceeded` | Trop de requêtes dans la minute. Voir `Retry-After`. |
| 429 | `daily_quota_exceeded` | Quota journalier atteint. Voir `Retry-After`. |
| 500 | `internal_error` | Erreur interne. |

L'exemple ci-dessus décrit une course fictive. Il contient tous les champs ; voir le [catalogue](fields.md) pour les formules.
