# Introduction

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

L'API Trail Horizon donne accès au catalogue des courses de trail publiées sur [Trail Horizon](https://dev.trailhorizon.fr), en lecture seule et au format JSON.

- **Base URL** : `https://dev.trailhorizon.fr/api/v1`
- **Format** : JSON, UTF-8. Les dates sont au format ISO 8601.
- **Version** : `/v1` est stable. Ajouter un champ n'est pas un changement cassant : ignorez les champs inconnus. Un changement cassant (renommage, suppression, changement de type, nouvelle valeur d'une liste fermée) donnera lieu à `/v2`.

## Authentification

Envoyez votre clé API dans le header `Authorization` de chaque requête. Les clés ont la forme `th_live_` suivie de 32 caractères. Elles se créent dans votre espace développeur et ne sont affichées qu'une fois.

```bash
curl -H "Authorization: Bearer th_live_VOTRE_CLE" \
  "https://dev.trailhorizon.fr/api/v1/races"
```

## Endpoints

| Endpoint | Formules | Description | Documentation |
| --- | --- | --- | --- |
| `GET /races` | `FREE`, `PRO`, `PARTNER` | Liste des courses publiées, paginée par curseur. | [Détail](races.md) |
| `GET /races/{id}` | `FREE`, `PRO`, `PARTNER` | Une course publiée. | [Détail](race.md) |
| `GET /races/{id}/editions` | `PARTNER` | Historique des éditions par année et par format. | [Détail](editions.md) |
| `GET /export` | `PRO`, `PARTNER` | Catalogue complet en un seul document JSON. | [Détail](export.md) |

## Réponses et champs

Seuls les champs autorisés pour votre compte sont renvoyés. Un champ non autorisé est **absent** de la réponse (jamais `null`). Un champ `null` signifie que la donnée n'est pas renseignée. Voir le [catalogue des champs](fields.md).

## Quotas

Les quotas sont comptés **par compte** (toutes clés confondues). Valeurs des formules publiques ; la formule `PARTNER` est définie par contrat.

| Limite | Free | Pro |
| --- | --- | --- |
| Licence | Non commerciale | Commerciale |
| Requêtes par jour | 500 | 10 000 |
| Requêtes par minute | 20 | 60 |
| Taille de page maximale | 100 | 100 |
| Export complet | Non disponible | 1 par jour |
| Historique des éditions | Non | Non |
| Webhooks | Non | Non |
| Clés API | 2 maximum | 5 maximum |
| Membres du compte | 1 maximum | 5 maximum |

### En-têtes de limitation

Chaque réponse porte les en-têtes suivants (le plus contraignant de la minute et du jour) :

| En-tête | Description |
| --- | --- |
| `X-RateLimit-Limit` | Limite de la fenêtre. |
| `X-RateLimit-Remaining` | Requêtes restantes dans la fenêtre. |
| `X-RateLimit-Reset` | Instant de remise à zéro (timestamp Unix, en secondes). |
| `Retry-After` | Sur un `429` : délai en secondes avant de réessayer. |

Les requêtes refusées par un quota ne sont pas décomptées. Les erreurs (`4xx`) sont, elles, comptées comme des requêtes.

## Erreurs

Toutes les erreurs ont la même enveloppe :

```json
{
  "error": {
    "code": "invalid_parameter",
    "message": "Invalid \"limit\": must be between 1 and 100."
  }
}
```

| 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 | `forbidden` / `field_not_allowed` | Endpoint ou filtre non autorisé pour votre formule. |
| 404 | `not_found` | Course inconnue, non publiée ou masquée. |
| 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`. |
| 429 | `export_quota_exceeded` | Nombre d'exports journaliers atteint. |
| 500 | `internal_error` | Erreur interne. |

## Suivre les mises à jour

Relisez le catalogue à intervalle régulier (`GET /races` paginé, ou `GET /export`). Le champ `updatedAt` de chaque course (s'il est disponible pour votre compte) permet d'ignorer ce qui n'a pas changé. Les comptes disposant des [webhooks](webhooks.md) sont notifiés à chaque changement.

## Licence et attribution

Votre usage des données est encadré par les [conditions d'utilisation de l'API](https://dev.trailhorizon.fr/legal/api-terms) (version 2026-10-27) : licence non commerciale (`FREE`), commerciale (`PRO`) ou par contrat (`PARTNER`), revente des données interdite.

**Citation obligatoire** : chaque course affichée doit porter la mention « Données : Trail Horizon » avec un lien, **sans `nofollow`**, vers l'URL `url` de la course.

```html
Données : <a href="https://trailhorizon.fr/races/…">Trail Horizon</a>
```

## Pages de la documentation

- [GET /races](races.md)
- [GET /races/{id}](race.md)
- [GET /races/{id}/editions](editions.md)
- [GET /export](export.md)
- [Webhooks](webhooks.md)
- [Catalogue des champs](fields.md)
