Voltar ao blog

Solução de problemasGuia

429 Too Many Requests de uma API de IA: retries com backoff

O que um 429 de uma API de IA significa e como tratá-lo: backoff exponencial com jitter, Retry-After e concorrência de agentes, com código em Python e JavaScript.

Por CheapTokens TeamPublicado em Atualizado em 6 min de leitura

Diagrama de um loop de requisições: uma resposta 429, uma etapa de espera com atraso crescente e uma nova tentativa
Nesta página
  1. O que um 429 significa
  2. Leia os cabeçalhos antes de chutar um atraso
  3. Python: backoff com jitter
  4. JavaScript: o mesmo loop
  5. Como agentes devem tratar 429
  6. Rode loops de agente no CheapTokens
  7. Se ainda falhar

Um 429 Too Many Requests de uma API de IA significa que a requisição foi rejeitada porque você enviou demais rápido demais, ou atingiu um limite na sua conta. A solução é esperar e tentar novamente com backoff exponencial e jitter, usar o cabeçalho Retry-After quando a resposta tiver um, e limitar quantas requisições seu agente envia em paralelo. Não repita um 429 que na verdade é um limite de gasto ou de saldo: esperar não vai resolver.

Este guia é baseado nos guias oficiais de rate limit da OpenAI e da Anthropic, ambos verificados em 22 de outubro de 2026. Nenhuma das páginas mostra data de última atualização.

O que um 429 significa#

A OpenAI descreve um 429 como uma requisição rejeitada por exceder um rate limit (rate_limit_error, com o código slow_down quando o tráfego cresce rápido demais), ou porque um limite rígido de gasto foi atingido. A Anthropic também usa rate_limit_error para 429. Um 429 de limite de gasto não tem cabeçalho retry-after e continua falhando até o acesso ser retomado.

Dois vizinhos são fáceis de confundir com ele. O 503 server_is_overloaded da OpenAI e o 529 overloaded_error da Anthropic são condições de sobrecarga, não o seu rate limit. Normalmente são tratados com o mesmo loop de retry, mas dizem algo diferente sobre a causa.

Então há três casos, e cada um exige uma reação diferente:

Resposta Causa O que fazer
429 rate limit Requisições ou tokens por minuto em excesso Esperar, repetir com backoff
429 limite de gasto ou saldo Limite da conta atingido Não repetir; ajustar o limite ou recarregar o saldo
503 / 529 overloaded O lado do provedor está ocupado Repetir com backoff, com um teto

Se seu saldo acabou, leia o erro "not enough tokens". Para falhas em nível de rede, como timeouts e conexões recusadas, veja erros de conexão comuns.

Leia os cabeçalhos antes de chutar um atraso#

A OpenAI documenta estes cabeçalhos em respostas com rate limit: Retry-After, e x-ratelimit-limit-requests, x-ratelimit-remaining-requests, x-ratelimit-reset-requests com variantes -tokens. Outras APIs podem enviar um subconjunto ou nenhum deles, então escreva o cliente para usar Retry-After quando estiver presente e recorrer ao seu próprio cronograma quando não estiver.

A orientação da OpenAI sobre retries, em resumo:

  • Trate Retry-After como a espera mínima e adicione um pequeno atraso aleatório por cima.
  • Sem ele, use backoff exponencial com jitter.
  • Limite tanto o número de tentativas quanto o tempo total de retry.
  • Não empilhe seus próprios retries em cima dos retries do SDK.
  • Não repita erros de cota ou de cobrança.
  • Requisições com falha ainda contam para o limite por minuto, então um loop agressivo piora as coisas.
  • Para streaming, não reenvie uma requisição depois de já ter consumido saída dela.

Python: backoff com jitter#

O pacote oficial openai funciona com qualquer endpoint compatível com OpenAI. Desative os retries do próprio SDK (max_retries=0) para que não se multipliquem com os seus. O id do modelo vem da documentação do CheapTokens; os ids diferem entre provedores.

python
import os
import random
import time

from openai import OpenAI, APIStatusError

client = OpenAI(
    base_url="https://api.cheaptokens.dev/v1",
    api_key=os.environ["CHEAPTOKENS_API_KEY"],
    max_retries=0,  # we retry ourselves
)

RETRYABLE = {429, 500, 502, 503, 504}


def chat_with_retry(model, messages, max_attempts=5, max_total=60.0):
    started = time.monotonic()
    for attempt in range(max_attempts):
        try:
            return client.chat.completions.create(model=model, messages=messages)
        except APIStatusError as err:
            last = attempt == max_attempts - 1
            if err.status_code not in RETRYABLE or last:
                raise
            header = err.response.headers.get("retry-after")
            if header and header.replace(".", "", 1).isdigit():
                delay = float(header) + random.uniform(0, 1)  # minimum wait + jitter
            else:
                delay = random.uniform(0, min(30.0, 2.0 ** attempt))  # full jitter
            if time.monotonic() - started + delay > max_total:
                raise
            time.sleep(delay)

O limite superior do atraso sem cabeçalho cresce 1, 2, 4, 8 segundos (limitado a 30), e random.uniform escolhe um valor aleatório abaixo dele, para que muitos workers não acordem todos no mesmo momento. Retry-After também pode ser uma data HTTP; o código acima trata a forma numérica e, caso contrário, recorre ao backoff.

JavaScript: o mesmo loop#

javascript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.cheaptokens.dev/v1",
  apiKey: process.env.CHEAPTOKENS_API_KEY,
  maxRetries: 0, // we retry ourselves
});

const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

export async function chatWithRetry(model, messages, maxAttempts = 5, maxTotalMs = 60000) {
  const started = Date.now();
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await client.chat.completions.create({ model, messages });
    } catch (err) {
      const last = attempt === maxAttempts - 1;
      if (!RETRYABLE.has(err.status) || last) throw err;
      const header = Number(err.headers?.get?.("retry-after"));
      const delayMs = Number.isFinite(header) && header > 0
        ? header * 1000 + Math.random() * 1000 // minimum wait + jitter
        : Math.random() * Math.min(30000, 2 ** attempt * 1000); // full jitter
      if (Date.now() - started + delayMs > maxTotalMs) throw err;
      await sleep(delayMs);
    }
  }
}

Quando funciona, uma chamada com rate limit simplesmente demora mais e depois retorna uma completion normal. Quando ainda falha após a última tentativa, o erro original é lançado, para que seu código possa registrá-lo ou enfileirar o job para depois.

Como agentes devem tratar 429#

Um loop de agente envia muitas chamadas curtas, e vários agentes ou chamadas de ferramentas podem rodar ao mesmo tempo. Repetir sozinho não basta; reduza a taxa de requisições na origem:

  1. Limite a concorrência. Um semáforo de 3–5 chamadas paralelas é um começo sensato; aumente só enquanto os erros permanecerem raros.
  2. Compartilhe um único limitador. Se vários workers usam a mesma chave, eles compartilham os mesmos limites, então um loop de retry por worker não basta: use uma fila ou um semáforo para o processo.
  3. Faça backoff global. Quando uma chamada recebe um 429 com atraso, pause o pool inteiro por esse tempo, não apenas a chamada que falhou.
  4. Mantenha as requisições menores. Menos tokens por requisição significa menos pressão sobre os limites de tokens por minuto: corte histórico e saída de ferramentas. Veja como estimar o custo de um agente de IA para saber para onde vão os tokens.
  5. Pare em erros não repetíveis. Um 401, 400 ou um erro de saldo não se resolve sozinho; falhe rápido e reporte.
python
import asyncio

limit = asyncio.Semaphore(4)  # at most 4 requests in flight


async def run(job):
    async with limit:
        return await call_model(job)  # your async call with the retry loop above

Rode loops de agente no CheapTokens#

Para loops de agente com muitas chamadas curtas, o CheapTokens oferece o MiniMax-M2.7: um preço a qualquer hora do dia, contexto de 180K, até 8.192 tokens por resposta e um saldo para todos os modelos. A saída custa $0.15 por 1M de tokens e, no total, com entrada e saída contadas juntas, é mais barato que o OpenRouter. Funciona pelo endpoint compatível com OpenAI https://api.cheaptokens.dev/v1. O código acima já aponta para esse endpoint; defina o id do modelo a partir da documentação do CheapTokens e execute.

Preços ao vivo para uma carga de 500.000 de entrada e 100.000 de saída:

Modelo Custo OpenRouter
GLM-5.3-Flash $0.014 $0.0282
DeepSeek-V4-Flash $0.0175 $0.0394
MiniMax-M2.7 $0.04 $0.27

500.000 tokens de entrada e 100.000 tokens de saída no total. Os preços por 1M de tokens são atuais e mudam junto com nossas tarifas; a coluna OpenRouter mostra preços de referência para a mesma carga de trabalho.

Veja todos os preços, a documentação de configuração, e crie uma chave para enviar sua primeira requisição.

Se ainda falhar#

  • 429 na primeira requisição. Leia o tipo e a mensagem de erro no corpo da resposta: um 429 de gasto ou limite de conta continua falhando no retry até o limite ser aumentado ou o acesso ser retomado, enquanto um 429 de rate limit se resolve após uma pausa.
  • 429 continua aparecendo com um loop longo. Você pode estar empilhando retries: retries do SDK vezes seus retries. Defina o SDK para zero, como nos exemplos.
  • Erros vêm em rajadas no início de um lote. Aumente gradualmente em vez de disparar todos os workers de uma vez; a OpenAI reporta slow_down para tráfego que cresce rápido demais.
  • Chamada de streaming falhou no meio. Não a reenvie às cegas depois de consumir saída; decida se reinicia a resposta inteira ou continua a partir do que já tem.
  • Sem Retry-After na resposta. O código recorre ao backoff com jitter, então nenhuma mudança é necessária.