Rate Limiting API
Le rate limiting (limitation de débit) est un mécanisme qui restreint le nombre de requêtes qu’un client peut envoyer à une API dans un intervalle de temps donné, protégeant l’infrastructure backend contre les surcharges, les abus et les dénis de service.
- Catégorie
- Sécurité API / Gestion du trafic
- Code HTTP de rejet
- 429 Too Many Requests (RFC 6585)
- Algorithmes principaux
- Token bucket, Sliding window, Fixed window, Leaky bucket, GCRA
- Headers standards
- X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After
- Store distribué
- Redis (standard de facto), Memcached, DynamoDB
- Implémenté par
- API Gateways (Kong, NGINX, AWS API Gateway), middleware applicatif, edge/CDN
- Variante IA
- Token-based rate limiting (par tokens LLM, pas par requêtes HTTP)
Pourquoi le rate limiting est indispensable
Sans rate limiting, un seul client mal configuré, un script en boucle, ou une attaque DDoS peut consommer toutes les ressources de votre API et rendre le service indisponible pour les autres utilisateurs. Le rate limiting pose un plafond par client, par endpoint, ou par critère arbitraire (clé API, IP, user ID, tenant).
Les raisons de l’implémenter sont multiples et complémentaires :
Protection contre les surcharges. Chaque requête consomme du CPU, de la mémoire, des connexions base de données et de la bande passante. Sans limite, un seul consommateur peut monopoliser ces ressources. Le rate limiting crée un plafond qui protège tous les autres tenants.
Défense contre les attaques. Les attaques par force brute (tentatives massives de mots de passe), le credential stuffing et les DDoS applicatifs (HTTP flood) deviennent inefficaces quand le nombre de tentatives est plafonné. Un rate limit de 5 requêtes par minute sur un endpoint d’authentification rend une attaque par force brute irréalisable en pratique.
Équité entre clients. Dans un système multi-tenant (plusieurs clients partagent la même infrastructure), le rate limiting garantit qu’un gros consommateur ne dégrade pas le service pour les autres. C’est la base du « fair use » dans tout plan tarifaire d’API.
Maîtrise des coûts. Sur une infrastructure pay-per-use (fonctions serverless, APIs LLM, services tiers), un usage non borné se traduit directement en coûts non maîtrisés. Le rate limiting empêche un client en boucle de générer une facture imprévue.
Différenciation commerciale. Les plans tarifaires des APIs se distinguent principalement par leurs limites de débit : un plan gratuit à 60 req/min, un plan pro à 600 req/min, un plan enterprise sans limite. Le rate limiting est l’outil technique qui rend ces plans réels.
Les algorithmes de rate limiting
Cinq algorithmes dominent le paysage. Chacun fait des compromis différents entre précision, consommation mémoire, tolérance aux bursts, et complexité d’implémentation.
Fixed Window (fenêtre fixe)
L’algorithme le plus simple. Le temps est découpé en intervalles fixes (par exemple, toutes les 60 secondes). Un compteur s’incrémente à chaque requête et se remet à zéro au début de la fenêtre suivante. Si le compteur dépasse la limite, les requêtes sont rejetées jusqu’à la prochaine fenêtre.
Avantage : très simple à implémenter, faible consommation mémoire (un seul compteur par client).
Inconvénient majeur : vulnérable au « boundary burst ». Un client peut envoyer 100 requêtes dans la dernière seconde de la fenêtre A, puis 100 requêtes dans la première seconde de la fenêtre B, soit 200 requêtes en 2 secondes au lieu des 100/minute prévues.
# Fixed Window avec Redis (Python)
import time
import redis
r = redis.Redis()
def is_allowed(client_id: str, max_requests: int, window_seconds: int) -> bool:
window_key = int(time.time()) // window_seconds
key = f"rl:fw:{client_id}:{window_key}"
current = r.incr(key)
if current == 1:
r.expire(key, window_seconds)
return current <= max_requests
Sliding Window (fenêtre glissante)
Au lieu de réinitialiser à intervalles fixes, la fenêtre « glisse » avec le temps. À chaque instant t, le système compte les requêtes dans l’intervalle [t – window, t]. Plus précis que le fixed window, il élimine le problème du boundary burst.
Deux variantes existent :
Sliding Window Log. Chaque requête est stockée avec son timestamp exact (dans un Redis Sorted Set, par exemple). Pour vérifier la limite, on compte les entrées dans la fenêtre courante. Très précis, mais consommation mémoire proportionnelle au nombre de requêtes (problématique à grande échelle).
Sliding Window Counter. Un compromis efficace : on utilise deux compteurs de fixed window (fenêtre courante et fenêtre précédente) et on pondère le compteur précédent proportionnellement au temps écoulé. Par exemple, si vous êtes à 75 % dans la fenêtre courante, le calcul est : count = current_count + previous_count × 0.25. Précision proche du log, mémoire proche du fixed window.
# Sliding Window Counter avec Redis (Python)
import time
import redis
r = redis.Redis()
def is_allowed_sliding(client_id: str, max_requests: int, window: int) -> bool:
now = time.time()
current_window = int(now) // window
previous_window = current_window - 1
elapsed_ratio = (now % window) / window
current_key = f"rl:sw:{client_id}:{current_window}"
previous_key = f"rl:sw:{client_id}:{previous_window}"
current_count = int(r.get(current_key) or 0)
previous_count = int(r.get(previous_key) or 0)
# Pondération : la fenêtre précédente compte proportionnellement
# au temps restant avant la fin de la fenêtre courante
weighted_count = current_count + previous_count * (1 - elapsed_ratio)
if weighted_count >= max_requests:
return False
pipe = r.pipeline()
pipe.incr(current_key)
pipe.expire(current_key, window * 2)
pipe.execute()
return True
Token Bucket (seau à jetons)
L’algorithme le plus populaire en production. Un « seau » virtuel contient des jetons. Chaque requête consomme un jeton. Les jetons se rechargent à un rythme constant (par exemple, 10 jetons/seconde). Le seau a une capacité maximale (par exemple, 100 jetons). Si le seau est plein et que le client n’a pas fait de requête depuis un moment, il a 100 jetons disponibles pour un burst. S’il envoie tout d’un coup, il devra ensuite attendre le rechargement.
Avantage : tolère les bursts tout en maintenant un débit moyen contrôlé. C’est l’algorithme utilisé par Anthropic pour les limites de l’API Claude.
Paramètres clés : le rate (vitesse de rechargement) contrôle le débit moyen ; le bucket size (taille du seau) contrôle le burst maximum autorisé.
# Token Bucket avec Redis + Lua (atomique)
import redis
import time
r = redis.Redis()
LUA_SCRIPT = """
local key = KEYS[1]
local rate = tonumber(ARGV[1]) -- jetons/seconde
local capacity = tonumber(ARGV[2]) -- taille max du seau
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4]) -- jetons demandés (1 pour requête HTTP)
local data = redis.call('HMGET', key, 'tokens', 'last_refill')
local tokens = tonumber(data[1]) or capacity
local last_refill = tonumber(data[2]) or now
-- Recharger les jetons accumulés depuis le dernier accès
local elapsed = now - last_refill
local refill = elapsed * rate
tokens = math.min(capacity, tokens + refill)
local allowed = 0
if tokens >= requested then
tokens = tokens - requested
allowed = 1
end
redis.call('HMSET', key, 'tokens', tokens, 'last_refill', now)
redis.call('EXPIRE', key, math.ceil(capacity / rate) * 2)
return {allowed, math.floor(tokens)}
"""
token_bucket = r.register_script(LUA_SCRIPT)
def check_rate_limit(client_id, rate=10, capacity=100):
result = token_bucket(
keys=[f"rl:tb:{client_id}"],
args=[rate, capacity, time.time(), 1]
)
return {"allowed": bool(result[0]), "remaining": result[1]}
Leaky Bucket (seau percé)
Les requêtes entrent dans une file d’attente (le seau) et sont traitées à un débit constant (la fuite). Si le seau est plein, les nouvelles requêtes sont rejetées. Contrairement au token bucket, le leaky bucket ne tolère pas les bursts : le débit de sortie est toujours constant.
Idéal pour : les cas où vous avez besoin d’un débit parfaitement lissé (traitement de paiements, écriture en base, appels à un service externe avec un SLA strict sur le débit).
GCRA (Generic Cell Rate Algorithm)
Un algorithme élégant qui stocke un seul timestamp par client (le « theoretical arrival time » de la prochaine requête autorisée). Si la requête arrive après ce timestamp, elle passe. Sinon, elle est rejetée. L’avantage est la consommation mémoire minimale : un seul nombre par client, contre un sorted set potentiellement volumineux pour le sliding window log.
Idéal pour : les systèmes à très grande échelle (> 100K clés de rate limiting simultanées) où la mémoire est une contrainte.
Comparatif des algorithmes
| Algorithme | Tolérance burst | Précision | Mémoire | Complexité | Cas d’usage |
|---|---|---|---|---|---|
| Fixed Window | Non (boundary burst) | Faible | Très faible (1 compteur) | Triviale | Prototypage, APIs internes low-stakes |
| Sliding Window Log | Non | Exacte | Élevée (1 timestamp/requête) | Moyenne | Billing, compliance, audit |
| Sliding Window Counter | Non | Bonne (~99,7 %) | Faible (2 compteurs) | Moyenne | Choix par défaut pour la plupart des APIs |
| Token Bucket | Oui (configurable) | Bonne | Faible (2 valeurs) | Moyenne | APIs publiques, LLM (Anthropic, OpenAI) |
| Leaky Bucket | Non (débit constant) | Exacte | Moyenne (file d’attente) | Moyenne | Traitement de flux, paiements |
| GCRA | Oui (limitée) | Bonne | Très faible (1 timestamp) | Faible | Grande échelle, systèmes mémoire-contraints |
Recommandation : pour la majorité des APIs, le token bucket est le meilleur choix (burst toléré, mémoire faible, adopté par les principaux fournisseurs). Si vous avez besoin d’une précision exacte (facturation, compliance), utilisez le sliding window log. Si la mémoire est critique, le GCRA est imbattable.
Headers HTTP et communication avec le client
Un bon rate limiter ne se contente pas de rejeter les requêtes excédentaires. Il informe le client de ses limites, de sa consommation, et du moment où il pourra réessayer. Cette transparence est essentielle pour que les développeurs puissent intégrer votre API correctement.
Headers de réponse
Les headers X-RateLimit-* sont le standard de facto (le draft IETF RateLimit sans préfixe est toujours en cours de formalisation début 2026) :
| Header | Description | Exemple |
|---|---|---|
X-RateLimit-Limit |
Nombre max de requêtes dans la fenêtre | 100 |
X-RateLimit-Remaining |
Requêtes restantes dans la fenêtre courante | 73 |
X-RateLimit-Reset |
Timestamp Unix de la remise à zéro | 1711036800 |
Retry-After |
Secondes à attendre avant de réessayer (dans la réponse 429) | 30 |
Ces headers doivent être présents dans chaque réponse, pas seulement dans les 429. Le client peut ainsi adapter son débit proactivement sans jamais atteindre la limite.
Structure de la réponse 429
Quand une requête est rejetée, retournez un corps JSON structuré en plus du header Retry-After :
HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711036800
Content-Type: application/json
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Limite de débit dépassée. Réessayez dans 30 secondes.",
"retry_after": 30,
"limit": 100,
"window": "60s"
}
}
Rate limiting distribué
Quand votre API tourne sur plusieurs instances (pods Kubernetes, instances EC2, conteneurs Docker), le rate limiting local (en mémoire de chaque instance) ne fonctionne plus : un client à 100 req/min se retrouve avec 100 req/min × N instances. Il faut centraliser l’état du rate limiter.
Redis comme store centralisé
Redis est le choix standard pour le rate limiting distribué. Ses avantages : latence sub-milliseconde (< 1 ms), support des scripts Lua atomiques, structures de données natives (strings, sorted sets, hashes), et haute disponibilité via Redis Sentinel ou Redis Cluster.
Point d’attention avec Redis Cluster : un script Lua ne peut accéder qu’aux clés mappées sur le même slot (hash slot). Pour garantir que toutes les clés d’un même client atterrissent sur le même slot, utilisez les hash tags : {user123}:rl:endpoint1 et {user123}:rl:endpoint2.
Fail open vs fail closed
Si Redis est injoignable, deux stratégies s’offrent à vous :
Fail open : les requêtes passent sans vérification. L’API reste disponible, mais sans protection. C’est le choix quand la disponibilité prime sur la sécurité (APIs internes, services non critiques).
Fail closed : toutes les requêtes sont rejetées. L’API est protégée, mais indisponible. C’est le choix pour les APIs sensibles (paiements, authentification).
En pratique, la plupart des systèmes utilisent un fail open avec un circuit breaker : si Redis est down pendant plus de X secondes, on bascule en mode dégradé avec un rate limiting local approximatif.
Les trois couches de rate limiting
Un rate limiting efficace en production opère à trois niveaux, chacun avec un rôle distinct :
| Couche | Emplacement | Rôle | Exemple |
|---|---|---|---|
| Edge / CDN | Cloudflare, AWS CloudFront, Akamai | Bloquer le DDoS et le trafic malveillant avant qu’il atteigne vos serveurs | Limite globale de 10 000 req/s par IP |
| API Gateway | Kong, NGINX, AWS API Gateway | Limites par client, par endpoint, par plan tarifaire | 100 req/min pour le plan Free, 1000 pour le plan Pro |
| Application | Middleware dans votre code (Express, FastAPI, Spring) | Logique métier fine : limites par opération coûteuse, par ressource | 5 exports PDF/heure, 10 générations d’images/minute |
La règle : appliquez le rate limiting au plus tôt dans la chaîne. Le edge bloque le bruit avant votre infrastructure. Le gateway applique les politiques par client. L’application gère la logique métier spécifique. Les trois couches sont complémentaires, pas alternatives.
Rate limiting des APIs LLM : un cas particulier
Les APIs de modèles de langage (LLM) posent un défi unique au rate limiting classique. Deux requêtes au même endpoint peuvent avoir des coûts radicalement différents : un prompt de 50 tokens et un prompt de 10 000 tokens comptent tous les deux comme « 1 requête », mais la seconde consomme 200 fois plus de compute.
RPM, TPM, et la double limite
Les principaux fournisseurs LLM appliquent des limites multiples simultanées :
| Fournisseur | Métriques limitées | Algorithme | Exemple (tier de base) |
|---|---|---|---|
| OpenAI | RPM, RPD, TPM, TPD, IPM (images) | Non documenté publiquement | GPT-5 Tier 1 : ~1 000 RPM, ~500K TPM |
| Anthropic (Claude) | RPM, ITPM (input), OTPM (output) | Token bucket | Tier 1 : 50 RPM, ITPM et OTPM selon modèle |
| Azure OpenAI | TPM par région/souscription, RPM proportionnel | Quotas par déploiement | GPT-4.1 : jusqu’à 5M TPM selon région |
Chez Anthropic, un point important : les tokens cachés via le prompt caching ne comptent pas vers la limite ITPM. Cela signifie que le caching augmente effectivement votre capacité de débit, parfois d’un facteur 5 à 10x.
Chez OpenAI, les limites RPM peuvent être quantifiées par seconde : un RPM de 60 est en réalité appliqué comme 1 req/s maximum. Un burst de 10 requêtes en une seconde sera rejeté même si vous êtes loin des 60 req/min sur la minute complète.
Token-based rate limiting pour vos propres APIs IA
Si vous exposez vous-même une API qui appelle un LLM en backend, le rate limiting classique par requêtes est insuffisant. Vous devez limiter par tokens consommés, ce qui reflète le coût réel.
Le principe : au lieu de décrémenter 1 jeton par requête dans votre token bucket, décrémentez le nombre de tokens du prompt + de la réponse. Un prompt de 8 000 tokens consomme 8 000 jetons du seau, pas 1.
Le défi : vous ne connaissez pas le nombre de tokens de la réponse avant qu’elle soit générée. Deux approches :
Pré-estimation : comptez les tokens d’input avant d’envoyer la requête au LLM et vérifiez que le client a assez de budget. Ajustez après la réponse.
Post-décompte : laissez la requête passer, comptez les tokens totaux (input + output) après la réponse, et déduisez du budget du client. Si le budget est épuisé, rejetez la prochaine requête.
Gérer le rate limiting côté client
Si vous consommez une API rate-limitée (OpenAI, Claude, APIs tierces), vous devez gérer les 429 côté client. Voici les techniques essentielles :
Exponential backoff avec jitter
La technique de base : quand vous recevez un 429, attendez avant de réessayer. Doublez le délai à chaque tentative échouée (1s, 2s, 4s, 8s…) et ajoutez un élément aléatoire (jitter) pour éviter que tous les clients réessaient au même moment.
import time
import random
import requests
def call_with_backoff(url, payload, max_retries=5):
for attempt in range(max_retries):
response = requests.post(url, json=payload)
if response.status_code != 429:
return response
# Lire le Retry-After si disponible
retry_after = int(response.headers.get("Retry-After", 0))
# Exponential backoff avec jitter
base_delay = max(retry_after, 2 ** attempt)
jitter = random.uniform(0, base_delay * 0.5)
wait_time = min(base_delay + jitter, 60) # cap à 60s
print(f"429 reçu. Retry dans {wait_time:.1f}s (tentative {attempt + 1})")
time.sleep(wait_time)
raise Exception("Rate limit : max retries atteint")
Autres stratégies côté client
Request queuing. Au lieu d’envoyer les requêtes au fil de l’eau, mettez-les dans une file et consommez-les à un rythme qui respecte la limite. Si la limite est 60 RPM, espacez vos requêtes d’au moins 1 seconde.
Caching local. Si vous posez la même question à un LLM plusieurs fois, cachez la réponse. La meilleure façon de ne pas atteindre un rate limit est de ne pas faire la requête.
Fallback multi-provider. Quand OpenAI vous rate-limite, basculez vers Anthropic ou Mistral. Un LLM Gateway comme LiteLLM ou Bifrost automatise ce basculement.
Pré-chunking. Découpez les grands documents en morceaux qui respectent individuellement la limite TPM, plutôt que d’envoyer un seul prompt massif qui consomme tout le budget d’un coup.
Bonnes pratiques d’implémentation
Appliquez le rate limiting AVANT les opérations coûteuses. Le middleware de rate limiting doit s’exécuter avant l’authentification lourde, les requêtes base de données, et la logique métier. Si vous vérifiez la limite après avoir exécuté la requête, vous avez déjà consommé les ressources que vous essayez de protéger.
Utilisez des identifiants fiables. Le rate limiting par IP seule est fragile : derrière un NAT ou un proxy d’entreprise, des milliers d’utilisateurs partagent la même IP. Combinez IP + clé API ou IP + user ID pour une granularité correcte.
Définissez des limites superposées. Une seule limite (100 req/min) est insuffisante. Combinez un rate limit par seconde (burst : 10 req/s), par minute (soutenu : 100 req/min), et par jour (quotidien : 10 000 req/jour). Chaque couche protège contre un scénario différent.
Documentez vos limites. Publiez clairement les limites dans votre documentation API, avec les headers retournés, le comportement en cas de dépassement, et les étapes pour demander une augmentation. Les développeurs qui intègrent votre API ne devraient jamais découvrir les limites par surprise.
Surveillez les métriques de rate limiting. Monitorer le taux de requêtes 429, le taux de faux positifs (utilisateurs légitimes bloqués), et le délai ajouté par le rate limiter (overhead de latence). Un taux de 429 élevé sur des clients légitimes indique des limites trop basses ou un algorithme mal calibré.
Questions fréquentes sur le rate limiting API
Quelle est la différence entre rate limiting et throttling ?
Le rate limiting rejette les requêtes excédentaires avec un code HTTP 429 (Too Many Requests). Le throttling ralentit les requêtes en les mettant en file d’attente sans les rejeter immédiatement. En pratique, beaucoup de systèmes combinent les deux : une file d’attente pour absorber les petits pics, et un rejet si la file déborde. Certaines documentations utilisent les deux termes de manière interchangeable, mais la distinction technique est utile pour concevoir votre stratégie.
Quel algorithme de rate limiting choisir ?
Le token bucket est le meilleur choix par défaut : il tolère les bursts, consomme peu de mémoire, et est utilisé par les principaux fournisseurs (Anthropic pour l’API Claude). Si vous avez besoin de précision exacte pour de la facturation ou de la compliance, le sliding window log est préférable. Pour des systèmes avec plus de 100 000 clients simultanés et des contraintes mémoire fortes, le GCRA (un seul timestamp par client) est optimal. Le fixed window est acceptable uniquement pour du prototypage ou des APIs internes peu critiques.
Comment implémenter le rate limiting avec plusieurs instances de serveur ?
Un rate limiter en mémoire locale ne fonctionne pas en environnement distribué : chaque instance applique la limite indépendamment, multipliant la capacité réelle par le nombre d’instances. La solution standard est Redis comme store centralisé, avec des scripts Lua pour garantir l’atomicité des opérations. Assurez-vous d’utiliser les hash tags Redis Cluster pour que toutes les clés d’un même client soient sur le même slot. Prévoyez aussi une stratégie de fallback si Redis tombe (fail open ou fail closed selon la criticité).
Quels sont les rate limits des APIs IA (OpenAI, Claude) ?
OpenAI et Anthropic appliquent des limites multiples simultanées : requêtes par minute (RPM) et tokens par minute (TPM). Chez OpenAI (GPT-5, Tier 1), les limites sont environ 1 000 RPM et 500 000 TPM. Chez Anthropic (Claude, Tier 1), c’est 50 RPM avec des ITPM et OTPM séparés pour l’input et l’output. Les limites augmentent automatiquement avec vos dépenses (système de tiers). Point important : Anthropic ne compte pas les tokens cachés (prompt caching) dans la limite ITPM, ce qui booste le débit effectif.
Comment éviter les erreurs 429 quand on utilise une API ?
Cinq stratégies complémentaires : implémentez l’exponential backoff avec jitter (doublez le délai à chaque retry, ajoutez un élément aléatoire). Lisez le header Retry-After pour savoir exactement quand réessayer. Cachez les réponses aux requêtes répétitives. Utilisez un request queue pour espacer vos appels au lieu de les envoyer en burst. Et pour les APIs LLM, configurez un fallback multi-provider via un LLM Gateway (LiteLLM, Bifrost) pour basculer automatiquement vers un autre fournisseur quand l’un vous rate-limite.