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.

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:

  1. 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.
  2. 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.
  3. 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

SintomaCausa principalO que verificar na respostaSolução de engenharia
Falha em rajadas paralelasLimite de RPM excedidox-ratelimit-remaining-requests: 0Limitar concorrência via semáforos ou filas
Falha em prompts longosLimite de TPM excedidox-ratelimit-remaining-tokens < tamanho do promptOtimizar contexto, usar prompt caching ou dividir lotes
100% das chamadas retornam 429Saldo / Cota esgotadaCódigo insufficient_quota ou mensagem de cobrançaInterromper retry e recarregar saldo ou ajustar chave
Crescimento em cascata de 429Tempestade de retentativasCabeçalhos Retry-After ignorados pelos workersAdicionar 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 ii é:

Textwait=extrandom(0,min(Textmax,Textbaseimes2i))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:

  1. Evite duplicar execuções de agentes: caso uma conexão caia por timeout, verifique se tokens foram debitados antes de reenviar a tarefa.
  2. Utilize identificadores únicos de cliente: inclua o cabeçalho X-Request-ID para rastrear requisições nos logs.
  3. 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.

Quer otimizar seu fluxo de trabalho com LLMs?

Conecte modelos por uma única API, gerencie chaves e controle os gastos com IA.