Le tourniquet ne ferme pas la station
Huit heures douze, le hall de la station est plein. La régie ne condamne pas l'entrée pour autant. Elle laisse passer les voyageurs un par un, au rythme que le quai peut absorber. Le tourniquet n'interdit rien, il cadence.
Deux rues plus loin, à la mairie, un autre dispositif fait un travail moins visible. On tire le ticket numéro 47, on attend, on est servi une fois. Si la file bouge mal et qu'on repasse la porte pour tirer un nouveau ticket, on recommence tout. Si on présente le même 47, l'agent retrouve le dossier et rend exactement le même résultat.
Ces deux objets répondent au même problème par deux bouts. Le premier protège le service de l'afflux. Le second protège le dossier des passages en double que l'afflux provoque.
Côté informatique, ce sont la limitation de débit et l'idempotence. Deux sujets que tout le monde repousse au prochain trimestre, et qui décident du comportement du système le jour où il est sous stress.
Limiter le débit protège le service de ses clients. L'idempotence protège les données des rejeux que la limitation, les délais d'attente et les reprises vont provoquer. Livrer l'un sans l'autre revient à poser des tourniquets sans jamais numéroter les tickets.
Les deux moitiés d'un même mécanisme
Le lien entre les deux se lit dans la chaîne d'événements, toujours la même. Le service refuse une requête parce que le quota est atteint. Le client, poliment, réessaie. Sa deuxième requête est en tout point identique à la première, et personne ne sait avec certitude si la première a été traitée avant d'être coupée.
Le même enchaînement se produit sans limitation de débit, dès qu'un délai d'attente expire. Le client abandonne au bout de cinq secondes, le serveur a mis six secondes, et l'écriture a bien eu lieu. La reprise en fabrique une seconde.
C'est aussi ce qui arrive derrière une file de messages configurée « au moins une fois », le mode par défaut de la plupart des systèmes de messagerie, détaillé dans Files de messages et Kafka. Le message sera relivré, c'est une garantie, pas un incident.
Trois causes différentes, un seul symptôme : la même opération arrive deux fois. La limitation décide de qui entre, l'idempotence décide de ce qui se passe quand quelqu'un entre deux fois avec le même ticket.
limiteur de débit"] G -->|"429 + Retry-After"| C G --> S["Service"] S --> K["Magasin de clés
d'idempotence"] K --> S S --> D[("Base de données")] classDef entree fill:#ede9e1,stroke:#8a4d16,color:#1f2428; classDef filtre fill:#b5651d,stroke:#8a4d16,color:#f5f2ec; classDef garde fill:#d89253,stroke:#8a4d16,color:#2c3338; class C entree; class G filtre; class S,K,D garde;
Un limiteur de débit sans idempotence transforme un pic de charge en pic de doublons. Il ne supprime pas le travail, il le décale et le multiplie.
Pourquoi limiter
Trois raisons, dans l'ordre où elles apparaissent en production.
Protéger la ressource. Un client mal codé qui boucle sur un appel n'a pas d'intention hostile, il a une boucle. Sans plafond, il consomme la capacité prévue pour les mille autres. Le limiteur transforme une panne globale en gêne locale.
Contenir le coût. Chaque requête coûte du calcul, de la bande passante, parfois un appel facturé chez un tiers. Un quota est la seule façon de rendre la facture prévisible quand le trafic ne l'est pas.
Garantir l'équité. Sur une plateforme mutualisée, la capacité est un bien commun. Sans répartition explicite, elle va à celui qui tape le plus fort, pas à celui qui en a le plus besoin.
Une quatrième raison, moins avouée, est défensive. Le bourrage d'identifiants et l'aspiration de données publiques ressemblent, vus du serveur, à un client très motivé. Un plafond par compte et par adresse en absorbe une bonne partie sans écrire une seule règle de détection.
Il faut distinguer deux dispositifs qu'on confond souvent. La limitation de débit est un contrat : elle s'applique par client, elle est annoncée à l'avance, elle refuse toujours au même seuil. Le délestage regarde l'état du système : quand la flotte sature, il commence à refuser le trafic le moins important, quel que soit le client. C'est la régie qui ferme les grilles de la station, pas le tourniquet qui cadence. Le premier est prévisible, le second est une soupape.
Un quota se documente et se négocie. Un délestage se déclenche. Publier le premier comme une promesse et le second comme un engagement de service est le meilleur moyen de perdre les deux.
Les cinq algorithmes
Cinq façons de régler le tourniquet. Toutes répondent à la même question, « cette requête passe-t-elle », et se distinguent par ce qu'elles gardent en mémoire pour y répondre. On prend une limite de 100 requêtes par minute et par client comme fil de comparaison.
Le compteur à fenêtre fixe
On découpe le temps en tranches d'une minute. Un compteur par client et par tranche, remis à zéro à chaque changement de minute. C'est deux lignes de code et un entier en mémoire.
Son défaut est à la frontière. Rien n'empêche un client de vider son quota à la fin d'une tranche et de le vider à nouveau au début de la suivante.
10:00:59 100 requêtes → fenêtre 10:00 remplie, tout passe
10:01:00 100 requêtes → fenêtre 10:01 vierge, tout passe
200 requêtes en 2 secondes
soit 2 x la limite sur une minute glissante
Sur des quotas généreux, cet effet de bord est tolérable. Sur un endpoint coûteux, il double la charge de pointe qu'on croyait avoir bornée.
Le journal à fenêtre glissante
On garde la liste des horodatages des requêtes du client. À chaque appel, on jette ceux qui datent de plus d'une minute et on compte ce qui reste. C'est exact, sans effet de bord, et c'est la plus coûteuse des cinq méthodes.
100 horodatages max par client
x 8 octets par horodatage = 800 octets par client
x 1 000 000 de clients actifs = 800 000 000 octets ≈ 800 Mo
Comparaison, compteur à fenêtre fixe :
8 octets x 1 000 000 = 8 000 000 octets ≈ 8 Mo
Un facteur cent, avant même de compter le surcoût de la structure de données qui héberge ces listes. Le journal se réserve aux quotas très bas, du type dix tentatives d'authentification par heure.
Le compteur à fenêtre glissante
Le compromis. On conserve deux compteurs par client, la minute courante et la précédente, et on pondère la précédente par la part de fenêtre encore couverte.
Limite : 100 / minute. Instant : 10:01:15
Minute précédente (10:00) : 84 requêtes
Minute courante (10:01) : 36 requêtes
Recouvrement : 45 s sur 60, soit 75 %
Estimation = 84 x 0,75 + 36 = 63 + 36 = 99
99 < 100 → la requête passe
La suivante porterait l'estimation à 100 : refusée
Deux entiers par client, l'effet de bord lissé, et une approximation qui suppose le trafic uniformément réparti dans la minute précédente. L'hypothèse est fausse en toute rigueur, et suffisamment juste en pratique.
Le seau à jetons
Chaque client possède un seau d'une capacité fixe, réalimenté à débit constant. Une requête consomme un jeton, un seau vide refuse.
Capacité : 10 jetons. Remplissage : 5 jetons / seconde
Seau vide → plein en 10 / 5 = 2 secondes
Client silencieux depuis 2 s : 10 requêtes d'un coup autorisées
Puis régime permanent à 5 / s
Sur 10 secondes : 10 + 5 x 10 = 60 requêtes, soit 6 / s en moyenne
Deux réglages indépendants, la rafale et le régime permanent, et c'est exactement ce qu'on veut pour une API publique. Un client qui envoie une salve toutes les dix secondes est un client normal, pas un abus.
Le seau percé
Même image, sortie différente. Les requêtes entrent dans une file de taille fixe et sont traitées à débit constant. Ce qui déborde est refusé, ce qui entre est servi à cadence régulière.
Débit de sortie : 5 requêtes / seconde. File : 20 places
Rafale de 40 requêtes :
20 entrent dans la file, 20 refusées immédiatement
la file se vide en 20 / 5 = 4 secondes
la dernière requête admise a attendu 4 secondes
Le débit vers l'aval est parfaitement lisse, et c'est la propriété recherchée devant un système fragile. Le prix est la latence introduite par l'attente en file, qui reste invisible dans les métriques du limiteur et bien visible chez l'utilisateur.
| Algorithme | Mémoire par client | Rafales | Précision | Cas d'usage |
|---|---|---|---|---|
| Fenêtre fixe | 1 compteur | Doublées à la frontière | Faible | Quotas larges, protection grossière |
| Journal glissant | 1 horodatage par requête | Aucune au-delà du seuil | Exacte | Quotas très bas, authentification |
| Compteur glissant | 2 compteurs | Lissées | Approchée | Défaut raisonnable pour une API |
| Seau à jetons | 1 compteur + 1 horodatage | Autorisées jusqu'à la capacité | Bonne | API publique, clients légitimes irréguliers |
| Seau percé | 1 file | Absorbées puis étalées | Bonne | Protection d'un aval à débit fixe |
Le seau à jetons est le choix par défaut d'une API publique, parce qu'il sépare deux décisions que les autres algorithmes mélangent : combien on tolère d'un coup, et combien on tolère à la longue.
Où poser le compteur
Reste à décider où planter le tourniquet. Trois emplacements possibles, et ils ne se remplacent pas.
Côté client, la limitation est une politesse. Une bibliothèque bien faite s'auto-limite, respecte les en-têtes de quota et espace ses reprises. Cela évite du trafic inutile et ne protège rien : le client suivant n'aura pas la même bibliothèque.
À la passerelle, c'est le point de passage naturel. Un seul endroit qui connaît l'identité de l'appelant, refuse avant que la requête ne consomme du calcul applicatif, et applique la même règle à tous les services derrière. C'est là que va la limitation générique, par compte et par adresse.
Dans le service, on pose les limites que la passerelle ne peut pas connaître : trois exports par jour, cinq virements par heure, une réinitialisation de mot de passe par quart d'heure. Ce sont des règles métier déguisées en quotas, et elles n'ont rien à faire dans une configuration d'infrastructure.
Reste le problème du compteur partagé. Six instances derrière un répartiteur, chacune avec son compteur local, et l'arithmétique se retourne contre vous.
Limite globale : 600 requêtes / minute
6 instances → quota local = 600 / 6 = 100 par instance
Un client émet 300 requêtes, dont 40 % atterrissent sur la même instance :
0,40 x 300 = 120 requêtes pour un quota local de 100
→ 20 requêtes refusées alors que le compteur global est à 300 sur 600
D'où le compteur centralisé, typiquement dans le magasin clé-valeur en mémoire déjà présent pour le cache, décrit dans Cache distribué et CDN. Deux précautions : l'incrément doit être atomique, sinon deux instances lisent la même valeur et la réécrivent, et le limiteur ne doit pas devenir un point de défaillance unique. Quand le magasin ne répond pas, il faut avoir décidé à l'avance si on laisse passer ou si on refuse.
| Emplacement | Ce qu'il protège | Ce qu'il ignore | Coût |
|---|---|---|---|
| Client | La bande passante et le quota du client lui-même | Tout client qui ne coopère pas | Nul, et sans garantie |
| Passerelle | Le calcul applicatif et l'équité entre comptes | Le sens métier de l'appel | Un état partagé à maintenir |
| Service | Les règles métier et les ressources coûteuses | Les clients qui n'atteignent jamais le service | Du code métier en plus |
Un compteur partagé ajoute un aller-retour réseau sur le chemin critique de chaque requête. C'est acceptable, à condition d'avoir écrit noir sur blanc ce qui se passe quand cet aller-retour échoue.
Ce qu'on renvoie au client
Le refus est une réponse, pas une absence de réponse. Le code adapté est 429 Too Many Requests, défini par la RFC 6585. Il dit une chose précise : la demande était valide, l'appelant a dépassé son quota, il peut revenir plus tard. Ni 403, qui parle de droits, ni 503, qui parle de l'état du serveur.
L'en-tête Retry-After, spécifié dans la RFC 9110, accompagne ce refus avec un nombre de secondes ou une date. C'est la seule information dont le client a réellement besoin pour se comporter correctement.
HTTP/1.1 429 Too Many Requests
Retry-After: 3
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1735689600
Content-Type: application/json
{"error":"rate_limit_exceeded","message":"Quota de 100 requêtes par minute dépassé"}
Les en-têtes préfixés X-RateLimit- sont une convention de fait, pas un standard : GitHub les documente sur son API REST sous cette forme, d'autres plateformes utilisent d'autres noms. Un travail de normalisation existe à l'IETF, dans le groupe httpapi, sous le titre RateLimit header fields for HTTP. En attendant, le service doit renvoyer ses quotas sur toutes les réponses, pas seulement sur les refus, pour qu'un client puisse ralentir avant de heurter le mur.
Côté client, la reprise se fait avec attente exponentielle et bruit aléatoire. Le doublement laisse au service surchargé le temps de se remettre.
Attente exponentielle, base 200 ms :
200 + 400 + 800 + 1 600 + 3 200 = 6 200 ms pour 5 tentatives
Le bruit gère un cas que le doublement seul aggrave. Si le service refuse dix mille clients dans la même milliseconde et que tous attendent exactement une seconde, il reçoit dix mille requêtes dans la même milliseconde, une seconde plus tard.
Sans bruit : 10 000 reprises dans la même milliseconde
Bruit uniforme sur [0 ; 1 000 ms] :
10 000 / 1 000 = 10 reprises par milliseconde en moyenne
Le bruit aléatoire n'est pas un raffinement. Sans lui, la reprise coordonnée reproduit à l'identique le pic qui a provoqué le refus, et le système entre en oscillation.
L'idempotence, verbe par verbe
Une opération est idempotente quand l'appliquer plusieurs fois produit le même effet que l'appliquer une fois. La définition porte sur l'effet côté serveur, pas sur la réponse renvoyée.
La nuance a son importance. Un premier DELETE supprime la ressource et renvoie 204, un second renvoie 404. Les deux réponses diffèrent, l'état final est identique, la méthode est bien idempotente.
La RFC 9110 classe les méthodes en deux propriétés distinctes. Une méthode sûre ne modifie rien. Une méthode idempotente peut modifier, mais sa répétition ne change plus rien.
| Méthode | Sûre | Idempotente | Remarque |
|---|---|---|---|
GET, HEAD, OPTIONS, TRACE |
Oui | Oui | Aucune modification d'état |
PUT |
Non | Oui | Remplace la ressource par la représentation fournie |
DELETE |
Non | Oui | Le second appel ne trouve plus rien à supprimer |
POST |
Non | Non | Crée une ressource nouvelle à chaque appel |
PATCH |
Non | Cela dépend | Idempotent si le patch décrit un état, pas un incrément |
Le cas qui pose problème est toujours le même : le POST. C'est le verbe de la création, de la commande, du virement, du message envoyé. Et c'est précisément là que le doublon coûte de l'argent ou de la réputation.
Un PATCH mérite un regard attentif. « Mettre le solde à 40 » se rejoue sans dommage, « ajouter 40 au solde » ne se rejoue pas du tout. Le même verbe HTTP, deux comportements opposés, selon ce que le corps de la requête exprime.
L'idempotence ne se déduit pas du verbe employé, elle se déduit de ce que fait le code derrière. Un PUT qui envoie un courriel de confirmation à chaque appel n'est pas idempotent, quoi qu'en dise le tableau.La clé d'idempotence
Pour rendre un POST rejouable, il faut lui donner un numéro de ticket. Le client génère un identifiant unique par intention métier, l'envoie dans un en-tête, et le réutilise à l'identique pour toutes ses reprises de cette intention.
POST /v1/paiements HTTP/1.1
Host: api.exemple.com
Idempotency-Key: 6f1a9c3e-2b47-4d10-9d5e-1c8b0f5a7e33
Content-Type: application/json
{"montant":4000,"devise":"eur","compte":"acct_812"}
Le point capital est que la clé soit produite par le client, avant le premier envoi, et conservée par lui jusqu'à obtention d'une réponse définitive. Une clé régénérée à chaque tentative ne sert à rien : c'est un nouveau ticket à chaque passage.
Côté serveur, la clé est le pivot d'une petite machine à états. Trois cas seulement, et le troisième est celui qu'on oublie.
état en cours"] C --> D["Exécution du traitement"] D --> E["Écriture du résultat
état terminé"] E --> F["Réponse 201"] B -->|"Oui, état en cours"| G["409 requête déjà en cours"] B -->|"Oui, état terminé"| H["Rejeu du résultat stocké"] classDef depart fill:#ede9e1,stroke:#8a4d16,color:#1f2428; classDef travail fill:#b5651d,stroke:#8a4d16,color:#f5f2ec; classDef sortie fill:#d89253,stroke:#8a4d16,color:#2c3338; class A,B depart; class C,D,E travail; class F,G,H sortie;
L'insertion doit être atomique et servir de verrou. Un INSERT sur une clé primaire fait très bien l'affaire : si deux reprises concurrentes arrivent en même temps, l'une insère, l'autre reçoit une violation de contrainte et sait qu'elle doit attendre ou renvoyer un conflit.
CREATE TABLE idempotency_key (
key VARCHAR(255) PRIMARY KEY,
request_hash CHAR(64) NOT NULL,
state VARCHAR(16) NOT NULL,
response_code SMALLINT,
response_body JSONB,
locked_until TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX idx_idempotency_key_created_at ON idempotency_key (created_at);
L'empreinte de la requête sert à détecter le mauvais usage : même clé, corps différent. Ce n'est pas une reprise, c'est un bogue côté client, et il vaut mieux le refuser bruyamment que traiter silencieusement l'une des deux intentions.
Voici le déroulé complet du cas qui justifie tout ce mécanisme, la réponse perdue en chemin.
Reste la durée de conservation, qui est un problème de volumétrie avant d'être un problème de correction. Le calcul se pose comme les autres.
500 écritures idempotentes / seconde
x 86 400 s = 43 200 000 clés par jour
x 1 Ko par entrée (clé, empreinte, code, corps)
= 43 200 000 Ko
= 43 200 Mo
= 43,2 Go conservés en permanence
Rétention à 7 jours : 43,2 x 7 = 302,4 Go
Vingt-quatre heures couvrent l'immense majorité des reprises automatiques. Sept jours couvrent les reprises manuelles et les rapprochements comptables, au prix d'un stockage sept fois plus gros. Ce choix se prend en connaissance du chiffre, pas au doigt mouillé.
La clé d'idempotence n'empêche pas le doublon, elle le rend inoffensif. Le second appel a bien lieu, il est bien reçu, il est bien traité : simplement, son traitement consiste à relire une réponse déjà écrite.
Ce qui casse en vrai
Le mécanisme est simple sur le papier, et il se casse toujours aux mêmes endroits.
La clé dérivée du contenu. Hacher le corps de la requête pour en faire la clé paraît élégant. Le procédé fusionne en réalité deux intentions légitimes identiques. Deux virements de 40 euros au même bénéficiaire dans la même minute, c'est parfois une erreur, et parfois deux virements.
L'absence de verrou. Sans insertion atomique, deux reprises concurrentes lisent toutes les deux « clé inconnue » et exécutent toutes les deux le traitement. Le magasin de clés donne alors une fausse impression de sécurité, ce qui est pire que son absence.
La purge mal calibrée. Trop courte, la clé disparaît avant la reprise manuelle du lendemain matin. Trop longue, la table grossit sans borne et finit par ralentir l'écriture qu'elle était censée protéger.
L'effet de bord hors transaction. L'écriture en base est protégée, le courriel de confirmation ne l'est pas, parce qu'il part d'un composant qui ignore la clé. L'utilisateur reçoit deux messages pour un seul paiement, et personne ne comprend pourquoi.
Le rejeu partiel. Le service a écrit en base, puis est mort avant d'enregistrer la réponse. La clé reste en état « en cours » pour toujours et bloque toutes les reprises. Il faut une expiration du verrou, et une décision explicite sur ce qu'on fait à son échéance.
| Erreur | Symptôme observé | Correctif |
|---|---|---|
| Clé dérivée du contenu | Deux opérations légitimes identiques fusionnées | Clé générée par le client, une par intention |
| Pas d'insertion atomique | Double exécution sous concurrence | Clé primaire, ou verrou explicite avec échéance |
| Purge trop courte | Reprise du lendemain rejouée pour de bon | Rétention alignée sur les procédures d'exploitation |
| Effet de bord non couvert | Notification envoyée deux fois | Effets externes publiés depuis la même transaction |
| Verrou sans échéance | Clé bloquée en état « en cours » | locked_until et reprise après expiration |
Ces mêmes contraintes réapparaissent dès qu'on distribue une transaction sur plusieurs services, où chaque étape et chaque compensation doivent être rejouables. C'est le sujet de Transactions distribuées : 2PC, 3PC, SAGA et verrous.
Un magasin de clés d'idempotence qui n'a jamais été testé sous concurrence réelle ne protège rien. Le test utile lance deux requêtes identiques à la même milliseconde et vérifie qu'un seul débit sort.
Scénario B : Stripe documente les deux moitiés
Le cas est rare, parce que l'entreprise a publié les deux versants du sujet, chacun de son côté.
Sur l'idempotence, la documentation de l'API Stripe décrit le mécanisme sans ambiguïté. Le client fournit un en-tête Idempotency-Key sur ses requêtes POST, et Stripe recommande d'y placer un identifiant unique universel de version 4. Le service enregistre le code de statut et le corps de la première réponse obtenue pour cette clé, succès comme échec, et renvoie ce résultat enregistré aux requêtes suivantes portant la même clé.
La documentation traite aussi les deux cas limites vus plus haut. Réutiliser une clé avec des paramètres différents produit une erreur, ce qui interdit la confusion entre deux intentions. Envoyer une seconde requête avec une clé encore utilisée par une requête en cours produit également une erreur, ce qui matérialise l'état intermédiaire de la machine à états. Enfin, la documentation indique que les clés deviennent éligibles à une suppression automatique lorsqu'elles ont au moins vingt-quatre heures, et qu'une requête présentant une clé déjà supprimée est traitée comme une requête neuve.
Sur la limitation, le billet d'ingénierie Rate limiters, publié en mars 2017, décrit ce que Stripe fait tourner en production. Le texte pose d'abord la distinction entre le limiteur de débit, qui applique un quota à l'appelant, et le délesteur, qui décide au vu de l'état global du système plutôt que de l'identité du demandeur. Il détaille ensuite quatre dispositifs qui cohabitent : un limiteur de requêtes par seconde et par utilisateur, un limiteur du nombre de requêtes en cours simultanément pour un même utilisateur, un délesteur qui réserve une fraction de la flotte aux appels critiques, et un délesteur de dernier recours qui coupe le trafic de moindre priorité quand les processus de traitement saturent. L'algorithme retenu est le seau à jetons, un seau par utilisateur, et l'implémentation repose sur Redis. Sur le code de refus, le billet renvoie le choix à l'implémenteur, 429 ou 503 selon la situation, le délesteur de flotte refusant pour sa part avec un 503.
Le billet relie les deux moitiés dès son introduction : une API de production se rend d'abord robuste par des techniques comme l'idempotence, et se prépare seulement ensuite à l'échelle. Côté client, il résume le comportement attendu en une formule, taper sur l'API puis lever le pied dès qu'un 429 revient. La forme précise de cette reprise, immédiate au premier échec puis à intervalles de plus en plus longs, et toujours avec la même clé, est décrite à part, dans les pages de gestion des erreurs de Stripe.
Les deux publications se répondent. La limitation fabrique les reprises, l'idempotence les rend anodines, et il faut avoir lu les deux pour traiter correctement le premier 429 reçu en production.
Points clés
- Le limiteur ne supprime pas le travail, il le refuse et le reporte. Chaque refus fabrique une reprise, donc un doublon potentiel.
- Le seau à jetons est le défaut raisonnable d'une API publique, parce qu'il règle séparément la rafale tolérée et le régime permanent.
- Un refus se dit avec un 429 et un
Retry-After, et une reprise se fait avec doublement du délai et bruit aléatoire, sinon le pic se reforme à l'identique. - L'idempotence se juge sur le code exécuté, pas sur le verbe HTTP. Le
POSTest le seul verbe courant qui n'offre aucune garantie, et c'est celui qui touche à l'argent. - Une clé d'idempotence se génère chez le client, se verrouille par insertion atomique, stocke la réponse complète, et se purge selon une durée qu'on a calculée.
Dans la série
Palier 2 : Faire circuler. Domaine : Flux & fiabilité.
- Précédent : Files de messages et Kafka : découpler pour tenir
- Suivant : Haute disponibilité : redondance, réplication, bascule
- Vue d'ensemble : System design : par où commencer
Pour aller plus loin
- Files de messages et Kafka : découpler pour tenir, pour comprendre d'où viennent les livraisons multiples quand la garantie est « au moins une fois ».
- Cache distribué et CDN : rapprocher la donnée du lecteur, pour le magasin partagé qui héberge en pratique les compteurs de quota.
- RFC 9110, HTTP Semantics, pour les définitions normatives des méthodes sûres et idempotentes ainsi que de
Retry-After. - RFC 6585, Additional HTTP Status Codes, qui définit le code 429.
- Documentation Stripe sur les requêtes idempotentes et le billet Rate limiters, les deux publications au cœur du scénario B.