HTTP 429 Too Many Requests: O que Desenvolvedores Perdem sobre Rate Limits
Seu deploy sai sexta às 16h. O monitoramento fica vermelho em três dashboards. Usuários estão vendo erros 429 no checkout, no login, em todas as chamadas de API que importam. Seu rate limiter está rejeitando requisições — ou pior, uma API de terceiros está rejeitando você, e sua lógica de retry está piorando tudo. O código de status HTTP 429 Too Many Requests acabou de se tornar a única coisa entre você e um fim de semana que funciona. A maioria dos desenvolvedores sabe que 429 significa 'diminua a velocidade'. O que eles perdem é quais requisições retentar, quando fazer backoff exponencial, e como definir headers Retry-After que realmente evitam a espiral de morte. Aqui está o que realmente está acontecendo quando sua API começa a rejeitar tráfego.
Eu estive dos dois lados disso. Fui o desenvolvedor que acidentalmente fez DDoS em uma API de CMS por causa de um processo de build mal configurado, e fui aquele implementando rate limiting para proteger nossos próprios servidores de clientes descontrolados. Ambas as experiências me ensinaram coisas que a documentação não cobre. Vamos passar por tudo isso.
O que HTTP 429 Realmente Significa?
HTTP 429 é definido em RFC 6585, publicado em 2012. A especificação é surpreendentemente curta. Aqui está a essência: o usuário (ou cliente) enviou muitas requisições em uma determinada quantidade de tempo.
É isso. É uma resposta de rate limiting. O servidor está dizendo: "Entendi sua requisição, provavelmente é válida, mas você precisa diminuir a velocidade."
Isso é diferente de um 403 Forbidden (você não tem permissão) ou um 503 Service Unavailable (o servidor inteiro está com dificuldades). Um 429 é direcionado. É sobre sua taxa de requisição especificamente.
A resposta DEVE incluir um header Retry-After informando ao cliente quanto tempo esperar antes de tentar novamente. Eu disse "deve" porque muitas APIs não se incomodam, o que torna a vida de todos mais difícil.
Onde Você Verá 429s no Mundo Real
- APIs de terceiros: Stripe, OpenAI, GitHub, Contentful, Sanity — todos têm rate limits
- CDNs e plataformas de hospedagem: Vercel, Cloudflare e AWS retornarão 429s se você bater seus edge rate limits
- Suas próprias APIs: Se você implementou rate limiting (e deveria ter feito)
- Processos de build: Geração de site estático que bate em uma API de CMS para cada página pode facilmente disparar rate limits
- Web scraping: Se você está buscando dados de fontes externas agressivamente
Causas Comuns de Erros 429
Vou dividir os cenários que realmente encontrei em produção, classificados aproximadamente por frequência.
1. Builds de Sites Estáticos Martelando um Headless CMS
Este é o que mais afeta equipes trabalhando com arquiteturas headless. Você tem um site com 2.000 páginas, cada uma precisando de dados do seu CMS. Seu processo de build dispara todas essas requisições em paralelo, o CMS vê um pico massivo, e começa a retornar 429s. Seu build falha.
Vemos isso regularmente quando trabalhando em projetos de headless CMS. O fix envolve enfileiramento de requisições e limites de concorrência, que cobrirei abaixo.
2. Cache Ausente ou Quebrado
Se cada carregamento de página dispara uma chamada de API fresca porque sua camada de cache não está funcionando, você baterá em rate limits rápido — especialmente com picos de tráfego. Uma vez debuguei um app Next.js onde revalidate estava acidentalmente definido como 0, significando que ISR estava efetivamente desabilitado. Cada visitante acionava uma nova chamada de API para Contentful. Levou cerca de 45 minutos de tráfego real para começar a receber 429s.
3. Retry Loops Sem Backoff
Seu código recebe um erro, tenta novamente imediatamente, recebe outro erro, tenta novamente imediatamente... parabéns, você construiu uma máquina acionadora de rate-limit. Eu já vi esse padrão em manipuladores de webhook, jobs em background, e até em chamadas fetch do lado do cliente.
4. Múltiplos Serviços Compartilhando uma Chave de API
Seu ambiente de staging, seu ambiente de produção, sua configuração local de dev, e seu pipeline de CI/CD estão todos usando a mesma chave de API. Cada um parece estar bem individualmente, mas coletivamente estão queimando seu orçamento de rate limit.
5. Fetch do Lado do Cliente Sem Debouncing
Um recurso search-as-you-type que dispara uma chamada de API a cada keystroke. Um dashboard que faz polling a cada 500ms. Um infinite scroll que aciona fetches mais rápido do que o usuário pode scrollar. Esses padrões podem absolutamente disparar 429s, especialmente quando multiplicados em todos seus usuários.
6. Abuso ou Ataque Real
Às vezes um 429 está fazendo exatamente o que deveria — protegendo seu servidor de alguém enviando uma quantidade irrazoável de requisições. Bots, credential stuffing, scraping — rate limiting é sua primeira linha de defesa.
O Header Retry-After Explicado
O header Retry-After é a forma do servidor de dizer exatamente quando tentar novamente. Pode vir em dois formatos:
Segundos a esperar:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Data/hora específica:
HTTP/1.1 429 Too Many Requests
Retry-After: Thu, 01 Jan 2026 00:00:00 GMT
O formato de segundos é muito mais comum. O formato de data usa HTTP-date como definido em RFC 7231.
Aqui está o que a maioria dos tutoriais não dirá: muitas APIs não enviam Retry-After, ou o enviam inconsistentemente. A API do OpenAI geralmente inclui. A API do GitHub inclui junto com X-RateLimit-Reset. Muitas APIs menores apenas enviam um 429 nu e deixam você adivinhar.
Algumas APIs também enviam headers de rate limit adicionais:
| Header | Propósito | Exemplo |
|---|---|---|
X-RateLimit-Limit |
Máximo de requisições permitidas por janela | 100 |
X-RateLimit-Remaining |
Requisições restantes na janela atual | 0 |
X-RateLimit-Reset |
Timestamp Unix quando a janela reseta | 1735689600 |
Retry-After |
Segundos a esperar antes de retentar | 30 |
Sempre procure por esses headers. Eles deixam você implementar lógica de retry mais inteligente e até desacelerar proativamente antes de bater no limite.

Como Lidar com Erros 429 como Cliente
Quando você está recebendo erros 429, aqui está como lidar com eles adequadamente.
Exponential Backoff com Jitter
Este é o padrão ouro. Não apenas espere uma quantidade fixa de tempo — aumente o atraso exponencialmente a cada retry, e adicione alguma aleatoriedade (jitter) para evitar problemas de rebanho trovejante.
async function fetchWithRetry(
url: string,
options: RequestInit = {},
maxRetries: number = 5
): Promise<Response> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
if (attempt === maxRetries) {
throw new Error(`Still getting 429 after ${maxRetries} retries`);
}
// Check for Retry-After header first
const retryAfter = response.headers.get('Retry-After');
let delay: number;
if (retryAfter) {
// Could be seconds or a date
const parsed = parseInt(retryAfter, 10);
if (!isNaN(parsed)) {
delay = parsed * 1000;
} else {
delay = new Date(retryAfter).getTime() - Date.now();
}
} else {
// Exponential backoff with jitter
const baseDelay = Math.pow(2, attempt) * 1000;
const jitter = Math.random() * 1000;
delay = baseDelay + jitter;
}
console.log(`Rate limited. Waiting ${Math.round(delay / 1000)}s before retry ${attempt + 1}`);
await new Promise(resolve => setTimeout(resolve, delay));
}
// TypeScript wants this, though we'll never reach it
throw new Error('Unexpected end of retry loop');
}
Request Queuing para Processos de Build
Para geração de site estático onde você precisa fazer centenas ou milhares de chamadas de API, use uma fila com controle de concorrência:
import pLimit from 'p-limit';
// Limit to 5 concurrent requests
const limit = pLimit(5);
const pages = await getAllPageSlugs(); // Returns ['/', '/about', '/blog/post-1', ...]
const results = await Promise.all(
pages.map(slug =>
limit(() => fetchWithRetry(`https://api.cms.com/pages/${slug}`))
)
);
A biblioteca p-limit (2.5M+ downloads semanais no npm em 2026) é minha preferência para isso. Você também pode adicionar um atraso entre requisições:
const limit = pLimit(3);
const delay = (ms: number) => new Promise(r => setTimeout(r, ms));
const results = await Promise.all(
pages.map((slug, i) =>
limit(async () => {
if (i > 0) await delay(200); // 200ms between requests
return fetchWithRetry(`https://api.cms.com/pages/${slug}`);
})
)
);
Implementando Rate Limiting em Rotas de API Next.js
Agora vamos virar para o outro lado — você está construindo uma API e precisa protegê-la. Se você está construindo com Next.js, aqui está como adicionar rate limiting às suas rotas de API.
Rate Limiter Simples em Memória
Para uma implantação de servidor único ou durante o desenvolvimento, isto funciona:
// lib/rate-limit.ts
type RateLimitEntry = {
count: number;
resetTime: number;
};
const rateLimitMap = new Map<string, RateLimitEntry>();
export function rateLimit({
windowMs = 60 * 1000,
maxRequests = 100,
}: {
windowMs?: number;
maxRequests?: number;
} = {}) {
return function check(identifier: string): {
allowed: boolean;
remaining: number;
resetIn: number;
} {
const now = Date.now();
const entry = rateLimitMap.get(identifier);
if (!entry || now > entry.resetTime) {
rateLimitMap.set(identifier, {
count: 1,
resetTime: now + windowMs,
});
return { allowed: true, remaining: maxRequests - 1, resetIn: windowMs };
}
if (entry.count >= maxRequests) {
return {
allowed: false,
remaining: 0,
resetIn: entry.resetTime - now,
};
}
entry.count++;
return {
allowed: true,
remaining: maxRequests - entry.count,
resetIn: entry.resetTime - now,
};
};
}
Usando em uma rota de API do App Router do Next.js:
// app/api/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { rateLimit } from '@/lib/rate-limit';
const limiter = rateLimit({ windowMs: 60_000, maxRequests: 30 });
export async function GET(request: NextRequest) {
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous';
const { allowed, remaining, resetIn } = limiter(ip);
if (!allowed) {
return NextResponse.json(
{ error: 'Too many requests. Please slow down.' },
{
status: 429,
headers: {
'Retry-After': String(Math.ceil(resetIn / 1000)),
'X-RateLimit-Limit': '30',
'X-RateLimit-Remaining': '0',
},
}
);
}
// Your actual route logic here
return NextResponse.json(
{ data: 'Here you go' },
{
headers: {
'X-RateLimit-Limit': '30',
'X-RateLimit-Remaining': String(remaining),
},
}
);
}
Rate Limiting em Produção com Upstash Redis
A abordagem em memória quebra quando você está rodando em plataformas serverless como Vercel, porque cada invocação de função pode bater em uma instância diferente. Você precisa de um armazenamento compartilhado. Upstash Redis é a escolha mais popular para isso em 2026.
npm install @upstash/ratelimit @upstash/redis
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
export const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(30, '60 s'),
analytics: true,
prefix: 'api-ratelimit',
});
// app/api/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';
export async function GET(request: NextRequest) {
const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success, limit, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
const retryAfter = Math.ceil((reset - Date.now()) / 1000);
return NextResponse.json(
{ error: 'Rate limit exceeded' },
{
status: 429,
headers: {
'Retry-After': String(retryAfter),
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': '0',
'X-RateLimit-Reset': String(reset),
},
}
);
}
return NextResponse.json({ data: 'Success' }, {
headers: {
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': String(remaining),
},
});
}
O tier gratuito do Upstash oferece 10.000 requisições/dia, o que é bastante para projetos pequenos. Seu plano Pro começa em $10/mês para 500K comandos diários a partir de início de 2026.
Rate Limiting no Nível de Middleware
Se você quer rate limiting em todas suas rotas de API, o middleware do Next.js é o lugar:
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';
export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/api/')) {
const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success, reset } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: 'Too many requests' },
{
status: 429,
headers: {
'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
},
}
);
}
}
return NextResponse.next();
}
export const config = {
matcher: '/api/:path*',
};
Estratégias de Rate Limiting Comparadas
Nem todos os algoritmos de rate limiting são iguais. Aqui está como os principais se comparam:
| Algoritmo | Como Funciona | Prós | Contras | Melhor Para |
|---|---|---|---|---|
| Fixed Window | Conta requisições em janelas de tempo fixas (ex: por minuto) | Simples de implementar | Burst no limite da janela pode permitir 2x do limite | APIs simples, ferramentas internas |
| Sliding Window | Conta requisições em um período de tempo contínuo | Distribuição mais suave | Um pouco mais complexo, mais memória | Maioria das APIs em produção |
| Token Bucket | Tokens são reabastecidos a uma taxa constante, cada requisição custa um token | Permite bursts controlados | Gerenciamento de estado mais complexo | APIs que precisam tolerância a burst |
| Leaky Bucket | Requisições entram em uma fila e são processadas a uma taxa fixa | Taxa de saída muito suave | Pode adicionar latência, requisições podem ser descartadas | Entrega de webhook, processamento de job |
| Sliding Window Log | Armazena timestamp de cada requisição | Mais preciso | Alto uso de memória em escala | Necessidades de baixo volume e alta precisão |
Para a maioria das aplicações web, sliding window é o ponto ideal. É o que o Upstash usa por padrão, e é o que eu recomendaria a menos que você tenha uma razão específica para escolher outra coisa.
Rate Limiting em Astro e Outros Frameworks
Se você está construindo com Astro, rate limiting funciona diferentemente porque Astro é principalmente um framework estático-first. Mas com endpoints de servidor do Astro (disponíveis em modo SSR), os conceitos são os mesmos:
// src/pages/api/data.ts
import type { APIRoute } from 'astro';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(30, '60 s'),
});
export const GET: APIRoute = async ({ request }) => {
const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success, reset } = await ratelimit.limit(ip);
if (!success) {
return new Response(JSON.stringify({ error: 'Rate limit exceeded' }), {
status: 429,
headers: {
'Content-Type': 'application/json',
'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
},
});
}
return new Response(JSON.stringify({ data: 'Hello' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
};
Para aplicações implantadas na edge em Cloudflare Workers, você também pode considerar as regras de Rate Limiting integradas do Cloudflare, que operam no nível de infraestrutura e podem lidar com muito mais tráfego do que soluções no nível de aplicação. Seu Advanced Rate Limiting começa em $0.05 por 10.000 requisições boas no plano Business.
Monitoramento e Debug de Erros 429 em Produção
Você não pode corrigir o que não consegue ver. Aqui está meu checklist para lidar com erros 429 em produção:
Quando Você Está Recebendo 429s
- Verifique qual API está retornando 429 — Procure pela URL de resposta, não apenas pelo código de status
- Registre o header
Retry-After— Se for consistentemente muito longo, você pode precisar de um plano de tier mais alto - Audit seus padrões de requisição — Você está fazendo chamadas redundantes? Pode agrupar requisições?
- Implemente caching — Use
stale-while-revalidate, caching Redis, ou Next.js ISR para reduzir chamadas de API - Verifique se múltiplos ambientes compartilham chaves de API — Esta é a causa de 429 "mistério" mais comum
Quando Você Está Enviando 429s
- Configure dashboards — Rastreie taxas de resposta 429 ao longo do tempo
- Identifique os principais infratores — Quais endereços IP ou chaves de API estão batendo em limites mais?
- Revise seus limites — Eles são muito restritivos? Muito soltos? Verifique sua capacidade de servidor e ajuste
- Sempre envie
Retry-After— Seja um bom cidadão de API - Inclua uma mensagem de erro útil — Diga ao cliente qual limite ele bateu e quando retentar
Uma resposta 429 bem elaborada parece assim:
{
"error": {
"type": "rate_limit_exceeded",
"message": "You've exceeded 30 requests per minute. Please wait before retrying.",
"retryAfter": 42,
"documentation": "https://docs.yourapi.com/rate-limits"
}
}
Isso é infinitamente mais útil do que apenas { "error": "Too many requests" }.
Se você está lidando com problemas persistentes de rate limiting em uma arquitetura headless — seja durante builds, em runtime, ou ambos — pode valer a pena entrar em contato para discutir sua arquitetura. Vimos muitos desses problemas em diferentes combinações de CMS e framework e geralmente há um fix no nível de padrão em vez de apenas colocar band-aids nos sintomas.
FAQ
O que significa HTTP 429 Too Many Requests?
HTTP 429 é um código de status que significa que você enviou muitas requisições para um servidor em um determinado período de tempo. O servidor está fazendo rate limiting você — está pedindo que você diminua a velocidade. Não é um erro de autenticação ou um erro de servidor; suas requisições provavelmente são válidas, há apenas muitas delas. O servidor deve incluir um header Retry-After informando quando tentar novamente.
Como faço para corrigir um erro 429?
Se você está recebendo erros 429 de uma API, implemente exponential backoff com jitter em sua lógica de retry, reduza sua frequência de requisição, adicione caching para evitar chamadas redundantes, e respeite o header Retry-After. Se você está batendo no limite durante builds, use request queuing com limites de concorrência. Se está acontecendo consistentemente, você pode precisar fazer upgrade para um plano de API de tier mais alto com rate limits mais generosos.
O que é o header Retry-After?
O header Retry-After é enviado com uma resposta 429 (ou 503) para dizer ao cliente quanto tempo esperar antes de fazer outra requisição. Pode ser especificado como um número de segundos (ex: Retry-After: 60) ou como uma data HTTP (ex: Retry-After: Thu, 01 Jan 2026 00:00:00 GMT). Nem todas as APIs incluem este header, mas as bem projetadas incluem.
Como implemento rate limiting em Next.js?
Para desenvolvimento ou implantações de servidor único, você pode usar um Map em memória para rastrear contagens de requisição por endereço IP. Para implantações serverless de produção em plataformas como Vercel, use Upstash Redis com o pacote @upstash/ratelimit. Você pode aplicar rate limiting no nível de rota individual ou em todas as rotas de API usando middleware do Next.js.
Qual é a diferença entre erros 429 e 503?
Um 429 Too Many Requests é especificamente sobre rate limiting — seu cliente está enviando muitas requisições. Um 503 Service Unavailable significa que o servidor está sobrecarregado ou em manutenção e não consegue lidar com nenhuma requisição de ninguém. Ambos podem incluir um header Retry-After, mas indicam problemas muito diferentes. Um 429 é direcionado a você; um 503 afeta todos.
Rate limiting pode prevenir ataques DDoS?
Rate limiting é uma camada de defesa contra ataques DDoS, mas não é suficiente por si só. Rate limiting no nível de aplicação (como o que você implementaria em Next.js) pode lidar com abuso moderado, mas um ataque DDoS sério precisa ser mitigado no nível de infraestrutura — usando serviços como Cloudflare, AWS Shield, ou as proteções integradas de seu provedor de hospedagem. Pense em rate limiting no nível de app como um segurança, e proteção no nível de infraestrutura como as paredes da fortaleza.
Qual rate limit devo definir para minha API?
Depende completamente de seu caso de uso. Um ponto de partida comum para APIs públicas é 60 requisições por minuto por IP, ou 1.000 requisições por hora por chave de API. Para usuários autenticados, você pode permitir mais. A chave é monitorar padrões de uso real, definir limites que acomodem uso legítimo com alguma margem, e ajustar com base em dados reais. Comece mais restritivo e afrouxe — é mais fácil do que apertar limites depois que usuários dependem de taxas mais altas.
Por que estou recebendo erros 429 durante meu build de site estático?
Geradores de site estático como Next.js e Astro buscam dados para cada página em tempo de build. Se você tem centenas ou milhares de páginas, isso significa centenas ou milhares de chamadas de API em rápida sucessão. A maioria das APIs de CMS tem rate limits entre 5-20 requisições por segundo. Use p-limit ou bibliotecas similares para capping concorrência em 3-5 requisições simultâneas, adicione pequenos atrasos entre lotes, e considere usar builds incrementais (ISR em Next.js, ou Astro's incremental content collections) para evitar recompilar tudo de uma vez.