Erro API 429 Too Many Requests: limites, Retry-After e backoff seguro
Guia prático para resolver erros HTTP 429 em APIs de LLM: análise de limites RPM/TPM, leitura de Retry-After e implementação de backoff com jitter.
Conteúdo
Erro API 429 Too Many Requests: limites, Retry-After e backoff seguro
O erro HTTP 429 Too Many Requests ocorre quando o cliente ultrapassa os limites de frequência de requisições ou o volume de tokens estabelecidos pelo provedor de API. Executar tentativas infinitas e sem controle não resolve a falha, mas sim agrava o bloqueio, gerando tempestades de repetição (retry storms).
Para restabelecer a estabilidade do seu sistema, é fundamental identificar com precisão o tipo de limite (RPM, TPM ou saldo esgotado), processar corretamente o cabeçalho Retry-After e aplicar um algoritmo de recuo exponencial com ruído aleatório (Full Jitter Exponential Backoff).
Por que ocorre o HTTP 429: anatomia dos limites
Em APIs modernas de modelos de linguagem (OpenAI, Anthropic e gateways compatíveis), o status 429 é disparado por três mecanismos principais:
- RPM (Requests Per Minute): limite na quantidade de requisições HTTP por minuto. Ocorre com frequência ao executar vários workers paralelos sem filas de controle.
- TPM (Tokens Per Minute): limite no volume acumulado de tokens de entrada e saída em uma janela de um minuto. É comum ao enviar contextos extensos.
- Esgotamento de cota ou saldo: bloqueio decorrente de saldo zerado, limite de gastos atingido ou término do pacote pré-pago.
HTTP/1.1 429 Too Many Requests
Date: Sun, 23 Aug 2026 03:00:00 GMT
Content-Type: application/json
Retry-After: 6
x-ratelimit-limit-requests: 500
x-ratelimit-remaining-requests: 0
x-ratelimit-reset-requests: 6s
x-ratelimit-limit-tokens: 30000
x-ratelimit-remaining-tokens: 1200
x-ratelimit-reset-tokens: 150ms
{
"error": {
"message": "Rate limit reached for model in organization on tokens per minute (TPM). Please try again in 6s.",
"type": "tokens",
"param": null,
"code": "rate_limit_exceeded"
}
}
Quando o erro decorre de falta de créditos ou limites rígidos de assinatura, tentativas automáticas apenas desperdiçam recursos de rede. Para diagnosticar a causa raiz imediatamente e sem decodificar logs complexos, o BetterToken oferece um painel de controle transparente: exibe em tempo real o status HTTP de cada chamada, o detalhamento exato de tokens de entrada, saída e cache, e o saldo no modelo pay-as-you-go sem bloqueios de 5 horas.
Matriz de diagnóstico do erro 429
| Sintoma | Causa principal | O que verificar na resposta | Solução de engenharia |
|---|---|---|---|
| Falha em rajadas paralelas | Limite de RPM excedido | x-ratelimit-remaining-requests: 0 | Limitar concorrência via semáforos ou filas |
| Falha em prompts longos | Limite de TPM excedido | x-ratelimit-remaining-tokens < tamanho do prompt | Otimizar contexto, usar prompt caching ou dividir lotes |
| 100% das chamadas retornam 429 | Saldo / Cota esgotada | Código insufficient_quota ou mensagem de cobrança | Interromper retry e recarregar saldo ou ajustar chave |
| Crescimento em cascata de 429 | Tempestade de retentativas | Cabeçalhos Retry-After ignorados pelos workers | Adicionar ruído aleatório (Jitter) ao cálculo do atraso |
Como interpretar o cabeçalho Retry-After
A especificação RFC 6585 define dois formatos válidos para o cabeçalho Retry-After:
- Segundos relativos (número inteiro ou decimal, por exemplo
Retry-After: 12); - Data HTTP (timestamp GMT padronizado, por exemplo
Retry-After: Sun, 23 Aug 2026 03:05:00 GMT).
import datetime
import email.utils
import time
def parse_retry_after(header_value: str | None, default_delay: float = 1.0) -> float:
if not header_value:
return default_delay
header_value = header_value.strip()
try:
return max(0.0, float(header_value))
except ValueError:
pass
try:
parsed_date = email.utils.parsedate_to_datetime(header_value)
now = datetime.datetime.now(datetime.timezone.utc)
delay = (parsed_date - now).total_seconds()
return max(0.0, delay)
except Exception:
return default_delay
Implementação do Full Jitter Exponential Backoff
Caso o cabeçalho Retry-After não esteja presente, a abordagem padrão é o recuo exponencial com ruído aleatório total (Full Jitter). A fórmula para a tentativa $i$ é:
$$T_{ ext{wait}} = ext{random}(0, \min(T_{ ext{max}}, T_{ ext{base}} imes 2^i))$$
import asyncio
import json
import random
import httpx
class RateLimitRetryClient:
def __init__(
self,
base_url: str = "https://www.bettertoken.ai/v1",
api_key: str = "",
max_retries: int = 4,
base_delay: float = 1.0,
max_delay: float = 32.0,
):
self.base_url = base_url
self.api_key = api_key
self.max_retries = max_retries
self.base_delay = base_delay
self.max_delay = max_delay
self.client = httpx.AsyncClient(
base_url=self.base_url,
headers={"Authorization": f"Bearer {self.api_key}"},
timeout=60.0,
)
async def send_chat_completion(self, payload: dict) -> dict:
for attempt in range(self.max_retries + 1):
try:
response = await self.client.post("/chat/completions", json=payload)
if response.status_code == 200:
return response.json()
if response.status_code == 429:
error_data = response.json().get("error", {})
error_code = error_data.get("code")
if error_code in ("insufficient_quota", "billing_not_active"):
raise RuntimeError(f"Erro de cobrança: {error_data.get('message')}")
if attempt == self.max_retries:
raise RuntimeError(f"Limite de tentativas esgotado (429): {response.text}")
retry_after = response.headers.get("Retry-After")
if retry_after:
wait_time = parse_retry_after(retry_after) + random.uniform(0.1, 0.5)
else:
backoff_cap = min(self.max_delay, self.base_delay * (2 ** attempt))
wait_time = random.uniform(0, backoff_cap)
await asyncio.sleep(wait_time)
continue
response.raise_for_status()
except httpx.RequestError as exc:
if attempt == self.max_retries:
raise
wait_time = min(self.max_delay, self.base_delay * (2 ** attempt))
await asyncio.sleep(wait_time)
raise RuntimeError("A requisição falhou após esgotar o orçamento de tentativas")
Idempotência e segurança em operações com efeitos colaterais
Repetir operações de leitura (GET) é seguro por natureza. No entanto, ao acionar tarefas generativas via POST:
- Evite duplicar execuções de agentes: caso uma conexão caia por timeout, verifique se tokens foram debitados antes de reenviar a tarefa.
- Utilize identificadores únicos de cliente: inclua o cabeçalho
X-Request-IDpara rastrear requisições nos logs. - Não trate erros 401 ou 403 como 429: falhas de autenticação exigem correção de credenciais e não pausas em loop.
Verificação e recuperação do serviço
Antes de restaurar o fluxo completo de requisições:
- Envie uma requisição de teste mínima (
max_tokens: 5). - Confirme o recebimento do código HTTP 200 e inspecione o cabeçalho
x-ratelimit-remaining-requests. - Aumente a concorrência gradualmente enquanto monitora a taxa de erros 429 em suas métricas.
Para evitar interrupções inesperadas por limites de requisições por minuto e manter visibilidade completa sobre o status de cada chamada, acesse a BetterToken API, crie chaves dedicadas e monitore o consumo de tokens em tempo real no Dashboard.