### **5. Backend et API**

#### **5.1. Surface d'API**

L'API se divise en deux familles aux propriétés opposées, et cette séparation gouverne toute la stratégie de cache du chapitre 7.

**Endpoints publics.** Lus par le SDK, authentifiés par la seule clé publique, mis en cache.

| Méthode et chemin | Rôle |
| --- | --- |
| `GET /v1/p/{publicKey}/manifest.json` | Parcours actifs du projet, avec leurs déclencheurs et leur version courante |
| `GET /v1/p/{publicKey}/tours/{tourId}/{version}.json` | Contenu immuable d'une version publiée |
| `POST /v1/events` | Réception des lots de télémétrie |

**La clé publique est placée dans le chemin, non en paramètre de requête.** Elle participe ainsi naturellement à la clé de cache, sans dépendre de la manière dont chaque intermédiaire normalise, réordonne ou ignore les paramètres de requête. Le cahier fonctionnel proposait la chaîne de requête ; le chemin atteint le même but de façon plus prévisible.

**Endpoints d'administration.** Appelés par le dashboard et l'extension, authentifiés par session ou par jeton, jamais mis en cache.

| Méthode et chemin | Rôle |
| --- | --- |
| `GET /v1/auth/me` | Utilisateur et organisation courants |
| `GET /v1/projects` | Projets de l'organisation |
| `GET /v1/projects/{id}/tours` | Parcours d'un projet |
| `POST /v1/tours` | Création d'un parcours à l'état de brouillon |
| `PATCH /v1/tours/{id}` | Mise à jour du brouillon |
| `POST /v1/tours/{id}/publish` | Publication, création d'une version |
| `POST /v1/tours/{id}/status` | Activation, suspension, archivage |
| `GET /v1/tours/{id}/analytics` | Tunnel de complétion et statistiques |

#### **5.2. Validation et contrats**

Le typage de bout en bout s'appuie sur le client RPC de Hono, consommé directement par le dashboard et l'extension depuis le monorepo. Aucune description d'API n'est publiée : il n'existe aujourd'hui aucun consommateur tiers, et produire une spécification pour personne serait un livrable à maintenir sans lecteur. Le jour où un client demandera un accès programmatique, cette décision sera réexaminée avec son besoin sous les yeux.

Chaque entrée est validée par le schéma partagé de `contracts` avant tout accès aux données. Les erreurs suivent une forme unique :

```json
{ "error": { "code": "invalid_step", "message": "L'option op_tech pointe vers une étape inexistante.", "field": "steps[2].options[0].next" } }
```

**Versionnage.** Le préfixe `/v1` ne change que sur rupture de compatibilité. À l'intérieur, seuls des ajouts de champs facultatifs sont autorisés : un SDK ancien doit toujours pouvoir lire un document récent en ignorant ce qu'il ne connaît pas (section 7.1).

#### **5.3. Publication**

La publication est la seule opération réellement délicate du backend, parce qu'elle fige une version que des milliers de navigateurs mettront ensuite en cache.

1. Le brouillon est validé en entier hors transaction : structure, intégrité des branchements, assainissement du contenu riche. Un brouillon invalide n'atteint jamais la base.
2. Dans une transaction unique, la ligne du parcours est verrouillée, le numéro de version est calculé comme le suivant du plus élevé existant, la version est insérée et le parcours est mis à jour.
3. Aucune purge de cache n'est déclenchée (ADR-03) : le nouveau contenu vit à une URL qui n'existait pas auparavant, et le manifeste expirera de lui-même.

Le verrouillage de la ligne rend impossible l'attribution du même numéro de version à deux publications simultanées, sans qu'il soit nécessaire d'écrire une gestion de concurrence applicative.

#### **5.4. Moteur de ciblage**

La répartition des règles entre serveur et client est une décision structurante, car elle conditionne à la fois la mise en cache et la conformité.

| Règle | Évaluée | Motif |
| --- | --- | --- |
| Projet et statut du parcours | Serveur | Détermine le contenu même du manifeste |
| Motif d'URL | Client | Dépend de la page consultée |
| Audience (rôle, langue, attributs) | Client | Dépend de l'utilisateur final |
| Fréquence d'affichage | Client | S'appuie sur le stockage local (section 3.5) |

**Tout ce qui dépend de l'utilisateur final est évalué chez lui.** Si le serveur filtrait par URL ou par rôle, il faudrait lui transmettre la page consultée et les attributs de chaque visiteur : la réponse deviendrait propre à chaque utilisateur, donc non mutualisable en cache, et Circuy collecterait des données de navigation dont il n'a aucun besoin.

En laissant le client filtrer, le manifeste est **identique pour tous les visiteurs d'un même projet**. Un seul document en cache sert tout le trafic, et aucune donnée de navigation ne quitte le navigateur. La contrepartie assumée est que le manifeste expose la liste des parcours actifs du projet à qui inspecte la page ; il ne contient que des titres et des motifs d'URL, jamais de données d'utilisateur.

#### **5.5. Ingestion des analytics**

L'endpoint reçoit un lot d'événements émis par `sendBeacon`. C'est la seule surface publique en écriture, donc la plus exposée.

**Contrôles appliqués :** existence de la clé publique, appartenance de l'origine de la requête aux origines autorisées du projet, taille du corps plafonnée à 64 kilooctets, nombre d'événements par lot plafonné, conformité de chaque événement au schéma partagé. Un lot partiellement invalide est rejeté en entier plutôt que d'écrire des données douteuses.

**Limitation de débit** par clé publique, avec un compteur en mémoire de l'instance. Aux volumes de la section 0.2 et sur l'instance unique de l'ADR-10, un compteur en mémoire suffit ; un magasin partagé ne deviendra nécessaire que le jour où plusieurs instances coexisteront, et il sera alors justifié par cette nécessité et non par anticipation.

**L'adresse IP n'est jamais enregistrée**, ni dans la table d'événements ni dans les journaux applicatifs. Elle sert uniquement, en mémoire et le temps de la requête, à la limitation de débit.

L'écriture se fait en une insertion groupée, et la réponse est un code 204 sans corps. Le SDK n'attend rien et ne réessaie jamais (section 3.6).

#### **5.6. Agrégation**

Les indicateurs sont calculés à la demande, par requêtes SQL sur les index de la section 2.4.

| Indicateur | Calcul |
| --- | --- |
| Taux de complétion | Événements `complete` rapportés aux `start`, pour une version donnée |
| Abandon par étape | Décroissance du nombre de `step_view` distincts par session, étape après étape |
| Répartition des choix | Comptage des `choice` par option |
| Sélecteurs rompus | Comptage des `target_lost` par étape |

**Aucune table d'agrégat n'est constituée à l'avance.** À 600 000 événements par mois, une agrégation sur index reste très en deçà du seuil de perception. Le seuil de déclenchement est explicite : si une requête d'agrégation dépasse 500 millisecondes en production, une vue matérialisée rafraîchie périodiquement sera introduite, et pas avant.

**Rétention.** Les événements de plus de treize mois sont supprimés par une tâche mensuelle. Cette durée permet la comparaison d'une année sur l'autre tout en bornant la croissance de la table, et constitue l'engagement de conservation annoncé au chapitre 8.
