Einladen & verdienen

So funktionieren Einladungsboni

Teile deinen Einladungslink. Registriert sich ein Freund darüber und lädt Guthaben auf, erhältst du die angezeigte Prämie für seine weiteren Aufladungen.

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:

  1. RPM (Requests Per Minute): Begrenzung der HTTP-Aufrufe pro Minute. Tritt häufig auf, wenn parallele Worker ohne Warteschlangensteuerung agieren.
  2. TPM (Tokens Per Minute): Begrenzung des gesamten Token-Volumens (Input und Output) in einem gleitenden Minutenfenster.
  3. 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

SymptomUrsachePrüfung in der ServerantwortTechnische Lösung
Fehler bei parallelen LastspitzenRPM-Limit überschrittenx-ratelimit-remaining-requests: 0Parallelität über Semaphore oder Warteschlangen drosseln
Fehler bei langen PromptsTPM-Limit überschrittenx-ratelimit-remaining-tokens < PromptgrößeKontext optimieren, Prompt Caching nutzen oder Batches teilen
100 % der Aufrufe liefern 429Guthaben / Kontingent erschöpftCode insufficient_quota oder AbrechnungsmeldungRetries stoppen, Guthaben aufladen oder API-Key prüfen
Lawinenartiger Anstieg von 429Retry-SturmRetry-After-Header von Workern ignoriertZufä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:

  1. Doppelte Agentenaufgaben vermeiden: Prüfen Sie bei Verbindungsabbrüchen, ob Tokens abgebucht wurden, bevor Sie den Aufruf blind wiederholen.
  2. Eindeutige Client-IDs nutzen: Übermitteln Sie den Header X-Request-ID zur Nachverfolgung in Server-Logs.
  3. 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.

Quellen und Referenzen

Bereit, Ihren LLM-Workflow zu optimieren?

Verbinden Sie Modelle über eine API, verwalten Sie Schlüssel und behalten Sie KI-Kosten im Blick.

Kostenlos starten