# Sécurité et garde-fous

Une personne qui se trompe s'arrête d'elle-même. Un programme qui se trompe recommence.
Les garde-fous ci-dessous existent pour ça.

Tous sont facultatifs : une clé qui ne déclare ni portée ni plafond n'en subit aucun,
ce qui est le comportement des clés créées avant leur mise en place.

## Portées

Une clé peut être restreinte à ce dont son porteur a besoin :

| Portée | Ce qu'elle autorise |
| --- | --- |
| `email.send` | Envoyer des emails, transactionnels et campagnes. |
| `email.read_status` | Lire le statut de livraison des messages. |
| `domain.read` | Lire les domaines et leur authentification. |
| `domain.write` | Déclarer et vérifier un domaine. |
| `contacts.read` | Lire les contacts, les listes, les suppressions. |
| `contacts.write` | Créer et modifier les contacts et les listes. |
| `analytics.read` | Lire les statistiques, la délivrabilité, le journal. |
| `billing.read` | Lire la consommation et les quotas. |

Un appel hors portée reçoit un 403 avec le code `INSUFFICIENT_SCOPE`, la portée qui
manque, et celles que la clé possède. Un agent peut donc dire précisément ce qui lui
manque, au lieu d'échouer sans savoir pourquoi.

Chaque endpoint de la [référence](api.md) indique la portée qu'il exige, et la
spécification OpenAPI la porte en `x-vaemail-scope`.

## Plafonds

Par clé :

- `max_emails_per_day` : plafond d'envois par jour. Au-delà, un 429 avec le code
  `DAILY_LIMIT_REACHED`, marqué `retryable: true` puisque le plafond se remet à zéro.
- `max_recipients_per_email` : nombre de destinataires par message.
- `allowed_domains` : domaines expéditeurs autorisés. Un envoi depuis un autre domaine
  reçoit un 403 `SENDER_DOMAIN_NOT_ALLOWED`.

`GET /api/v1/usage` rend ce qui reste. Un programme qui ne sait pas combien il lui reste
ne peut pas décider de s'arrêter.

## Idempotence

L'en-tête `Idempotency-Key` mémorise la réponse pendant 24 heures et la rejoue à
l'identique, avec `Idempotent-Replay: true`. La même clé réutilisée avec un contenu
différent est refusée par un 409 `IDEMPOTENCY_KEY_REUSED` : c'est une erreur d'appel, pas
un rejeu, et rejouer la première réponse donnerait un résultat qui ne correspond pas à la
demande.

Les erreurs serveur (5xx) ne sont pas mémorisées : elles doivent rester retentables.

## Journal

`GET /api/v1/audit-logs` rend une ligne par appel : quelle clé, quel agent, quelle
opération, quels paramètres, quel résultat, combien d'emails engagés.

Le corps des messages, les pièces jointes et les variables de fusion n'y figurent jamais.
On garde de quoi comprendre l'action, pas de quoi relire le courrier.

## Actions irréversibles

`POST /v3/emailCampaigns/{id}/sendNow` expédie immédiatement à toute une liste. L'appel
exige une confirmation explicite dans le corps de la requête, et la spécification le
marque `x-vaemail-destructive: true`.

Ce garde-fou existe parce que le 5 septembre 2026, un appel sans corps a expédié 2 982
emails un samedi soir.

## Données

Serveurs en Allemagne, envoi via Amazon SES Europe. Export et effacement d'un contact par
API (`/api/v1/contacts/export` et `/api/v1/contacts/erase`). L'effacement est irréversible :
les données personnelles partent, l'adresse reste en liste de suppression pour ne pas être
recontactée.

## Clés d'API

Une clé n'est jamais stockée en clair : seule son empreinte est conservée, et elle n'est
affichée qu'une fois, à la création. Elle peut porter une date d'expiration et être
révoquée à tout moment.

Pour un agent, créer une clé dédiée, bornée, nommée, plutôt que de réutiliser celle d'une
application : le journal devient lisible, et la révoquer n'arrête rien d'autre.
