Torna al blog

Risoluzione dei problemiGuida

429 Too Many Requests da un'API AI: retry con backoff

Cosa significa un 429 da un'API AI e come gestirlo: backoff esponenziale con jitter, Retry-After e concorrenza degli agenti, con codice Python e JavaScript.

Di CheapTokens TeamPubblicato il Aggiornato il 6 min di lettura

Diagramma di un ciclo di richieste: una risposta 429, un passo di attesa con ritardo crescente e un retry
In questa pagina
  1. Cosa significa un 429
  2. Leggi gli header prima di ipotizzare un ritardo
  3. Python: backoff con jitter
  4. JavaScript: lo stesso ciclo
  5. Come gli agenti dovrebbero gestire il 429
  6. Esegui i cicli di agente su CheapTokens
  7. Se continua a fallire

Un 429 Too Many Requests da un'API AI significa che la richiesta è stata rifiutata perché hai inviato troppo in troppo poco tempo, oppure hai raggiunto un limite sul tuo account. La soluzione è attendere e riprovare con backoff esponenziale e jitter, usare l'header Retry-After quando la risposta lo contiene, e limitare il numero di richieste che il tuo agente invia in parallelo. Non riprovare un 429 che in realtà è un limite di spesa o di saldo: attendere non lo risolverà.

Questa guida si basa sulle guide ufficiali ai rate limit di OpenAI e Anthropic, entrambe verificate il 22 ottobre 2026. Nessuna delle due pagine mostra una data di ultimo aggiornamento.

Cosa significa un 429#

OpenAI descrive un 429 come una richiesta rifiutata per superamento di un rate limit (rate_limit_error, con il codice slow_down quando il traffico cresce troppo in fretta), oppure perché è stato raggiunto un limite di spesa rigido. Anche Anthropic usa rate_limit_error per il 429. Un 429 da limite di spesa non ha l'header retry-after e continua a fallire finché l'accesso non riprende.

Due casi vicini sono facili da confondere con questo. Il 503 server_is_overloaded di OpenAI e il 529 overloaded_error di Anthropic sono condizioni di sovraccarico, non il tuo rate limit. Di solito vengono gestiti con lo stesso ciclo di retry, ma indicano una causa diversa.

Quindi ci sono tre casi, e ognuno richiede una reazione diversa:

Risposta Causa Cosa fare
429 rate limit Troppe richieste o token al minuto Attendere, riprovare con backoff
429 limite di spesa o saldo Limite dell'account raggiunto Non riprovare; correggere il limite o ricaricare il saldo
503 / 529 overloaded Il lato del provider è occupato Riprovare con backoff, con un tetto

Se il tuo saldo è esaurito, leggi invece l'errore "not enough tokens". Per i guasti a livello di rete come timeout e connessioni rifiutate, vedi errori di connessione comuni.

Leggi gli header prima di ipotizzare un ritardo#

OpenAI documenta questi header sulle risposte con rate limit: Retry-After, e x-ratelimit-limit-requests, x-ratelimit-remaining-requests, x-ratelimit-reset-requests con le varianti -tokens. Altre API possono inviarne un sottoinsieme o nessuno, quindi scrivi il client per usare Retry-After quando è presente e ricadere sul tuo schema quando non lo è.

Le indicazioni di OpenAI sui retry, in breve:

  • Tratta Retry-After come attesa minima e aggiungi un piccolo ritardo casuale in più.
  • In sua assenza, usa il backoff esponenziale con jitter.
  • Limita sia il numero di tentativi sia il tempo totale di retry.
  • Non sovrapporre i tuoi retry ai retry dell'SDK.
  • Non riprovare gli errori di quota o di fatturazione.
  • Le richieste fallite contano comunque nel limite al minuto, quindi un ciclo aggressivo peggiora le cose.
  • Per lo streaming, non ripetere una richiesta dopo averne già consumato l'output.

Python: backoff con jitter#

Il pacchetto ufficiale openai funziona con qualsiasi endpoint compatibile OpenAI. Disattiva i retry dell'SDK (max_retries=0) così non si moltiplicano con i tuoi. L'id del modello proviene dalla documentazione CheapTokens; gli id differiscono tra i provider.

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)

Il limite superiore del ritardo senza header cresce 1, 2, 4, 8 secondi (con tetto a 30), e random.uniform sceglie un valore casuale al di sotto, così molti worker non si risvegliano tutti nello stesso momento. Retry-After può anche essere una data HTTP; il codice sopra gestisce la forma numerica e altrimenti ricade sul backoff.

JavaScript: lo stesso ciclo#

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 funziona, una chiamata con rate limit semplicemente impiega più tempo e poi restituisce un completamento normale. Quando fallisce ancora dopo l'ultimo tentativo, viene sollevato l'errore originale, così il tuo codice può registrarlo o accodare il job per dopo.

Come gli agenti dovrebbero gestire il 429#

Un ciclo di agente invia molte chiamate brevi, e più agenti o chiamate di tool possono essere in esecuzione contemporaneamente. Riprovare da solo non basta; riduci la frequenza delle richieste alla fonte:

  1. Limita la concorrenza. Un semaforo di 3–5 chiamate parallele è un inizio ragionevole; aumentalo solo finché gli errori restano rari.
  2. Condividi un unico limiter. Se più worker usano la stessa chiave, condividono gli stessi limiti, quindi un ciclo di retry per worker non basta: usa una coda o un semaforo per il processo.
  3. Fai backoff globalmente. Quando una chiamata riceve un 429 con un ritardo, metti in pausa l'intero pool per quel tempo, non solo la chiamata fallita.
  4. Mantieni le richieste più piccole. Meno token per richiesta significa meno pressione sui limiti di token al minuto: riduci la cronologia e l'output dei tool. Vedi come stimare il costo di un agente AI per capire dove finiscono i token.
  5. Fermati sugli errori non ritentabili. Un 401, un 400 o un errore di saldo non si risolveranno da soli; fallisci rapidamente e segnala.
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

Esegui i cicli di agente su CheapTokens#

Per i cicli di agente con molte chiamate brevi, CheapTokens offre MiniMax-M2.7: un unico prezzo a qualsiasi ora del giorno, una finestra di contesto da 180K, fino a 8.192 token per risposta e un unico saldo per tutti i modelli. L'output costa $0.15 per 1M di token e, in totale, contando insieme input e output, è più economico di OpenRouter. Funziona tramite l'endpoint compatibile OpenAI https://api.cheaptokens.dev/v1. Il codice sopra punta già a questo endpoint; imposta l'id del modello dalla documentazione CheapTokens ed eseguilo.

Prezzi live per un carico di 500.000 token in input e 100.000 in output:

Modello Costo 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 token di input e 100.000 token di output in totale. I prezzi per 1M di token sono attuali e cambiano insieme alle nostre tariffe; la colonna OpenRouter mostra i prezzi di riferimento per lo stesso carico di lavoro.

Vedi tutti i prezzi, la documentazione di setup, e crea una chiave per inviare la tua prima richiesta.

Se continua a fallire#

  • 429 alla primissima richiesta. Leggi il tipo e il messaggio di errore nel corpo della risposta: un 429 da limite di spesa o dell'account continua a fallire al retry finché il limite non viene alzato o l'accesso non riprende, mentre un 429 da rate limit si risolve dopo una pausa.
  • Il 429 continua ad arrivare con un ciclo lungo. Potresti stare sovrapponendo i retry: i retry dell'SDK moltiplicati per i tuoi. Imposta l'SDK a zero, come negli esempi.
  • Gli errori arrivano a raffica all'inizio di un batch. Aumenta gradualmente invece di lanciare tutti i worker insieme; OpenAI segnala slow_down per il traffico che cresce troppo in fretta.
  • Chiamata in streaming fallita a metà. Non ripeterla alla cieca dopo aver consumato l'output; decidi se ricominciare l'intera risposta o continuare da ciò che hai.
  • Nessun Retry-After nella risposta. Il codice ricade sul backoff con jitter, quindi non serve alcuna modifica.