Rate limiting de APIs com Claude Code: 429, Redis e Cloudflare
Implemente rate limiting de APIs com Claude Code: 429, Redis, Cloudflare, prevenção de abuso e erros comuns.
Rate limiting de API significa definir quantas requisições o mesmo cliente pode enviar em um curto período e pedir que ele espere quando ultrapassar esse limite. Não é desligar o serviço para todos. É impedir que um usuário, bot, script ou integração consuma mais recursos do que deveria.
Claude Code consegue criar endpoints, autenticação e testes muito rápido. O risco é confundir “funciona localmente” com “está pronto para produção”. Tentativas de login, busca, geração com IA, SMS, envio de email e retries de webhook têm custo real. Em um teste de Masa com um pequeno formulário de contato, envios duplicados pareciam apenas um detalhe de UX, até que a cota do provedor de email começou a cair durante o QA. O problema não era só o formulário; faltava definir quantas vezes aquela ação podia ocorrer.
Este guia transforma o assunto em um workflow prático para Claude Code: design antes da implementação, exemplo Node.js sem dependências, implementação Express + Redis, cliente que respeita Retry-After, uso de Cloudflare na borda, prevenção de abuso, falhas concretas e CTA de consultoria. Para contexto, leia também desenvolvimento de APIs em produção com Claude Code, boas práticas de segurança e o guia de Cloudflare Workers.
Use as fontes oficiais como base: Cloudflare Rate limiting rules, OWASP API Security 2023 API4: Unrestricted Resource Consumption e API6: Unrestricted Access to Sensitive Business Flows, além da referência da MDN para 429 Too Many Requests.
Primeiro defina o que proteger
O erro comum é começar pelo número, como “60 requisições por minuto”. Antes disso, identifique o risco. Rate limiting protege servidor, banco de dados, custo de APIs externas, estoque, recuperação de senha, cota de email, créditos de IA, qualidade de leads e regras de negócio.
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"]
Pontos de partida realistas:
| Caso | Chave de limite | Início | O que protege |
|---|---|---|---|
| Login, OTP, reset de senha | IP + conta | 5 tentativas / 10 min | Força bruta, custo SMS |
| Busca e listas | User id + path | 60 / min | Banco, scraping |
| Geração IA ou imagem | User id + plano | 10 por dia no grátis | Custo LLM, free tier |
| Webhook recebido | Emissor + event id | Permitir burst curto | Duplicidade, fila |
Não dependa só do IP. Em empresas, escolas e redes móveis, muitos usuários legítimos podem compartilhar IP. Um atacante também pode rotacionar IPs. Em APIs autenticadas, combine user id, API key, organização, plano, endpoint e tipo de operação.
Dê uma especificação real ao Claude Code
“Adicione rate limiting” é vago demais. Informe algoritmo, estratégia de chave, resposta 429, headers, testes, logs e armazenamento local/produção. Este prompt já é um bom começo:
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.
Com isso, Claude Code sabe o que será considerado pronto. Ele não deve apenas adicionar middleware; deve estabilizar o contrato do cliente. Combine com o guia de testes de API para testar 429 como parte do comportamento oficial.
Exemplo mínimo executável: servidor Node.js com 429
Salve como rate-limit-demo.mjs e execute com Node.js 20 ou superior. O exemplo usa token bucket: o balde recebe tokens em uma taxa fixa e cada requisição consome um token. Isso permite uma pequena rajada, mas controla a média.
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
Em outro terminal:
for i in 1 2 3 4 5 6 7; do
curl -i http://localhost:3000/api/demo
done
No Windows PowerShell:
1..7 | ForEach-Object {
curl.exe -i http://localhost:3000/api/demo
}
A sexta ou sétima chamada deve retornar 429 Too Many Requests. A MDN explica que 429 pode incluir Retry-After, que informa ao cliente quando tentar de novo.
Redis para múltiplas instâncias
A versão em memória é boa para aprender, mas falha quando existem várias instâncias. O servidor A pode achar que não há saldo, enquanto o servidor B ainda acha que há cinco chamadas. Redis centraliza o contador.
Este exemplo usa Express e sorted set do Redis para sliding window. Sliding window conta os últimos 60 segundos a partir de agora, em vez de zerar em uma virada fixa de minuto.
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
Ao pedir uma versão de produção ao Claude Code, defina o comportamento quando Redis falhar. Um formulário de marketing pode falhar aberto por pouco tempo. Login, pagamento e créditos de IA talvez devam falhar fechado. Essa é uma decisão de risco.
O cliente deve respeitar Retry-After
O servidor é só metade. SDKs, batch jobs e emissores de webhook devem ler Retry-After e aguardar.
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, peça: em 429, usar backoff exponencial, priorizar Retry-After quando existir, limitar tentativas e registrar a falha final. Retry infinito vira incidente.
Cloudflare na borda, regras de usuário na aplicação
Cloudflare Rate Limiting Rules é forte para rejeitar picos óbvios antes do origin. A documentação oficial descreve expressões, períodos, thresholds, mitigation timeout e ações. Use em login, busca pública, rotas admin, entrada de geração IA e padrões de bot.
Cloudflare não substitui regras de produto. Cotas de plano, uso por organização, créditos por usuário, abuso de reembolso ou convites exigem dados da aplicação. Na prática:
| Camada | Papel | Exemplo |
|---|---|---|
| Cloudflare/WAF | Bloquear bursts e bots cedo | Limitar /api/login por IP |
| Aplicação | Limitar usuário, organização, plano e operação | Grátis: 10 gerações/dia |
| Queue/worker | Suavizar tarefas caras | Email, imagens, PDF |
| Billing/monitoring | Detectar custo anormal | Alertas SMS e LLM |
OWASP API4 trata consumo ilimitado de CPU, memória, tamanho de arquivo e serviços externos como risco de segurança. OWASP API6 cobre abuso automatizado de fluxos sensíveis, como compra, reserva, postagem e indicação. Portanto rate limiting também protege receita: evita esgotar free tier, spam, revenda, custo de SMS e ataques de conta.
Falhas e armadilhas comuns
A primeira falha é um limite global para tudo. Ler perfil e pedir reset de senha não têm o mesmo risco. Separe por endpoint e operação.
A segunda é não padronizar 429. Se uma rota retorna HTML, outra texto e outra JSON, os clientes ficam frágeis. Padronize body JSON, Retry-After e headers.
A terceira é contar só sucessos. Logins falhos, payload inválido e reset para email inexistente também custam e indicam abuso. Muitas vezes falhas precisam de limites mais rígidos.
A quarta é colocar dados pessoais na chave. Não grave email ou telefone em texto puro em Redis keys ou logs. Faça hash quando necessário e use TTL curto.
A quinta é esperar 60 segundos em testes. Injete now e avance o tempo no teste para não atrasar CI.
A última é bloquear infraestrutura legítima. Search bots, uptime checks, monitoramento interno, webhooks de pagamento e parceiros podem precisar de política separada. Exceções devem ser estreitas e auditáveis.
Checklist de revisão com Claude Code
Depois de implementar, peça ao Claude Code para revisar:
- Todos os 429 têm o mesmo JSON?
Retry-Aftere headers de remaining estão presentes?- A chave para IP, user id, API key e organização está correta?
- O comportamento em falha do Redis está explícito?
- Falhas de auth, validação e API externa são contadas quando necessário?
- Os testes cobrem allowed, blocked e recovered?
- Exceções de admin, monitoring, webhook e crawler estão amplas demais?
Isso não é apenas qualidade de código. Em produtos com IA, SMS, email ou pagamento, falha de rate limiting aparece direto na fatura.
CTA de consultoria
ClaudeCodeLab cobre implementação de APIs, revisão de segurança, rate limiting, proteção de custos e monitoramento em Claude Code training and consulting. Em projetos Next.js, Express, Cloudflare Workers ou AWS API Gateway, o trabalho útil é transformar “qual operação, para quem, quantas vezes” em código, testes e logs.
Para projetos individuais, comece pelo demo Node.js e passe para Redis quando houver múltiplas instâncias. Para times, documente prompt, checklist, variáveis de ambiente e runbook para mudar limites sem improviso.
Testei os exemplos deste artigo: o servidor em memória retornou 429 após chamadas repetidas, a versão Redis retornou 429 com Retry-After ao passar da janela de 10 requests, e o cliente com espera parou de tentar de novo imediatamente. A lição é simples: rate limiting só está pronto para produção quando resposta, retry, logs e exceções são verificados juntos.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Permission receipt no Claude Code: escopo, prova e rollback
Padrão de permission receipt para Claude Code: ações permitidas, limites de aprovação, comandos de prova, rollback e CTA de receita.
Agent Harness seguro para Claude Code e Codex: permissoes, verificacao e rollback
Monte uma base segura para agentes com Claude Code e Codex usando politicas, plano, verificacao e recuperacao.
Subagentes no Claude Code: guia prático para delegar trabalho com segurança
Guia prático de subagentes no Claude Code para dividir artigos e código: regras, prompts, riscos e checklist.