Rate limiting d'API avec Claude Code : 429, Redis et Cloudflare
Implémentez un rate limiting d'API fiable avec Claude Code : 429, Redis, Cloudflare, anti-abus et pièges courants.
Le rate limiting d’une API consiste à définir combien de requêtes un même client peut envoyer pendant une courte période, puis à lui demander d’attendre lorsqu’il dépasse cette limite. Ce n’est pas une coupure globale du service. C’est une façon d’empêcher un utilisateur, un bot, un script ou une intégration de consommer plus que sa part.
Claude Code peut générer très vite des endpoints, de l’authentification et des tests. Le piège est de croire qu’une API qui fonctionne en local est prête pour la production. Les tentatives de connexion, la recherche, la génération IA, les SMS, les emails et les webhooks ont tous un coût réel. Lors d’un test de Masa sur un petit formulaire de contact, les doubles soumissions semblaient anodines jusqu’à ce que le quota d’email parte pendant la QA. Le vrai problème n’était pas seulement le formulaire, mais l’absence de règle : combien de fois cette action est-elle autorisée, et quelle réponse reçoit le client après la limite ?
Ce guide transforme le sujet en workflow Claude Code : cadrage, exemple Node.js sans dépendance, implémentation Express + Redis, client qui respecte Retry-After, rôle de Cloudflare, prévention d’abus, cas d’échec et CTA de conseil. Pour le contexte adjacent, lisez aussi développement d’API de production avec Claude Code, bonnes pratiques de sécurité et le guide Cloudflare Workers.
Gardez les références officielles ouvertes : Cloudflare Rate limiting rules, OWASP API Security 2023 API4: Unrestricted Resource Consumption et API6: Unrestricted Access to Sensitive Business Flows, ainsi que MDN sur 429 Too Many Requests.
Décider ce que l’on protège
Le mauvais réflexe est de commencer par un chiffre, par exemple 60 requêtes par minute. Commencez plutôt par le risque. Le rate limiting protège la capacité serveur, la base de données, les coûts d’API tierces, l’inventaire, les flux de mot de passe oublié, le quota email, les crédits IA, la qualité des leads et les règles métier.
flowchart LR
A["Request"] --> B["Identify client"]
B --> C["Check policy"]
C -->|allowed| D["Run handler"]
C -->|too many| E["Return 429 + Retry-After"]
D --> F["Log count and cost"]
Quelques cas réalistes :
| Cas | Clé de limite | Point de départ | Ce que l’on protège |
|---|---|---|---|
| Login, OTP, reset mot de passe | IP + compte | 5 essais / 10 min | Bruteforce, coût SMS |
| Recherche et listes | User id + path | 60 / min | DB, scraping |
| Génération IA ou image | User id + plan | 10 / jour en gratuit | Coût LLM, free tier |
| Webhook entrant | Émetteur + event id | Burst court autorisé | Doublons, files |
Ne vous reposez pas seulement sur l’IP. Dans une entreprise, une université ou un réseau mobile, beaucoup d’utilisateurs légitimes peuvent partager la même IP. Un attaquant peut aussi en changer. Pour une API authentifiée, combinez user id, API key, organisation, plan, endpoint et type d’opération.
Donner une vraie spécification à Claude Code
« Ajoute du rate limiting » est trop vague. Il faut préciser l’algorithme, les clés, la réponse 429, les headers, les tests, les logs et le stockage local/production. Ce prompt est directement réutilisable :
Add rate limiting to the existing API.
Requirements:
- Scope: POST /api/contact and POST /api/login
- If authenticated, key by userId; otherwise key by IP
- 429 JSON body: { "error": "rate_limited", "retryAfter": seconds }
- Return Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining
- Tests must cover allowed requests, limit reached, and recovery after time passes
- Use Redis in production and an in-memory store locally
- Make limits configurable through environment variables
After implementation, report the verification commands and any unverified risks.
Cette précision rend la livraison vérifiable. Claude Code sait qu’il doit produire un contrat client, pas seulement un middleware. Ajoutez les tests de la guide d’automatisation des tests API pour figer aussi les réponses 429.
Exemple minimal exécutable : serveur Node.js 429
Enregistrez ce fichier sous rate-limit-demo.mjs et lancez-le avec Node.js 20 ou plus. Il utilise un token bucket : le seau se remplit à vitesse fixe et chaque requête consomme un token. Cela autorise une petite rafale tout en contrôlant la moyenne.
import http from "node:http";
class TokenBucket {
constructor({ capacity, refillPerSecond }) {
this.capacity = capacity;
this.refillPerSecond = refillPerSecond;
this.tokens = capacity;
this.updatedAt = Date.now();
}
take(now = Date.now()) {
const elapsed = (now - this.updatedAt) / 1000;
this.tokens = Math.min(
this.capacity,
this.tokens + elapsed * this.refillPerSecond,
);
this.updatedAt = now;
if (this.tokens >= 1) {
this.tokens -= 1;
return { allowed: true, remaining: Math.floor(this.tokens), retryAfter: 0 };
}
const missing = 1 - this.tokens;
const retryAfter = Math.ceil(missing / this.refillPerSecond);
return { allowed: false, remaining: 0, retryAfter };
}
}
const buckets = new Map();
function clientKey(req) {
return req.headers["x-api-key"] ?? req.socket.remoteAddress ?? "anonymous";
}
function checkLimit(req) {
const key = clientKey(req);
if (!buckets.has(key)) {
buckets.set(key, new TokenBucket({ capacity: 5, refillPerSecond: 1 }));
}
return buckets.get(key).take();
}
const server = http.createServer((req, res) => {
if (req.url !== "/api/demo") {
res.writeHead(404, { "content-type": "application/json" });
res.end(JSON.stringify({ error: "not_found" }));
return;
}
const result = checkLimit(req);
res.setHeader("X-RateLimit-Limit", "5");
res.setHeader("X-RateLimit-Remaining", String(result.remaining));
if (!result.allowed) {
res.writeHead(429, {
"content-type": "application/json",
"Retry-After": String(result.retryAfter),
});
res.end(JSON.stringify({
error: "rate_limited",
retryAfter: result.retryAfter,
}));
return;
}
res.writeHead(200, { "content-type": "application/json" });
res.end(JSON.stringify({ ok: true, remaining: result.remaining }));
});
server.listen(3000, () => {
console.log("Listening on http://localhost:3000/api/demo");
});
node rate-limit-demo.mjs
Dans un autre terminal :
for i in 1 2 3 4 5 6 7; do
curl -i http://localhost:3000/api/demo
done
Sous Windows PowerShell :
1..7 | ForEach-Object {
curl.exe -i http://localhost:3000/api/demo
}
La sixième ou septième requête doit répondre 429 Too Many Requests. Comme l’explique MDN, Retry-After indique au client quand réessayer et évite les boucles agressives.
Implémentation Redis pour plusieurs instances
La version mémoire est utile pour apprendre, mais elle casse avec plusieurs serveurs API. L’instance A peut penser qu’il ne reste plus rien tandis que l’instance B croit qu’il reste cinq requêtes. Redis centralise le compteur.
L’exemple suivant utilise Express et un sorted set Redis pour une sliding window. Une sliding window compte les 60 dernières secondes à partir de maintenant, au lieu de réinitialiser brutalement au changement de minute.
npm init -y
npm i express ioredis
docker run --rm --name redis-rate-limit -p 6379:6379 redis:7-alpine
import express from "express";
import Redis from "ioredis";
const app = express();
const redis = new Redis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379");
const limitScript = `
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window_ms = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local member = ARGV[4]
redis.call("ZREMRANGEBYSCORE", key, 0, now - window_ms)
local count = redis.call("ZCARD", key)
if count >= limit then
local oldest = redis.call("ZRANGE", key, 0, 0, "WITHSCORES")[2]
local retry_ms = math.max(1, oldest + window_ms - now)
return {0, 0, retry_ms}
end
redis.call("ZADD", key, now, member)
redis.call("PEXPIRE", key, window_ms)
return {1, limit - count - 1, 0}
`;
async function rateLimit(req, res, next) {
const user = req.get("authorization")?.replace(/^Bearer\s+/i, "");
const identity = user || req.ip || "anonymous";
const key = `rl:${identity}:${req.path}`;
const limit = Number(process.env.RATE_LIMIT_REQUESTS ?? 10);
const windowMs = Number(process.env.RATE_LIMIT_WINDOW_MS ?? 60000);
const now = Date.now();
const member = `${now}:${Math.random()}`;
const [allowed, remaining, retryMs] = await redis.eval(
limitScript,
1,
key,
limit,
windowMs,
now,
member,
);
res.setHeader("X-RateLimit-Limit", String(limit));
res.setHeader("X-RateLimit-Remaining", String(remaining));
if (allowed === 1) return next();
const retryAfter = Math.ceil(Number(retryMs) / 1000);
res.setHeader("Retry-After", String(retryAfter));
res.status(429).json({ error: "rate_limited", retryAfter });
}
app.use(rateLimit);
app.get("/api/search", (req, res) => {
res.json({ data: ["claude-code", "rate-limit"], at: new Date().toISOString() });
});
app.listen(3000, () => {
console.log("API ready on http://localhost:3000/api/search");
});
node redis-rate-limit-server.mjs
for i in $(seq 1 12); do
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/search
done
Quand Claude Code prépare une version production, précisez le comportement si Redis tombe. Un formulaire marketing peut parfois fail open quelques minutes. Login, paiement ou crédits IA doivent souvent fail closed. C’est une décision métier.
Le client doit respecter Retry-After
Le serveur ne suffit pas. SDKs, batchs et émetteurs de webhooks doivent lire Retry-After et attendre.
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function fetchWithRateLimit(url, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
const res = await fetch(url, options);
if (res.status !== 429) return res;
const retryAfter = Number(res.headers.get("retry-after") ?? "1");
const waitMs = Math.max(1, retryAfter) * 1000;
console.log(`429 received. Waiting ${waitMs}ms before retry.`);
await sleep(waitMs);
}
throw new Error("Rate limit retry budget exhausted");
}
for (let i = 0; i < 8; i += 1) {
const res = await fetchWithRateLimit("http://localhost:3000/api/demo");
console.log(i + 1, res.status, await res.text());
}
Pour un client d’API externe, demandez à Claude Code : backoff exponentiel sur 429, priorité à Retry-After s’il existe, nombre maximal de retries, log de l’échec final. Les retries infinis transforment une limite normale en incident.
Cloudflare au bord, règles utilisateur dans l’application
Cloudflare Rate Limiting Rules est très utile pour rejeter les pics évidents avant l’origine. La documentation officielle couvre expressions, périodes, seuils, mitigation timeout et actions. C’est adapté aux pages de login, recherches publiques, routes admin, endpoints IA et motifs de bots.
Cloudflare ne remplace pas les règles produit. Quotas par plan, usage par organisation, crédits IA, abus de remboursement ou d’invitation demandent les données applicatives. En pratique, on empile les couches :
| Couche | Rôle | Exemple |
|---|---|---|
| Cloudflare/WAF | Bloquer tôt bursts et bots | Limiter /api/login par IP |
| Application | Limiter user, organisation, plan, opération | Gratuit : 10 générations/jour |
| Queue/worker | Lisser les tâches coûteuses | Email, images, PDF |
| Billing/monitoring | Détecter les coûts anormaux | Alertes SMS et LLM |
OWASP API4 décrit le risque lié aux ressources sans limite : CPU, mémoire, taille de fichier, services tiers. OWASP API6 couvre l’abus automatisé de flux métier sensibles comme achat, réservation, publication ou parrainage. Le rate limiting est donc aussi une protection de revenu : free tier, spam, revente, SMS coûteux et attaques de compte.
Pièges et erreurs concrètes
Premier piège : un seul seuil global. Lire un profil et demander un reset de mot de passe n’ont pas le même risque. Découpez par endpoint et opération.
Deuxième piège : une réponse 429 non standardisée. HTML ici, texte là, JSON ailleurs rend les clients fragiles. Fixez le body JSON, Retry-After et les headers.
Troisième piège : compter seulement les succès. Logins ratés, payloads invalides et resets vers des emails inconnus coûtent aussi et signalent de l’abus. Souvent, les échecs doivent être limités plus strictement.
Quatrième piège : stocker des données personnelles dans les clés. Ne mettez pas emails ou téléphones en clair dans Redis ou les logs. Hashez si nécessaire et gardez un TTL court.
Cinquième piège : faire dormir les tests 60 secondes. Injectez now et avancez le temps en test.
Dernier piège : bloquer l’infrastructure légitime. Bots de recherche, uptime checks, monitoring interne, webhooks de paiement et partenaires méritent parfois des politiques séparées. Les exceptions doivent être étroites et auditables.
Checklist de revue Claude Code
Après l’implémentation, demandez une revue sur ces points :
- Les 429 ont-ils tous le même JSON ?
Retry-Afteret les headers restants sont-ils présents ?- La clé IP, user id, API key, organisation est-elle correcte ?
- Le comportement en panne Redis est-il explicite ?
- Les échecs auth, validation et API externes sont-ils comptés si nécessaire ?
- Les tests couvrent-ils allowed, blocked, recovered ?
- Les exceptions admin, monitoring, webhook et crawler sont-elles trop larges ?
Ce n’est pas seulement de la qualité de code. Avec IA, SMS, email ou paiement, un manque de rate limiting devient une facture.
CTA conseil
ClaudeCodeLab couvre l’implémentation API, la revue sécurité, le rate limiting, la protection des coûts et le monitoring dans Claude Code training and consulting. Sur un projet Next.js, Express, Cloudflare Workers ou AWS API Gateway, le vrai travail est de transformer “quelle opération, pour qui, combien de fois” en code, tests et logs.
Pour un projet solo, lancez d’abord la démo Node.js, puis passez à Redis dès qu’il y a plusieurs instances. Pour une équipe, documentez prompt Claude Code, checklist de revue, variables d’environnement et runbook afin de changer les seuils sans improviser.
J’ai vérifié les exemples de cet article : le serveur mémoire renvoie 429 après plusieurs requêtes, la version Redis renvoie 429 avec Retry-After après la fenêtre de 10 requêtes, et le client avec attente cesse de retry immédiatement. La leçon est nette : un rate limit n’est prêt pour la production que si réponse, retry, logs et exceptions sont vérifiés ensemble.
PDF gratuit: cheatsheet Claude Code
Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.
Nous protégeons vos données et n'envoyons pas de spam.
À propos de l'auteur
Masa
Ingénieur spécialisé dans les workflows pratiques avec Claude Code.
Articles liés
Permission receipt Claude Code : portée, preuves et rollback
Modèle de permission receipt pour Claude Code : actions autorisées, limites d'approbation, commandes de preuve, rollback et CTAs revenus.
Agent Harness securise pour Claude Code et Codex : permissions, verification et rollback
Construisez un Agent Harness pratique pour Claude Code et Codex avec politiques, plan, verification et recuperation.
Sous-agents Claude Code : guide pratique pour déléguer sans perdre le contrôle
Guide pratique des sous-agents Claude Code pour répartir articles et code : règles, prompts, pièges et checklist.