Umgang mit Ratenlimits und Wiederholungsversuchen bei LLM-APIs
An einem Dienstag um 09:12 schien unsere Zusammenfassungs-Pipeline einwandfrei zu laufen: 96 Worker waren online, die mediane Modelllatenz lag bei etwa 2.8 Sekunden, und die Rate der 5xx-Fehler war nicht erhöht. Dann lud ein Kunde einen Rückstau von 38,000 Support-Tickets hoch.
Innerhalb von vier Minuten war die Pipeline „ausgefallen“, ohne dass ein Server ausgefallen war. Wir stießen gleichzeitig an zwei verschiedene Grenzen:
requests_per_minute: zu viele API-Aufrufetokens_per_minute: zu großes Gesamtvolumen aus Prompt und Antwort
Die naive Wiederholungslogik machte alles noch schlimmer. Jeder Worker erhielt 429, wartete eine Sekunde und versuchte es dann gleichzeitig mit den anderen erneut. Dadurch entstand eine Welle von Wiederholungsversuchen, die weitere 429 verursachte und damit eine weitere Welle auslöste. Das System hatte versehentlich einen synchronisierten Denial-of-Service-Angriff gegen sein eigenes Kontingent aufgebaut.
Der Umgang mit LLM-Ratenbegrenzungen besteht nicht nur darin, „exponentielles Backoff hinzuzufügen“. Im Produktionsbetrieb müssen Sie Tokens, Warteschlangen, Parallelität, Wiederholungsversuche, Idempotenz, Ausweichanbieter und Abrechnungsverhalten als ein Gesamtsystem betrachten.
Die beiden entscheidenden Grenzen: RPM und TPM
Die meisten LLM-APIs setzen eine Kombination der folgenden Grenzen durch:
- RPM: Anfragen pro Minute
- TPM: Tokens pro Minute
- RPD / TPD: Anfragen oder Tokens pro Tag
- Grenzen für gleichzeitige Anfragen: maximale Anzahl laufender Aufrufe
- Modellspezifische Grenzen: höhere Grenzen für kleinere Modelle, niedrigere Grenzen für Spitzenmodelle
Der entscheidende Punkt: RPM und TPM führen auf unterschiedliche Weise zu Problemen.
Wenn Sie viele sehr kleine Prompts senden, erreichen Sie zuerst die RPM-Grenze. Wenn Sie weniger, dafür große Prompts senden, erreichen Sie zuerst die TPM-Grenze.
Beispiel:
Limit:
- 600 requests/minute
- 300,000 tokens/minute
Workload A:
- 1,000 short classification calls/minute
- 200 tokens each
- 200,000 tokens/minute total
Result:
- TPM is fine
- RPM fails
Workload B:
- 100 long document analysis calls/minute
- 5,000 tokens each
- 500,000 tokens/minute total
Result:
- RPM is fine
- TPM fails
Ein häufiger Fallstrick: Entwickler drosseln Anfragen, aber nicht Tokens. Das funktioniert in der Staging-Umgebung, wo die Prompts kurz sind. Dann fügen Nutzer im Produktionsbetrieb PDFs, Chatverläufe, Protokolle oder Verträge ein, und das Token-Budget ist im Nu aufgebraucht.
In der Praxis behandle ich jeden LLM-Aufruf so, als würde er zwei Ressourcen reservieren:
cost = {
requests: 1,
tokens: estimated_prompt_tokens + max_output_tokens
}
Selbst wenn das Modell weniger Ausgabetokens zurückgibt als max_output_tokens, verhindert die Reservierung der Obergrenze eine Überbuchung.
Was tatsächlich passiert, wenn Sie TPM ignorieren
Angenommen, Sie haben:
TPM limit: 120,000
Average request: 4,000 input tokens + 1,000 max output tokens
Reserved cost: 5,000 tokens
Ihr tatsächlicher maximaler Durchsatz ist nicht „so viele Worker wie möglich“. Er beträgt:
120,000 / 5,000 = 24 requests per minute
Das entspricht im Durchschnitt einer Anfrage alle 2.5 Sekunden.
Wenn Sie 100 Worker betreiben, können alle Anfragen des ersten Durchlaufs erfolgreich starten, sofern der Anbieter kurzfristige Lastspitzen zulässt. Danach stößt das Konto an eine harte Grenze. Jede nachfolgende Anfrage erhält 429, und die Wiederholungsversuche häufen sich.
Die Lösung besteht nicht nur darin, die Anzahl der Worker zu reduzieren. Sie brauchen einen Scheduler, der Token-Reservierungen berücksichtigt.
Ein minimaler Ratenbegrenzer mit Token-Berücksichtigung
Für einen einzelnen Prozess reicht meist ein Token-Bucket aus. Sie füllen die Kapazität im Laufe der Zeit wieder auf und verlangen, dass jede Anfrage sowohl Anfragekapazität als auch Token-Kapazität reserviert.
Hier ist ein vereinfachtes Python-Beispiel:
import asyncio
import time
from dataclasses import dataclass
@dataclass
class Bucket:
capacity: float
refill_per_second: float
tokens: float
updated_at: float
def refill(self):
now = time.monotonic()
elapsed = now - self.updated_at
self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_per_second)
self.updated_at = now
async def acquire(self, amount: float):
while True:
self.refill()
if self.tokens >= amount:
self.tokens -= amount
return
missing = amount - self.tokens
sleep_for = missing / self.refill_per_second
await asyncio.sleep(min(sleep_for, 1.0))
class LLMRateLimiter:
def __init__(self, rpm: int, tpm: int):
now = time.monotonic()
self.request_bucket = Bucket(
capacity=rpm,
refill_per_second=rpm / 60,
tokens=rpm,
updated_at=now,
)
self.token_bucket = Bucket(
capacity=tpm,
refill_per_second=tpm / 60,
tokens=tpm,
updated_at=now,
)
self.lock = asyncio.Lock()
async def acquire(self, estimated_tokens: int):
async with self.lock:
await self.request_bucket.acquire(1)
await self.token_bucket.acquire(estimated_tokens)
Verwendung:
estimated_tokens = prompt_tokens + max_output_tokens
await limiter.acquire(estimated_tokens)
response = await client.chat.completions.create(
model="gpt-5.5",
messages=messages,
max_tokens=max_output_tokens,
)
Das ist bewusst einfach gehalten. Verwenden Sie in einem verteilten System Redis, Advisory Locks in Postgres oder eine Warteschlange mit zentraler Zuweisung. Das entscheidende Prinzip bleibt gleich: Drosseln Sie sowohl die Anzahl der Anfragen als auch das Token-Budget, bevor Sie die API aufrufen.
Wiederholungsversuche: Backoff ist notwendig, reicht aber nicht aus
Wiederholungsversuche sind sinnvoll bei:
- Vorübergehenden
429Antworten - Netzwerk-Zeitüberschreitungen
- Temporären
500,502,503, oder504Fehlern - Überlastung auf Anbieterseite
- Verbindungsabbrüchen
Wiederholungsversuche sind riskant bei:
- Validierungsfehlern
- Fehlern aufgrund der Kontextlänge
- Authentifizierungsfehlern
- Ablehnungen aufgrund von Sicherheitsrichtlinien
- Prompts, die deterministisch zu Fehlern führen
- Nicht idempotenten Tool-Aufrufen
Die Richtlinie für Wiederholungsversuche sollte zunächst Fehler klassifizieren.
| Fehlertyp | Erneut versuchen? | Typische Maßnahme |
|---|---|---|
429 rate_limit_exceeded | Ja | Backoff anwenden, Header für Wiederholungsversuche beachten, Parallelität reduzieren |
500/502/503/504 | Ja | Backoff mit Jitter, begrenzte Anzahl von Versuchen |
| Zeitüberschreitung vor Empfang des Antwortinhalts | Vielleicht | Falls unterstützt, mit einem Idempotenzschlüssel erneut versuchen |
| Kontextlänge überschritten | Nein | Komprimieren, in Abschnitte aufteilen oder an ein Modell mit längerem Kontext weiterleiten |
| Ungültiger API-Schlüssel | Nein | Schlüssel deaktivieren, Betreiber benachrichtigen |
| Ablehnung aufgrund von Inhalten oder Sicherheitsrichtlinien | Nein | Kontrollierten Fehler zurückgeben oder Anfrage umformulieren |
| Nebeneffekt des Tools bereits ausgeführt | Nein/Vielleicht | Erfordert ein Idempotenzkonzept |
Das grundlegende Backoff-Schema:
import random
def retry_delay(attempt: int, base=0.5, cap=30.0):
exponential = min(cap, base * (2 ** attempt))
return random.uniform(0, exponential)
Das random.uniform(0, exponential) ist Jitter. Lassen Sie ihn nicht weg.
Ohne Jitter:
100 workers fail
100 workers sleep 1s
100 workers retry
100 workers fail
100 workers sleep 2s
100 workers retry
Mit Jitter:
100 workers fail
workers retry across a spread of 0-1s
then 0-2s
then 0-4s
Das zweite Muster gibt dem Anbieter und Ihrer eigenen Warteschlange Spielraum, sich zu erholen.
Retry-Header berücksichtigen, sofern vorhanden
Viele APIs enthalten einen Hinweis wie:
Retry-After: 12
oder anbieterspezifische Reset-Header. Wenn die API eine konkrete Wartezeit vorgibt, verwenden Sie diese als Untergrenze:
delay = max(header_retry_after_seconds, retry_delay(attempt))
await asyncio.sleep(delay)
In der Praxis begrenze ich auch die gesamte Dauer der Wiederholungsversuche. Eine Chat-Anfrage mit direkter Nutzerinteraktion sollte nicht unbemerkt zwei Minuten lang wiederholt werden, während der Nutzer auf eine Ladeanzeige starrt. Ein Batch-Job im Hintergrund kann deutlich länger warten.
Beispiel für eine Richtlinie:
{
"interactive_chat": {
"max_attempts": 3,
"max_total_retry_seconds": 8
},
"batch_summarization": {
"max_attempts": 8,
"max_total_retry_seconds": 300
}
}Warteschlangen sind besser als ein Ansturm von Workern
Wenn mehr Arbeit anfällt, als Ihr Kontingent erlaubt, sollten nicht alle Worker die LLM-API direkt aufrufen. Schalten Sie eine Warteschlange davor.
Eine praktische Architektur:
App/API
-> Job queue
-> Rate-aware dispatcher
-> Provider client
-> Result store
Der Dispatcher ist zuständig für:
- RPM-Erfassung
- TPM-Erfassung
- Modellauswahl
- Richtlinie für Wiederholungsversuche
- Anbieter-Fallback
- Parallelitätsgrenzen
- Warteschlangenpriorität
Dadurch wird Überlastung sichtbar. Statt Tausender Aufgaben, die mit 429fehlschlagen, sehen Sie, wie die Warteschlange wächst und die geschätzte Wartezeit steigt.
Bei Redis-basierten Systemen trenne ich gerne „ausführungsbereite“ Jobs von Jobs mit „verzögertem Wiederholungsversuch“:
llm:ready
llm:delayed
llm:dead_letter
Ein fehlgeschlagener Job kehrt nicht sofort zurück in ready. Er erhält einen geplanten Zeitstempel für den Wiederholungsversuch. Ein kleiner Scheduler verschiebt fällige Jobs zurück in die Warteschlange der ausführungsbereiten Jobs.
{
"job_id": "sum_7f13",
"model": "claude-sonnet-4.6",
"estimated_tokens": 6200,
"attempt": 2,
"not_before": "2026-02-03T09:14:22Z"
}
Das not_before Feld verhindert eine Flut von Wiederholungsversuchen.
Parallelitätssteuerung ist nicht dasselbe wie Ratenbegrenzung
Parallelitätsgrenzen begrenzen die gleichzeitig laufende Arbeit. Ratenbegrenzungen begrenzen die Arbeit pro Zeitfenster.
Sie brauchen beides.
Stellen Sie sich vor, jeder Aufruf dauert 20 Sekunden und Ihr RPM-Limit beträgt 60. Eine reine RPM-Begrenzung erlaubt eine Anfrage pro Sekunde. Nach 20 Sekunden laufen etwa 20 Aufrufe gleichzeitig. Wenn die Latenz auf 90 Sekunden steigt, führt dasselbe RPM-Limit zu 90 gleichzeitigen Aufrufen. Das kann die Grenzen des Anbieters oder der lokalen Ressourcen überschreiten.
Verwenden Sie ein Semaphor zur Parallelitätssteuerung:
provider_concurrency = asyncio.Semaphore(20)
async def call_model(job):
async with provider_concurrency:
await limiter.acquire(job.estimated_tokens)
return await send_request(job)
Ein häufiger Fallstrick: Wenn Sie das Semaphor zu früh belegen, warten Ihre Worker untätig auf Tokens der Ratenbegrenzung und halten dabei knappe Parallelitätsplätze belegt. In der Regel sollten Sie zuerst Ratenkapazität reservieren und dann unmittelbar vor dem Netzwerkaufruf einen Parallelitätsplatz belegen. Wenn die Wartezeit an der Ratenbegrenzung lang sein kann, vermeiden Sie es, währenddessen Worker-Ressourcen zu belegen.
Kontrollierter Leistungsabbau: Qualität dort einsetzen, wo sie zählt
Wenn Ratenbegrenzungen greifen, haben Sie andere Möglichkeiten, als einfach zu scheitern.
Ein Kundensupport-Assistent könnte seine Leistung beispielsweise so reduzieren:
| Belastungsgrad | Strategie | Auswirkung auf Nutzer |
|---|---|---|
| Normal | Leistungsfähigstes konfiguriertes Modell verwenden | Beste Qualität |
| Leichte TPM-Belastung | Reduzieren von max_tokens, abgerufenen Kontext kürzen | Etwas kürzere Antworten |
| Mittlere Belastung | Einfache Aufgaben an ein kleineres Modell weiterleiten | Minimal, wenn Klassifizierung und Routing einfach sind |
| Hohe Belastung | Nicht dringende Jobs in die Warteschlange stellen | Verzögerte Hintergrundverarbeitung |
| Sehr hohe Belastung | Zwischengespeicherte oder vorlagenbasierte Antwort zurückgeben | Geringere Qualität, aber verfügbar |
Konkrete Beispiele:
- Leiten Sie die Ticket-Kategorisierung an Haiku 4.5, Qwen oder eine kleine Llama-Variante weiter, statt ein Spitzenmodell zu verwenden.
- Verwenden Sie Sonnet 4.6 für normale Schlussfolgerungsaufgaben und reservieren Sie Opus 4.8 oder GPT-5.5 für Eskalationspfade.
- Verlagern Sie Analysen mit langem Kontext nur dann auf Fable 5 oder Gemini 3, wenn der Prompt dieses Kontextfenster tatsächlich benötigt.
- Pausieren Sie bei Batch-Jobs Mandanten mit niedriger Priorität, bevor der interaktive Datenverkehr beeinträchtigt wird.
Der Preis dafür ist Komplexität. Jeder Fallback-Pfad braucht Tests, Beobachtbarkeit und die Akzeptanz des Produktteams. In manchen Anwendungsbereichen kann eine schlechtere Antwort mehr Schaden anrichten als eine verzögerte Antwort.
Fallback mit mehreren Schlüsseln und Anbietern
Routing über mehrere Schlüssel und Anbieter kann die Ausfallsicherheit verbessern, lässt sich aber leicht schlecht umsetzen.
Es gibt drei unterschiedliche Muster:
| Muster | Welches Problem es löst | Risiko |
|---|---|---|
| Mehrere Schlüssel, gleicher Anbieter/gleiches Konto | Betriebliche Trennung | Kann gegen die Bedingungen des Anbieters verstoßen, wenn damit Limits umgangen werden |
| Mehrere Konten/Regionen | Fehlerisolierung | Höhere Komplexität bei Abrechnung und Governance |
| Mehrere Anbieter/Modelle | Verfügbarkeit und Kostenflexibilität | Schwankende Ausgaben, inkompatible Prompts |
Verwenden Sie mehrere Schlüssel nicht, um Limits heimlich zu umgehen. Nutzen Sie sie für eine legitime Trennung: Mandanten, Umgebungen, Produkte oder Abrechnungsgrenzen.
Eine saubere Routing-Richtlinie könnte so aussehen:
{
"task": "support_ticket_summary",
"primary": {
"provider": "anthropic",
"model": "claude-sonnet-4.6"
},
"fallbacks": [
{
"provider": "openai",
"model": "gpt-5.5",
"when": ["provider_5xx", "rate_limit_after_retry_budget"]
},
{
"provider": "google",
"model": "gemini-3",
"when": ["context_length_needed", "primary_unavailable"]
}
],
"degrade_to": {
"provider": "open_model_cluster",
"model": "qwen",
"when": ["batch_low_priority"]
}
}
Wenn Sie einen Aggregator oder eine Plattform wie AI Prime Tech für den Zugriff auf mehrere Modelle verwenden, gelten weiterhin dieselben technischen Regeln: Erfassen Sie Idempotenz, Budgets, Fallback-Bedingungen und die endgültige Abrechnungslogik ausdrücklich.
Doppelte Abrechnung beim Fallback vermeiden
Die Schwierigkeit beim Fallback besteht darin, zu wissen, ob die erste Anfrage abgeschlossen wurde.
Betrachten Sie diese Abfolge:
Client sends request to Provider A
Provider A processes it successfully
Network connection drops before client receives response
Client retries with Provider B
Both providers bill
Two completions may exist
Auf Clientseite lässt sich das nicht vollständig lösen, sofern der Anbieter keine Idempotenzschlüssel oder Abfragen zum Status einer Anfrage unterstützt. Sie können den Schaden jedoch begrenzen.
Idempotenzschlüssel verwenden, sofern verfügbar
Erzeugen Sie einen stabilen Schlüssel pro logischem Vorgang:
import hashlib
import json
def idempotency_key(task_name, tenant_id, input_payload):
canonical = json.dumps(input_payload, sort_keys=True, separators=(",", ":"))
digest = hashlib.sha256(canonical.encode()).hexdigest()
return f"{tenant_id}:{task_name}:{digest}"
Speichern Sie den Vorgang vor dem Senden:
{
"operation_id": "acme:summarize:9d2a...",
"status": "in_flight",
"provider": "anthropic",
"model": "claude-sonnet-4.6",
"created_at": "2026-02-03T09:12:01Z"
}
Aktualisieren Sie ihn dann nach Abschluss:
{
"operation_id": "acme:summarize:9d2a...",
"status": "completed",
"provider_request_id": "req_abc123",
"usage": {
"input_tokens": 4180,
"output_tokens": 612
}
}
Prüfen Sie die Vorgangstabelle, bevor Sie die Anfrage anderswo erneut versuchen. Wenn ein anderer Worker den Vorgang bereits abgeschlossen hat, geben Sie das gespeicherte Ergebnis zurück.
Timeout-Arten unterscheiden
Nicht alle Timeouts sind gleich:
- Verbindungs-Timeout: Die Anfrage hat den Anbieter wahrscheinlich nie erreicht
- Lese-Timeout vor Empfang der Header: ungewiss
- Timeout während des Token-Streamings: Der Anbieter hat die Anfrage mit ziemlicher Sicherheit verarbeitet
- Abbruch durch den Client: hängt davon ab, ob der Abbruch weitergegeben wurde
Für ungewisse Fälle bevorzuge ich eine vorsichtige Richtlinie für Wiederholungsversuche:
interactive:
- retry connect timeout
- do not cross-provider retry after partial stream
- show recoverable error if status unknown
batch:
- retry after reconciliation delay
- prefer same provider with idempotency key
- fallback only after retry budget expires
Mit Bedacht streamen
Streaming verbessert die wahrgenommene Latenz, erschwert aber Wiederholungsversuche. Sobald Sie dem Nutzer einen Teil der Ausgabe gesendet haben, kann ein erneuter Versuch mit einem anderen Modell zu einer anderen Fortsetzung führen.
Beim Chat-Streaming betrachte ich „erstes Token empfangen“ in der Regel als den Punkt, ab dem kein transparenter Wiederholungsversuch mehr möglich ist. Danach werden Fehler für den Nutzer sichtbar:
"The response was interrupted. Retry?"
Das ist weniger elegant als eine unbemerkte Wiederherstellung, verhindert aber, dass inkompatible Ausgaben verschiedener Modelle zusammengesetzt werden.
Beobachtbarkeit: Die Metriken, die tatsächlich helfen
Sie brauchen mehr als die Erfolgsquote.
Erfassen Sie mindestens:
llm_requests_total{provider,model,status}
llm_tokens_reserved_total{provider,model}
llm_tokens_used_total{provider,model}
llm_retries_total{provider,model,error_type}
llm_queue_depth{priority,task_type}
llm_queue_wait_seconds{priority,task_type}
llm_rate_limit_hits_total{provider,model,limit_type}
llm_fallbacks_total{from_provider,to_provider,reason}
llm_duplicate_suppressed_total{task_type}
Das nützlichste Dashboard-Panel bei tatsächlichen Störungen zeigt oft nicht die Latenz. Es zeigt:
queue_wait_seconds p50 / p95
rate_limit_hits by provider and model
tokens_reserved vs tokens_used
fallback rate by reason
Wenn tokens_reserved durchgehend deutlich höher ist als tokens_used, sind Ihre Reservierungen zu konservativ. Liegt der Wert zu nahe am tatsächlichen Verbrauch oder darunter, überbuchen Sie Ihre Kapazität und stoßen unerwartet an das TPM-Limit.
Ein praktischer Wrapper für Wiederholungsversuche
Hier ist ein kompaktes Muster, um Anfragen nur bei geeigneten Fehlern zu wiederholen:
import asyncio
import random
import time
RETRYABLE_STATUS = {429, 500, 502, 503, 504}
class LLMError(Exception):
def __init__(self, status, message="", retry_after=None):
self.status = status
self.retry_after = retry_after
super().__init__(message)
def jittered_backoff(attempt, base=0.5, cap=20):
return random.uniform(0, min(cap, base * (2 ** attempt)))
async def call_with_retries(fn, max_attempts=4, max_elapsed=20):
started = time.monotonic()
for attempt in range(max_attempts):
try:
return await fn()
except LLMError as error:
elapsed = time.monotonic() - started
if error.status not in RETRYABLE_STATUS:
raise
if attempt == max_attempts - 1 or elapsed >= max_elapsed:
raise
delay = jittered_backoff(attempt)
if error.retry_after is not None:
delay = max(delay, error.retry_after)
remaining = max_elapsed - elapsed
await asyncio.sleep(min(delay, remaining))
Der Wrapper ist bewusst unspektakulär. Die ausgefeilte Logik gehört in die Klassifizierung, Warteschlangenverwaltung, Idempotenz und das Routing – nicht in eine magische Wiederholungsschleife, die es endlos weiter versucht.
Praktische Empfehlungen
- Modellieren Sie Ratenlimits sowohl als RPM als auch als TPM. Eine Drosselung, die den Tokenverbrauch berücksichtigt, verhindert Überraschungen durch große Prompts.
- Verwenden Sie exponentielles Backoff mit zufälliger Streuung, und beachten Sie die Header für Wiederholungsversuche, wenn der Anbieter sie bereitstellt.
- Platzieren Sie eine Warteschlange und einen Dispatcher zwischen Ihrer Anwendung und den LLM-Anbietern, wenn Sie Batch-Verarbeitung oder hohe Anfragevolumen bewältigen müssen.
- Maßnahme Parallelität getrennt von Ratenlimits; andernfalls können Latenzspitzen zu viele gleichzeitig laufende Aufrufe verursachen.
- Design kontrollierte Funktionseinschränkungen gezielt: kleinere Modelle, kürzerer Kontext, Hintergrundaufgaben in der Warteschlange oder zwischengespeicherte Antworten.
- Behandeln Sie Fallback über mehrere Anbieter als Funktion zur Verbesserung der Zuverlässigkeit, nicht als Freibrief, wahllos doppelte Anfragen zu versenden.
- Verwenden Sie Idempotenzschlüssel und Vorgangsprotokolle um doppelte Abrechnungen zu reduzieren und doppelte Generierungen zu unterdrücken.
- Seien Sie gegenüber Nutzern ehrlich, wenn eine Streaming-Antwort fehlschlägt, nachdem bereits Tokens ausgegeben wurden.
- Richten Sie Dashboards auf folgende Kennzahlen aus: Wartezeit in der Warteschlange, Tokenreservierung, erreichte Ratenlimits, Wiederholungsversuche und Gründe für Fallbacks.
- Halten Sie die Wiederholungslogik einfach; die eigentliche Arbeit im Produktivbetrieb besteht in der Budgetierung, Ablaufplanung und darin, zu wissen, wann keine Wiederholung sinnvoll ist.
Ein API-Schlüssel für Claude Opus 5.5, Sonnet 5, Haiku 4.5 und Fable 5.1 sowie GPT-6 Modelle. Nutzungsbasierte Abrechnung, kein Abonnement.
API-Schlüssel erhalten →