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.

In questa pagina
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_ di OpenAI e il 529 overloaded_ 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:
Scorri la tabella verso destra →
| 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-, x-, x- 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-Aftercome 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.
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#
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:
- Limita la concorrenza. Un semaforo di 3–5 chiamate parallele è un inizio ragionevole; aumentalo solo finché gli errori restano rari.
- 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.
- 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.
- 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.
- Fermati sugli errori non ritentabili. Un 401, un 400 o un errore di saldo non si risolveranno da soli; fallisci rapidamente e segnala.
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 aboveEsegui 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:/. 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 | Input $/1M | Output $/1M | Costo | OpenRouter |
|---|---|---|---|---|
| GLM-5.3-Flash | $0.02 | $0.04 | $0.014 | $0.0282 |
| DeepSeek-V4-Flash | $0.015 | $0.10 | $0.0175 | $0.0394 |
| MiniMax-M2.7 | $0.05 | $0.15 | $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_downper 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.



