API-Fehler 429 Too Many Requests: Limits, Retry-After und sicheres Backoff
Praktischer Leitfaden zur Behebung von HTTP-429-Fehlern in LLM-APIs: Analyse von RPM/TPM-Limits, Auswertung von Retry-After und Implementierung von Backoff mit Jitter.
Inhalt
API-Fehler 429 Too Many Requests: Limits, Retry-After und sicheres Backoff
Der Fehler HTTP 429 Too Many Requests tritt auf, wenn ein Client die vom API-Anbieter festgelegten Frequenzlimits oder Token-Kontingente überschreitet. Unkontrollierte, endlose Wiederholungsversuche beheben das Problem nicht, sondern verschärfen die Blockade durch sogenannte Retry-Stürme (Retry Storms).
Um die Systemstabilität wiederherzustellen, müssen Entwickler die Art der Begrenzung (RPM, TPM oder Guthabenerschöpfung) exakt bestimmen, den Retry-After-Header korrekt parsen und einen exponentiellen Backoff-Algorithmus mit zufälligem Jitter implementieren.
Ursachen für HTTP 429: Anatomie der Limits
Bei modernen LLM-APIs (OpenAI, Anthropic und kompatiblen Schnittstellen) wird der Statuscode 429 durch drei verschiedene Mechanismen ausgelöst:
- RPM (Requests Per Minute): Begrenzung der HTTP-Aufrufe pro Minute. Tritt häufig auf, wenn parallele Worker ohne Warteschlangensteuerung agieren.
- TPM (Tokens Per Minute): Begrenzung des gesamten Token-Volumens (Input und Output) in einem gleitenden Minutenfenster.
- Erschöpfung von Kontingent oder Guthaben: Blockierung durch Nullguthaben, Erreichen von Ausgabenlimits oder Ablauf von Prepaid-Paketen.
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"
}
}
Wenn der Fehler auf fehlendes Guthaben oder starre Abonnementlimits zurückzuführen ist, belasten automatische Wiederholungsversuche nur das Netzwerk. Um die Fehlerursache sofort zu isolieren, bietet BetterToken ein transparentes Dashboard: Es zeigt in Echtzeit HTTP-Statuscodes, die detaillierte Aufteilung von Input-, Output- und Cache-Tokens sowie das Restguthaben im Pay-as-you-go-Modell ohne 5-stündige Sperrfristen.
Diagnosematrix für Fehler 429
| Symptom | Ursache | Prüfung in der Serverantwort | Technische Lösung |
|---|---|---|---|
| Fehler bei parallelen Lastspitzen | RPM-Limit überschritten | x-ratelimit-remaining-requests: 0 | Parallelität über Semaphore oder Warteschlangen drosseln |
| Fehler bei langen Prompts | TPM-Limit überschritten | x-ratelimit-remaining-tokens < Promptgröße | Kontext optimieren, Prompt Caching nutzen oder Batches teilen |
| 100 % der Aufrufe liefern 429 | Guthaben / Kontingent erschöpft | Code insufficient_quota oder Abrechnungsmeldung | Retries stoppen, Guthaben aufladen oder API-Key prüfen |
| Lawinenartiger Anstieg von 429 | Retry-Sturm | Retry-After-Header von Workern ignoriert | Zufälligen Jitter zur Wartezeitberechnung hinzufügen |
Korrektes Parsen des Retry-After-Headers
Gemäß RFC 6585 kann der Retry-After-Header zwei Standardformate enthalten:
- Relative Sekunden (Ganzzahl oder Dezimalwert, z. B.
Retry-After: 12); - HTTP-Date-Zeitstempel (z. B.
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
Implementierung von Full Jitter Exponential Backoff
Fehlt der Retry-After-Header, ist der exponentielle Backoff mit vollständigem Jitter das Mittel der Wahl. Die Formel für den Versuch $i$ lautet:
$$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"Abrechnungsfehler: {error_data.get('message')}")
if attempt == self.max_retries:
raise RuntimeError(f"Maximale Anzahl an Retries erreicht (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("Anfrage nach Erschöpfung aller Wiederholungsversuche fehlgeschlagen")
Idempotenz und Sicherheit bei Wiederholungen
Wiederholungen von Leseoperationen (GET) sind unkritisch. Bei generativen POST-Anfragen gilt:
- Doppelte Agentenaufgaben vermeiden: Prüfen Sie bei Verbindungsabbrüchen, ob Tokens abgebucht wurden, bevor Sie den Aufruf blind wiederholen.
- Eindeutige Client-IDs nutzen: Übermitteln Sie den Header
X-Request-IDzur Nachverfolgung in Server-Logs. - Fehler 401 und 403 nicht wie 429 behandeln: Authentifizierungsfehler erfordern eine Schlüsselkorrektur und keine Warteschleifen.
Verifizierung der Wiederherstellung
Vor der Rückkehr zur vollen Produktionslast:
- Senden Sie einen minimalen Probe-Aufruf (
max_tokens: 5). - Prüfen Sie den Status HTTP 200 und den Header
x-ratelimit-remaining-requests. - Steigern Sie die Parallelität schrittweise unter Beobachtung der 429-Fehlerrate im Monitoring.
Um plötzliche 429-Engpässe durch starre Minutenlimits zu vermeiden und die volle Kontrolle über den Request-Status zu behalten, wechseln Sie zur BetterToken API, erstellen Sie dedizierte API-Keys und überwachen Sie Ihren Token-Verbrauch in Echtzeit im Dashboard.