Rate limiting de APIs con Claude Code: 429, Redis y Cloudflare
Implementa rate limiting de APIs con Claude Code: 429, Redis, Cloudflare, prevención de abuso y errores comunes.
El rate limiting de una API consiste en definir cuántas peticiones puede enviar el mismo cliente durante un periodo corto y pedirle que espere cuando supera ese límite. No se trata de cerrar el servicio para todos. Se trata de evitar que un usuario, bot, script o integración consuma más de lo razonable.
Claude Code puede generar endpoints, autenticación y tests con rapidez. El riesgo es confundir “funciona en local” con “puede vivir en producción”. Los intentos de login, búsquedas, generación con IA, SMS, correos, webhooks y jobs por lotes cuestan dinero y recursos. En una prueba de Masa con un formulario de contacto, los envíos duplicados parecían un detalle menor hasta que el proveedor de email empezó a consumir cuota durante QA. El problema no era solo el formulario; faltaba una regla clara: cuántas veces puede ocurrir esta acción y qué respuesta recibe el cliente al pasar el límite.
Esta guía convierte el tema en un flujo práctico para Claude Code: diseño previo, demo sin dependencias en Node.js, implementación con Redis y Express, cliente que respeta Retry-After, uso de Cloudflare en el borde, prevención de abuso, fallos habituales y CTA de consultoría. Para bases relacionadas, lee desarrollo de APIs en producción con Claude Code, buenas prácticas de seguridad y la guía de Cloudflare Workers.
Trabaja siempre con fuentes oficiales abiertas: Cloudflare Rate limiting rules, OWASP API Security 2023 API4: Unrestricted Resource Consumption y API6: Unrestricted Access to Sensitive Business Flows, además de MDN sobre 429 Too Many Requests.
Decide qué estás protegiendo
El error inicial es elegir un número, por ejemplo “60 peticiones por minuto”, antes de entender el riesgo. El rate limiting protege capacidad de servidor, base de datos, coste de APIs externas, inventario, flujos de recuperación de contraseña, cuota de email, créditos de IA, calidad de leads y reglas de negocio.
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"]
Casos realistas para empezar:
| Caso | Clave del límite | Punto inicial | Qué protege |
|---|---|---|---|
| Login, OTP, reset de contraseña | IP + cuenta | 5 intentos / 10 min | Fuerza bruta, coste SMS |
| APIs de búsqueda y listas | Usuario + path | 60 / min | DB, scraping |
| Generación IA o imágenes | Usuario + plan | 10 al día en plan gratis | Coste LLM, free tier |
| Recepción de webhooks | Emisor + event id | Permitir ráfagas cortas | Duplicados, cola |
No dependas solo de la IP. En oficinas, universidades o redes móviles, muchos usuarios legítimos comparten IP. Un atacante también puede rotarlas. En APIs autenticadas, combina user id, API key, organización, plan, endpoint y tipo de operación.
Dale a Claude Code una especificación real
“Añade rate limiting” es demasiado amplio. Hay que indicar algoritmo, clave, respuesta 429, headers, tests, logs y almacenamiento local/producción. Este prompt sirve como base:
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.
Así Claude Code tiene criterios de aceptación y no se queda en “añadí middleware”. También fija el contrato que consumirán frontend, SDKs y jobs. Combínalo con la guía de testing de APIs para probar 429 igual que probarías 200 o 401.
Ejemplo mínimo ejecutable: servidor Node.js con 429
Guarda esto como rate-limit-demo.mjs y ejecútalo con Node.js 20 o superior. Usa token bucket: el bucket se rellena a ritmo constante y cada request consume un token. Permite una ráfaga corta, pero controla la media.
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
En otra terminal:
for i in 1 2 3 4 5 6 7; do
curl -i http://localhost:3000/api/demo
done
En Windows PowerShell:
1..7 | ForEach-Object {
curl.exe -i http://localhost:3000/api/demo
}
La sexta o séptima petición debería devolver 429 Too Many Requests. MDN explica que esta respuesta puede incluir Retry-After, y ese header ayuda al cliente a esperar en lugar de insistir sin control.
Implementación con Redis para varias instancias
La versión en memoria sirve para aprender, pero falla si despliegas más de una instancia. El servidor A puede creer que no quedan tokens y el B que todavía quedan cinco. Redis centraliza el contador.
Este ejemplo usa Express y un sorted set de Redis para una sliding window. Una sliding window cuenta los últimos 60 segundos desde el momento actual, no un minuto fijo del reloj, por lo que evita picos artificiales en el borde del periodo.
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
Cuando pidas a Claude Code una versión de producción, especifica qué ocurre si Redis falla. Un formulario de marketing quizá puede fallar abierto unos minutos. Login, pagos o créditos de IA quizá deban fallar cerrado. Es una decisión de riesgo, no un detalle técnico menor.
El cliente debe respetar Retry-After
El servidor es solo la mitad. SDKs, procesos batch y emisores de webhooks deben leer Retry-After y esperar antes de reintentar.
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());
}
Para clientes de APIs externas, pide a Claude Code: “si llega 429, usa backoff exponencial, prioriza Retry-After si existe, limita reintentos y registra el fallo final”. Los reintentos infinitos convierten un límite normal en incidente.
Cloudflare en el borde, reglas de usuario en la app
Cloudflare Rate Limiting Rules es útil para rechazar picos evidentes antes de llegar al origin. La documentación oficial cubre expresiones, periodos, umbrales, mitigation timeout y acciones. Encaja bien en login, búsqueda pública, rutas de administración, entrada a generación IA y patrones de bot.
Pero Cloudflare no sustituye las reglas del producto. Cuotas por plan, uso por organización, créditos por usuario, abuso de reembolsos o invitaciones necesitan datos de la aplicación. En la práctica se usan capas:
| Capa | Rol | Ejemplo |
|---|---|---|
| Cloudflare/WAF | Bloquear ráfagas y bots pronto | Limitar /api/login por IP |
| Aplicación | Limitar por usuario, organización, plan y operación | Plan gratis: 10 generaciones/día |
| Queue/worker | Suavizar trabajos caros | Email, imágenes, PDF |
| Billing/monitoring | Detectar coste anómalo | Alertas SMS y LLM |
OWASP API4 trata el consumo ilimitado de CPU, memoria, tamaño de archivo y servicios externos como riesgo de seguridad. OWASP API6 cubre abuso automatizado de flujos de negocio como compras, reservas, posts o referidos. Por eso el rate limiting también es defensa de ingresos: evita agotar free tiers, spam, SMS caros, abuso de inventario y ataques de cuenta.
Errores y trampas habituales
El primer error es un único límite global. Leer un perfil y pedir reset de contraseña no tienen el mismo coste ni el mismo riesgo. Divide por endpoint y operación.
El segundo es no fijar la respuesta 429. Si unas rutas devuelven HTML, otras texto y otras JSON, el cliente será frágil. Estandariza body JSON, Retry-After y headers de límite.
El tercero es contar solo peticiones exitosas. Logins fallidos, payloads inválidos y resets para emails inexistentes también consumen recursos y muestran señales de ataque. Muchas veces los fallos deben limitarse más.
El cuarto es guardar datos personales en las claves. No pongas emails o teléfonos en claro dentro de Redis keys o logs. Usa hash cuando haga falta y TTL corto.
El quinto es hacer que los tests esperen de verdad 60 segundos. Inyecta now en la función y avanza el reloj en tests.
El último es bloquear infraestructura legítima. Bots de búsqueda, checks de uptime, monitorización interna, webhooks de pago y partners pueden necesitar políticas separadas. Las excepciones deben ser estrechas y auditables.
Checklist de revisión con Claude Code
Después de implementar, pide a Claude Code revisar:
- Si todos los 429 tienen la misma estructura JSON
- Si existen
Retry-Aftery headers de remaining - Si la clave para IP, user id, API key y organización es correcta
- Si el comportamiento ante fallo de Redis está definido
- Si se cuentan fallos de auth, validación y APIs externas cuando corresponde
- Si los tests cubren permitido, bloqueado y recuperado
- Si excepciones para admin, monitorización, webhooks y crawlers son demasiado amplias
Esto no es solo calidad de código. En productos con IA, SMS, email o pagos, un hueco de rate limiting se convierte en factura.
CTA de consultoría
ClaudeCodeLab trabaja implementación de APIs, revisión de seguridad, rate limiting, protección de costes y monitorización en Claude Code training and consulting. Con un proyecto Next.js, Express, Cloudflare Workers o AWS API Gateway existente, el trabajo útil es convertir “qué operación, para quién y cuántas veces” en código, tests y logs.
Para un proyecto individual, empieza con el demo Node.js y pasa a Redis cuando despliegues varias instancias. Para equipos, documenta prompt de Claude Code, checklist de revisión, variables de entorno y runbook para que cambiar límites no dependa de memoria tribal.
Probé los ejemplos de esta guía como verificación práctica. El servidor en memoria devolvió 429 tras varias peticiones seguidas; la versión Redis devolvió 429 con Retry-After al superar la ventana de 10 requests; y el cliente con espera dejó de reintentar inmediatamente. La conclusión: un rate limit solo es útil en producción cuando respuesta, reintento, logs y excepciones se prueban juntos.
PDF gratis: cheatsheet de Claude Code
Introduce tu email y descarga una hoja con comandos, hábitos de revisión y flujos seguros.
Cuidamos tus datos y no enviamos spam.
Sobre el autor
Masa
Ingeniero enfocado en workflows prácticos con Claude Code.
Artículos relacionados
Permission receipt para Claude Code: alcance, prueba y rollback
Patrón de permission receipt para Claude Code: acciones permitidas, aprobación, pruebas, rollback y CTA de ingresos.
Agent Harness seguro para Claude Code y Codex: permisos, verificacion y rollback
Diseña un Agent Harness seguro para Claude Code y Codex con permisos, plan, verificaciones y rollback.
Subagentes de Claude Code: guía práctica para delegar trabajo de forma segura
Guía práctica de subagentes en Claude Code para dividir artículos y código: reglas, prompts, riesgos y checklist.