# Domaines autorisés

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

Chaque appel `/api/v1` indique le site qui exploite les données avec le header `X-TrailHorizon-Origin`. Ce site doit figurer parmi vos **domaines autorisés**, que vous déclarez dans l'onglet « Clés et domaines » de votre espace développeur.

> **Ce n'est pas une barrière de sécurité.** L'API est serveur à serveur : le header est déclaratif et peut être falsifié. C'est un engagement tracé de votre part, et une fausse déclaration constitue un manquement aux [conditions d'utilisation de l'API](https://trailhorizon.fr/legal/api-terms).

## Déclarer vos domaines

- Limite par compte : `FREE` 1 domaine, `PRO` 5 domaines, `PARTNER` sur mesure (5 par défaut).
- **Un domaine couvre ses sous-domaines** : `monsite.fr` couvre `staging.monsite.fr`, mais pas `evil-monsite.fr`. Seul le domaine principal est enregistré : si vous saisissez `www.monsite.fr`, `https://monsite.fr/contact` ou `blog.monsite.fr`, c'est `monsite.fr` qui est déclaré.
- La racine d'un hébergeur partagé reste refusée (`vercel.app`, `github.io`, `netlify.app`…) : déclarez le domaine de votre propre site. Sur ces hébergeurs, `monapp.vercel.app` est conservé tel quel, avec ses sous-domaines.
- Une adresse IP, un joker, un nom local ou réservé (`localhost`, `.local`, `.test`…) et `trailhorizon.fr` ne peuvent pas être déclarés.
- **Une clé de production est rattachée à au moins un de vos domaines** : sans domaine, elle est refusée. Un domaine nouvellement déclaré est rattaché automatiquement aux clés de production actives qui n'en ont aucun.
- Un produit sans site déclare le domaine de son produit.
- Seuls le propriétaire et les administrateurs du compte déclarent, retirent et rattachent les domaines (20 actions par heure et par compte au maximum).

## Le header `X-TrailHorizon-Origin`

Il est **obligatoire sur chaque appel** `/api/v1`. Sa valeur est le nom d'hôte du site, ou son URL : seul l'hôte est lu, le schéma, le port et le chemin sont ignorés (`monsite.fr`, `https://monsite.fr/` et `monsite.fr:8080` sont équivalents).

- La valeur est en **ASCII**. Pour un nom de domaine internationalisé, envoyez sa forme punycode (`xn--caf-dma.fr` pour `café.fr`).
- Une seule valeur : une liste séparée par des virgules, ou un header répété, est refusée.

```bash
curl -H "Authorization: Bearer th_live_VOTRE_CLE" \
  -H "X-TrailHorizon-Origin: votre-domaine.fr" \
  "https://trailhorizon.fr/api/v1/races"
```

## Clés de test et développement local

Une clé de test (`th_test_` suivie de 32 caractères) sert au développement local et ne demande aucun domaine autorisé.

- **Origines locales uniquement** : `localhost`, `*.localhost`, `127.0.0.1` et `::1`, sur n'importe quel port. Toute autre origine est refusée en `403 origin_not_allowed`.
- Quota de test **séparé** de la production : 200 requêtes par jour et 10 par minute. Les headers `X-RateLimit-*` décrivent ce quota.
- Une clé de test ne compte pas dans le plafond de clés de production.
- `GET /export` n'est pas disponible avec une clé de test (`403 forbidden`).
- La taille de page maximale est identique à celle de la production.
- À l'inverse, une clé de production appelée avec une origine locale est refusée : utilisez une clé de test.

## Revue par Trail Horizon

Trail Horizon revoit les domaines déclarés. Chacun porte un statut, visible dans votre espace :

| Statut | Effet |
| --- | --- |
| À vérifier | Statut à la déclaration. Le domaine fonctionne normalement en attendant la revue. |
| Conforme | Le domaine a été revu. |
| Non conforme | Toute origine couverte par ce domaine est refusée en `403 origin_suspended`. Le propriétaire du compte est prévenu par email, avec le motif. Les webhooks dont l'URL correspond au domaine sont désactivés. Le domaine ne peut être ni retiré ni redéclaré, et ne compte plus dans votre limite. |

## Au-delà de la limite de votre formule

Quand la limite baisse (par exemple après un changement de formule), seuls les domaines déclarés les plus anciens restent actifs. Les autres sont **suspendus, sans être supprimés** : ils répondent `403 origin_suspended` et le propriétaire du compte est prévenu par email. Ils sont réactivés automatiquement si la limite remonte.

## Journalisation

- Pour chaque appel, l'**hôte normalisé** de l'origine (jamais la valeur brute du header) est enregistré dans les logs de votre espace, avec le motif en cas de refus. Il est conservé selon la rétention des logs de votre formule : 7 à 30 jours selon la formule.
- Un refus d'origine ne consomme pas de quota et ne porte pas les headers `X-RateLimit-*`. Il est journalisé et compté dans vos erreurs du jour.

## Erreurs

| Statut | Code | Cas | Message |
| --- | --- | --- | --- |
| 403 | `origin_required` | Header absent. | Missing "X-TrailHorizon-Origin" header. Send the host of the site that uses the data, e.g. "X-TrailHorizon-Origin: example.com". |
| 403 | `origin_not_allowed` | Valeur invalide (pas un hôte ou une URL, caractères non ASCII, plusieurs valeurs). | Invalid "X-TrailHorizon-Origin" header: expected a host name or a URL, in ASCII (use punycode for internationalized names). |
| 403 | `origin_not_allowed` | Origine qui ne correspond à aucun domaine autorisé rattaché à la clé. | This origin is not allowed for this API key. |
| 403 | `origin_not_allowed` | Origine locale avec une clé de production. | Local origins cannot be used with a production key. Use a test key (th_test_...) for local development. |
| 403 | `origin_not_allowed` | Origine non locale avec une clé de test. | Test keys can only be used from a local origin (localhost, 127.0.0.1, ::1). |
| 403 | `origin_suspended` | Domaine suspendu (au-delà de la limite) ou Non conforme. | The domain matching this origin is currently suspended. See the domains of your developer space. |

Les messages ne listent jamais vos domaines autorisés. Les messages sont en anglais.
