Idempotence des API : empêcher les créations en double grâce à une conception sûre en cas de réessai
L’idempotence d’API aide à éviter les créations en double et les doubles prélèvements en utilisant des IDs de requête, des contraintes d’unicité et des règles sûres de réessai pour vos endpoints.

Pourquoi les créations en double arrivent dans la vraie vie
Les créations en double ne sont généralement pas causées par des « mauvais utilisateurs ». Elles arrivent parce que les réseaux réels et les personnes réelles sont désordonnées.
Un téléphone sur un Wi‑Fi instable envoie une requête, l’application affiche un spinner, et l’utilisateur appuie à nouveau sur le bouton. Ou la requête atteint votre serveur, mais la réponse ne revient jamais, alors le client réessaie.
Les réessais se produisent aussi automatiquement. Les applications mobiles, les bibliothèques fetch du navigateur, les API gateways et les runners de jobs en arrière‑plan réessaient souvent après un timeout, une connexion perdue ou une erreur 502/503. Du point de vue du client, réessayer est la décision sûre : « Je ne suis pas sûr que ça a marché, je réessaie. »
Concrètement, une « création en double » ressemble à ça :
- Deux commandes créées pour un seul checkout
- Deux tickets de support pour une même réclamation
- Deux abonnements ou prélèvements pour un seul clic
- Deux invitations envoyées à la même personne
Le plus inquiétant est que le serveur ne peut souvent pas faire la différence entre une requête toute neuve et la même requête répétée. Si votre endpoint POST insère toujours une nouvelle ligne, chaque réessai devient une seconde ligne. Le client peut ne pas l’afficher, mais votre base de données et vos clients le verront.
Les backends générés par IA sont particulièrement susceptibles de manquer cela parce qu’ils se concentrent souvent sur le chemin « heureux » : un clic, une requête, une réponse. Ils ne modélisent pas toujours les timeouts, les doubles tapotements ou les requêtes en concurrence.
L’objectif est simple : si la même requête de création est répétée, elle doit produire le même résultat, pas une seconde action. C’est ce que protège l’idempotence d’API.
Idempotence d'API en termes simples
L'idempotence d'API signifie : si la même requête est envoyée plusieurs fois, le résultat est le même que si elle avait été envoyée une seule fois.
C’est important parce que beaucoup de systèmes fonctionnent en pratique avec une livraison « au moins une fois ». Une requête peut arriver une fois ou deux fois, mais elle arrivera généralement.
Certaines actions sont naturellement idempotentes. Un GET typique peut être répété sans rien changer. Actualiser une page ne devrait pas créer un nouvel utilisateur.
D’autres actions ne sont pas idempotentes par nature. Un POST qui crée quelque chose (une commande, un utilisateur, un prélèvement) peut s’exécuter deux fois et produire deux enregistrements ou deux prélèvements. Si le premier POST a réussi mais que la réponse a été perdue, le client réessaye et crée accidentellement un doublon.
L’idempotence vous donne un moyen sûr de réessayer. Elle ne prévient pas toutes les erreurs et ne corrigera pas une logique métier cassée, mais elle empêche les doubles actions quand des réessais se produisent.
Où l’idempotence est la plus importante
Vous n’avez pas besoin d’idempotence partout. Vous en avez besoin là où un réessai peut déclencher une seconde action réelle : plus d’argent capturé, plus d’e‑mails envoyés, plus de jobs démarrés, plus de lignes créées.
Priorisez les endpoints qui créent ou déclenchent quelque chose de difficile à annuler. Exemples courants :
- Paiements (charge, authorize, capture)
- Commandes, abonnements, réservations
- Envois d’email/SMS (réinitialisation de mot de passe, invitations, reçus)
- Jobs en arrière‑plan (exports, traitements, génération de rapports)
- Handlers de webhooks qui créent ou accordent quelque chose
Surveillez aussi les endpoints qui ressemblent à des « mises à jour » mais qui se comportent comme des ajouts. Tout ce qui incrémente des compteurs, ajoute des éléments, applique des crédits ou modifie des totaux peut être appliqué deux fois lors de réessais.
Clés d’idempotence (IDs de requête) : le modèle de base
Les clés d’idempotence sont la manière la plus pratique de rendre les requêtes d’écriture sûres pour les réessais.
L’idée est simple : le client envoie un ID de requête unique avec une requête d’écriture (généralement un POST), et le serveur promet que répéter la même clé de requête ne créera pas une seconde ressource ni ne répétera l’action.
Une approche courante est d’accepter un en‑tête comme Idempotency-Key (ou un champ requestId dans le corps). Si le client subit un timeout et réessaie, il réutilise la même clé.
Côté serveur, vous enregistrez un petit enregistrement par clé. La plupart des équipes conservent :
- la clé
- à qui elle appartient (scope)
- à quelle opération elle s’applique (endpoint, méthode)
- le statut (in_progress, succeeded, failed)
- la réponse qui a été renvoyée (code de statut et corps)
Le scope compte
Une clé ne devrait pas être globale à tout votre système. De bons choix par défaut la limitent à une des options suivantes : par utilisateur, par compte/tenant, ou par token API, et généralement par endpoint.
Par exemple, une clé utilisée pour POST /orders ne devrait pas être acceptée pour POST /refunds, même si la chaîne est identique.
La conservation est un compromis
Conservez les clés assez longtemps pour couvrir les réessais réels (minutes à heures), plus une marge pour les clients lents et les files d’attente. Certaines équipes les gardent 24 heures ou quelques jours pour se protéger contre les replays accidentels. Une conservation plus longue réduit les doublons mais augmente le stockage et vous oblige à planifier un nettoyage.
Comportement au replay
Quand la même clé est rejouée, le serveur doit renvoyer le résultat original, pas relancer l’action.
Exemple : un checkout renvoie 201 avec order_id=123. Si le client réessaie avec la même clé, renvoyez le même 201 et le même order_id.
Contraintes d'unicité : le filet de sécurité qui gère les courses
Les clés d’idempotence aident, mais elles ne suffisent pas seules. Votre ultime ligne de défense est la base de données.
Quand deux requêtes frappent votre API presque en même temps, seule la base de données peut empêcher de manière fiable que les deux créent la même chose.
Une contrainte d’unicité dit à la base : « il ne peut y avoir qu’une seule ligne comme celle‑ci. » Même si votre code exécute deux copies de la requête en parallèle, la base rejettera le doublon.
Exemples courants :
- Email unique dans une table
users - Paire unique comme
(account_id, external_id)lors d’imports depuis Stripe, QuickBooks ou un CRM order_numberunique
Un modèle pratique consiste à stocker la clé de requête sur l’enregistrement créé. Par exemple, ajoutez une colonne idempotency_key sur orders et rendez‑la unique, souvent scoppée comme (account_id, idempotency_key). Ainsi, chaque réessai pointe vers la même ligne.
Quand la contrainte est déclenchée, votre API fait généralement l’une des deux choses :
- Renvoyer l’enregistrement existant (idéal pour « create order », « start checkout », « create invoice »)
- Renvoyer une erreur de conflit claire (mieux quand réutiliser une clé pourrait cacher une vraie erreur)
Ne comptez pas sur le schéma « vérifier puis insérer » dans le code applicatif. Deux workers peuvent tous les deux vérifier « existe‑t‑il ? » et voir « non » avant l’insertion. Faites de l’unicité une règle de la base.
Comment ajouter l’idempotence à un endpoint POST (pas à pas)
Si un client subit un timeout et réessaie un POST, vous voulez que le second appel renvoie le même résultat, pas qu’il crée un second enregistrement.
Commencez par deux décisions :
- Quelles actions causent un vrai dommage si répétées (commandes, prélèvements, invitations, jobs)
- À quoi la clé est scoppée (par endpoint + utilisateur/compte est un bon défaut)
Ensuite implémentez‑le de façon à rester correct sous concurrence.
1) Exiger un ID de requête stable
Pour les endpoints risqués, exigez un en‑tête Idempotency-Key (ou un champ d’ID dans la requête). Les clients doivent réutiliser la même valeur pour les réessais.
2) Persister la clé
Créez soit une table d’idempotence (key, endpoint, user/account, status, response), soit stockez la clé directement sur l’enregistrement créé quand il y a un mapping propre 1:1.
3) Réclamer la clé de façon atomique
Insérez d’abord l’enregistrement de clé, protégé par une contrainte d’unicité (ou prenez un verrou). C’est ainsi que vous empêchez deux requêtes concurrentes de « gagner » en même temps.
4) Stocker la réponse renvoyée
Après le succès, enregistrez la réponse (code de statut et corps). Aux répétitions, renvoyez cette réponse stockée sans relancer le travail.
5) Décider quoi faire quand une requête est en cours
Si un réessai arrive pendant que la première tentative tourne encore, vous avez besoin d’un comportement prévisible. Options courantes :
- attendre brièvement, puis revérifier
- renvoyer
409 Conflict(ou202 Accepted) avec un message clair « toujours en cours »
Choisissez une approche et restez‑y.
Gérer les cas compliqués : timeouts, concurrence et échecs partiels
Les réessais deviennent compliqués quand le client et le serveur ne sont pas d’accord sur ce qui s’est passé. L’idempotence rend ces moments ennuyeux : même requête, même résultat.
Timeout après succès
Le serveur a créé l’enregistrement, mais la réponse n’est jamais arrivée. Sans protection, le réessai crée un second enregistrement.
Considérez la clé d’idempotence comme le reçu. Si le réessai utilise la même clé, renvoyez le résultat original.
Crash en milieu d’exécution et réessais concurrents
Stocker une clé ne suffit pas si vous ne suivez pas ce qui s’est passé.
Un jeu de règles pratique :
- Stockez le statut : pending, succeeded, failed.
- Assurez‑vous qu’une seule requête peut posséder la clé (la contrainte d’unicité est la garde la plus simple).
- Si un réessai trouve un statut pending, ne lancez pas une seconde exécution. Attendez brièvement ou renvoyez une réponse claire « en cours ».
- Après succès, renvoyez toujours la même réponse pour la même clé tant qu’elle est conservée.
Échecs partiels
C’est le cas le plus difficile. Vous pouvez créer un utilisateur mais échouer à envoyer l’email de bienvenue, ou capturer un paiement mais échouer à créer la ligne de commande.
Choisissez une « source de vérité » claire pour la requête et faites en sorte que les réessais ne répètent pas des effets secondaires déjà réussis. Souvent cela signifie :
- terminer les étapes restantes de façon asynchrone
- utiliser une étape de compensation (par exemple rembourser un paiement si la commande n’a pas pu être créée)
Erreurs courantes qui laissent encore des doublons
La plupart des créations en double ne viennent pas d’une absence totale d’idempotence. Elles surviennent parce que l’idempotence a été ajoutée à moitié.
Modes d’échec fréquents :
- Rendre la clé d’idempotence optionnelle, donc seules certaines requêtes sont protégées.
- Sauver la clé sans sauvegarder le résultat, si bien qu’un réessai relance le travail.
- Ne pas scopper les clés (une clé réutilisée entre utilisateurs ou endpoints peut entrer en collision).
- Faire « vérifier puis insérer » sans contrainte d’unicité en base.
- Traiter des effets secondaires comme « envoyer un e‑mail » comme idempotents sans tracer s’ils ont déjà été envoyés.
Un scénario classique est POST /orders qui prélève d’abord la carte, puis plante avant de renvoyer une réponse. Si le réessai prélève à nouveau, vous obtenez un double prélèvement. Évitez cela en persistants l’issue et en la protégeant par l’unicité.
Liste de contrôle rapide pour vérifier le comportement sûr en cas de réessai
Une API sûre pour les réessais doit se comporter de la même façon à chaque fois que la même action est répétée.
Pour les endpoints d’écriture (POST, et parfois PATCH/DELETE) :
- Exigez un
Idempotency-Keypour les opérations qui créent ou déclenchent quelque chose. - Appliquez une contrainte d’unicité en base pour la règle de dédoublonnage.
- Vérifiez que les réessais renvoient le même ID de ressource et le même corps de réponse (ou la même représentation stable).
- Définissez le scope de la clé (par utilisateur/compte + par endpoint est une bonne base).
- Conservez les clés assez longtemps pour couvrir les réessais réels, et loggez suffisamment de contexte pour déboguer.
Pour les handlers de webhooks :
- Traitez l’ID d’événement du provider comme clé d’idempotence, stockez‑le et renvoyez un succès sur les réitérations.
Un test simple consiste à envoyer exactement la même requête cinq fois rapidement (avec la même clé). Vous devriez voir une création et quatre réponses « même résultat ».
Exemple : empêcher les doubles prélèvements dans un checkout instable
Un fondateur solo teste un checkout qui a commencé comme prototype généré par IA. Ça marche en démo, mais en réel le réseau est instable et l’UI bloque parfois sur un spinner.
Un client appuie une fois sur « Pay ». Rien ne semble se passer. Il appuie à nouveau. Les deux requêtes atteignent l’API.
Sans idempotence, le backend traite cela comme deux achats séparés. Vous pouvez vous retrouver avec deux paiements réussis, deux lignes de commande, et un ticket support qui commence par : « Je n’ai cliqué qu’une fois. » Le client peut même ouvrir un litige parce qu’il ne sait pas quelle charge est valide.
Avec une clé d’idempotence plus une contrainte d’unicité en base, la seconde requête ne crée rien de nouveau. Le serveur la reconnaît comme un réessai et renvoie le même résultat que le premier appel : l’ID de commande original et le statut de paiement.
Ce que ça donne habituellement :
- Le client génère une clé d’idempotence quand l’utilisateur presse « Pay »
POST /checkoutstocke cette clé avec la tentative de commande- La base applique l’unicité sur
(user_id, idempotency_key)ou(merchant_id, idempotency_key) - Au réessai, l’API récupère l’enregistrement existant et le renvoie
Le support est plus simple si vous loggez quelques champs : clé d’idempotence, ID de commande résultant, ID de charge du fournisseur de paiement, horodatage, et si la requête était un replay.
Prochaines étapes pour les APIs générées par IA qui cassent sous les réessais
Les backends générés par IA semblent souvent corrects en démo, puis cassent quand les utilisateurs rafraîchissent, que les réseaux mobiles tombent, ou que les providers réessaient des webhooks. Stabilisez les endpoints qui peuvent causer un vrai dommage s’ils s’exécutent deux fois.
Choisissez vos trois opérations les plus risquées (souvent paiements, commandes, invitations et webhooks entrants). Ajoutez deux couches :
- Clés d’idempotence pour rendre les réessais sûrs volontairement
- Contraintes d’unicité en base pour attraper les courses
Puis lancez un petit test réaliste : double‑cliquez sur le bouton de soumission, simulez un timeout client et un réessai avec le même ID de requête, et envoyez deux requêtes concurrentes avec le même payload. Vous voulez une seule création réelle et des réponses « même résultat » cohérentes.
Si vous avez hérité d’un codebase généré par IA et que des doublons arrivent déjà, il est souvent plus rapide de faire une remédiation ciblée que de tout réécrire : corrigez un endpoint de bout en bout, puis passez au suivant. Si vous voulez un deuxième avis, FixMyMess (fixmymess.ai) se spécialise dans le diagnostic et la réparation des backends générés par IA, y compris l’ajout d’idempotence, de garde‑fous d’unicité et d’un comportement de réessai prêt pour la production.
Questions Fréquentes
Why am I seeing duplicate records even when users swear they clicked once?
Parce que les réseaux et les applications réessaient. Une requête peut réussir côté serveur mais la réponse peut être perdue, ou l’utilisateur peut taper deux fois alors que l’interface est bloquée. Si votre POST insère toujours une nouvelle ligne, chaque réessai peut devenir une seconde création.
What does “API idempotency” mean in plain terms?
L'idempotence signifie que répéter la même requête produit le même résultat que si elle avait été envoyée une seule fois. Pour les actions de création, cela signifie généralement que la même ressource est renvoyée au réessai plutôt que d’en créer une seconde.
Which endpoints should I make idempotent first?
Appliquez-la aux actions où un réessai peut déclencher un effet réel qu’on ne veut pas répéter : facturer de l’argent, créer des commandes, envoyer des invitations, lancer des jobs longs. Vous n’en avez généralement pas besoin pour les lectures, et parfois pas pour des mises à jour sans conséquence.
What is an idempotency key and how is it used?
Une clé d'idempotence est un ID de requête généré par le client envoyé avec une requête d'écriture, souvent via un en‑tête Idempotency-Key. Le serveur enregistre cette clé et, s’il la voit à nouveau, renvoie le résultat original au lieu de relancer l’action.
How should I scope idempotency keys so they don’t collide?
Scoppez-la par acteur et par opération. Un bon défaut est par compte ou utilisateur + endpoint et méthode, ainsi la même chaîne ne peut pas s’appliquer par erreur à un autre utilisateur ou à une action différente comme un remboursement.
How long should I store idempotency keys?
Gardez-les assez longtemps pour couvrir les réessais réels et les replays retardés, typiquement quelques heures à un jour pour beaucoup de produits. Une rétention plus longue réduit le risque de doublons mais augmente le stockage et nécessite un nettoyage.
What should the server return when it receives the same idempotency key again?
Renvoyez la réponse originale pour cette clé, y compris le même code de statut et le même corps, et ne répétez pas l’effet secondaire. Cela rend les réessais clients sûrs et prévisibles, surtout après des timeouts ou des connexions perdues.
Why do I still need a database unique constraint if I have idempotency keys?
Considérez la contrainte d’unicité de la base comme la dernière barrière contre les courses. Deux requêtes peuvent passer le test “vérifier puis insérer” en même temps, mais la base peut appliquer la règle “une seule ligne comme celle‑ci” et vous permettre de récupérer et renvoyer l’enregistrement existant en cas de conflit.
What happens if a retry arrives while the first request is still running?
Vous devez avoir une règle claire pour les clés “en cours”. Approches courantes : attendre brièvement puis re‑vérifier, ou renvoyer une réponse claire “en cours de traitement”, puis garantir que l’issue finale stockée est ce que les réessais recevront.
How can I quickly test that my API is safe to retry?
Envoyez exactement la même requête plusieurs fois avec la même clé d'idempotence et vérifiez que vous obtenez une seule création et des réponses répétées cohérentes. Si vous avez hérité d’un backend généré par IA qui casse sous les réessais, une remédiation ciblée est souvent la plus rapide ; FixMyMess peut auditer les endpoints à risque et ajouter idempotence plus des contraintes DB de bout en bout.