Rejouer une requête sans double effet
L'idempotence permet de rejouer une requête POST sans craindre le double effet. Un client qui perd la réponse pour cause de timeout peut retenter avec la même clé d'idempotence, et l'API retourne la réponse stockée au lieu de ré-exécuter le traitement. CaptainDNS implémente ce contrat dans la lignée de Stripe, avec une fenêtre de validité de 24 heures.
Pourquoi utiliser l'idempotence
Les réseaux ne sont jamais fiables. Un client peut :
- Émettre une requête qui aboutit côté serveur mais dont la réponse se perd en route.
- Être coupé en plein milieu d'un retry automatique après un crash.
- Voir un timeout côté load balancer alors que l'API a bien traité la demande.
Sans idempotence, vous devez choisir entre :
- Ne pas retenter, au risque de perdre des opérations légitimes.
- Retenter aveuglément, au risque d'exécuter plusieurs fois la même opération.
L'idempotence vous offre une troisième voie : retenter en toute sécurité.
Comment l'utiliser
Ajoutez un header Idempotency-Key à votre requête POST :
POST /public/v1/deliverability/score HTTP/1.1
Host: api.captaindns.com
Authorization: Bearer cdns_live_a3f2XK7mN9QrVtZ4yP1sH6eL8cF2dB5aR3gW7kJxM
Content-Type: application/json
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
{"email":"contact@captaindns.com"}
La clé doit être une chaîne opaque entre 10 et 255 caractères. Les UUIDv4 sont idéaux (entropie suffisante, collisions statistiquement impossibles), mais tout identifiant unique côté client convient.
Ce qui se passe côté serveur
À la première requête :
- CaptainDNS calcule le SHA-256 du body de la requête.
- Après succès, il stocke l'association entre la clé d'idempotence et le hash du body.
- Il capture le status et le body de la réponse avant de la renvoyer au client.
À la seconde requête avec la même clé :
- CaptainDNS trouve un enregistrement existant.
- Si le hash du nouveau body correspond à celui stocké : replay. La réponse stockée est retournée telle quelle, avec le header
X-Idempotent-Replay: true. - Si le hash diffère : conflit. L'API retourne
409 IDEMPOTENCY_CONFLICTavec un message explicite.
Le replay consomme zéro crédit et ne déclenche pas le traitement sous-jacent. Il ne touche ni au bucket de rate limit, ni au quota mensuel.
Fenêtre de validité
Les enregistrements d'idempotence ont une durée de vie de 24 heures. Après ce délai, ils sont purgés et une clé réutilisée déclenche un nouveau traitement normalement.
Cette fenêtre est un compromis : trop courte, vous perdez l'intérêt pour les retries différés ; trop longue, vous stockez un volume de plus en plus grand de réponses anciennes. 24 heures couvre la majorité des patterns de retry (retry immédiat après erreur réseau, retry dans les minutes suivant une reprise de job, retry programmé dans la nuit après un échec détecté en monitoring).
Méthodes concernées
L'idempotence est uniquement applicable aux méthodes mutatives ou coûteuses. En pratique, tous les endpoints publics CaptainDNS sont des POST, donc tous sont éligibles. Les requêtes GET ne passent pas par le mécanisme d'idempotence : elles sont par nature idempotentes côté HTTP.
Le header Idempotency-Key est optionnel. Si vous ne l'envoyez pas, la requête est traitée normalement, sans garantie de replay.
Conflits
Un conflit 409 IDEMPOTENCY_CONFLICT est déclenché quand :
- Vous réutilisez une clé d'idempotence pour une requête dont le body diffère, même d'un caractère.
- Cela peut arriver si votre client régénère la requête entre deux tentatives (timestamps, UUID embarqués, ordre des clés JSON).
Pour éviter les conflits :
- Calculez la clé d'idempotence avant de serialiser le body, pas après.
- Ne mélangez pas des appels différents sous la même clé.
- Si votre framework réordonne les clés JSON, fixez l'ordre à la main.
En cas de conflit légitime (par exemple, vous voulez bel et bien rejouer une opération différente), générez une nouvelle clé.
Exemples d'usage
Retry après timeout réseau
idempotencyKey = uuid()
for attempt in 1..3:
try:
response = post(url, body, headers={"Idempotency-Key": idempotencyKey})
return response
except TimeoutError:
sleep(2 * attempt)
raise
Chaque retry utilise la même clé. Si l'un des appels a déjà réussi côté serveur, les suivants retourneront la réponse stockée.
Deduplication dans une file de jobs
job = fetch_next_job()
idempotencyKey = hash(job.id)
response = post(url, job.payload, headers={"Idempotency-Key": idempotencyKey})
mark_job_done(job.id, response)
Si la file redistribue un job après un crash, la deuxième exécution du même job id réutilise la même clé et récupère la réponse stockée, au lieu de double-facturer le client.
Batch avec dedupe côté serveur
for domain in domains:
idempotencyKey = "batch-" + run_id + "-" + domain
post(url, {"domain": domain}, headers={"Idempotency-Key": idempotencyKey})
Si le batch est relancé après un échec, chaque domaine déjà traité saute directement au replay, et seuls les non traités consomment des crédits.
Limitations
- Stockage limité à 24 h : au-delà, le replay n'est plus possible. Les jobs très différés doivent stocker les réponses côté client.
- Scopée à la clé API : une même clé d'idempotence utilisée par deux clés API différentes ne collisionne pas. C'est voulu : chaque clé a son espace de nom indépendant.
- Pas de reprise inter-serveur : si vous utilisez plusieurs backends distincts, ils partagent la même API mais doivent coordonner les clés d'idempotence entre eux.
- Body identique strict : un whitespace en plus casse le hash et déclenche un conflit. Sérialisez de manière déterministe.
Prochaines étapes
Enchaînez avec les codes d'erreur pour comprendre comment réagir a 409 IDEMPOTENCY_CONFLICT, ou consultez la référence OpenAPI pour voir le header documenté dans chaque endpoint public.