Advanced (Aktualisiert: 2.6.2026)

API-Rate-Limiting mit Claude Code: 429, Redis und Cloudflare

API-Rate-Limiting mit Claude Code: 429, Redis, Cloudflare, Missbrauchsschutz und typische Fehler.

API-Rate-Limiting mit Claude Code: 429, Redis und Cloudflare

API-Rate-Limiting bedeutet: Ein Client darf innerhalb eines kurzen Zeitfensters nur eine begrenzte Anzahl von Requests senden. Wird diese Grenze überschritten, muss er warten. Das ist keine Abschaltung des Dienstes, sondern eine faire Bremse für Nutzer, Bots, Skripte oder Integrationen, die zu viel auf einmal verbrauchen.

Claude Code kann Endpoints, Authentifizierung und Tests schnell erzeugen. Der gefährliche Punkt ist die Annahme, dass eine lokal funktionierende API automatisch produktionsreif ist. Login-Versuche, Suche, KI-Generierung, SMS, E-Mail-Versand und Webhook-Retries verursachen echte Last und echte Kosten. Bei einem kleinen Kontaktformular-Test sah Masa doppelte Sends zuerst als UX-Problem. Während der QA wurde aber schnell E-Mail-Kontingent verbraucht. Das eigentliche Problem war nicht nur das Formular, sondern die fehlende Antwort auf die Frage: Wie oft darf diese Aktion passieren?

Dieser Artikel macht daraus einen praktischen Claude-Code-Workflow: Design vor der Implementierung, ein Node.js-Beispiel ohne Abhängigkeiten, eine Redis-Express-Variante, Client-Code für Retry-After, Cloudflare am Edge, Sicherheits- und Missbrauchsperspektive, konkrete Fehlerfälle und ein Consulting-CTA. Verwandte Grundlagen sind Production API Development mit Claude Code, Claude Code Security Best Practices und der Cloudflare Workers Guide.

Als offizielle Quellen dienen Cloudflare Rate limiting rules, OWASP API Security 2023 API4: Unrestricted Resource Consumption und API6: Unrestricted Access to Sensitive Business Flows sowie MDN zu 429 Too Many Requests.

Erst das Schutzziel festlegen

Ein häufiger Anfängerfehler ist, mit einer Zahl wie 60 Requests pro Minute zu starten. Besser ist zuerst die Frage nach dem Risiko. Rate Limiting schützt nicht nur Serverkapazität, sondern auch Datenbanklast, Kosten externer APIs, Inventar, Passwort-Reset-Flows, E-Mail-Quota, KI-Credits, Lead-Qualität und Geschäftsregeln.

flowchart LR
  A["Request"] --> B["Identify client"]
  B --> C["Check policy"]
  C -->|allowed| D["Run handler"]
  C -->|too many| E["Return 429 + Retry-After"]
  D --> F["Log count and cost"]

Realistische Startpunkte:

Use CaseLimit-KeyStartwertSchutz
Login, OTP, Passwort-ResetIP + Account-ID5 Versuche / 10 MinBruteforce, SMS-Kosten
Suche und Listen-APIsUser-ID + Path60 / MinDB-Last, Scraping
KI- oder BildgenerierungUser-ID + Plan10 / Tag im Free PlanLLM-Kosten, Free Tier
Webhook-EmpfangSender + Event-IDKurze Bursts erlaubenDoppelverarbeitung, Queue

Verlasse dich nicht nur auf die IP-Adresse. In Firmen, Schulen oder Mobilfunknetzen teilen viele legitime Nutzer eine IP. Angreifer können IPs außerdem rotieren. Für authentifizierte APIs sind User-ID, API-Key, Organisation, Plan, Endpoint und Operationstyp zusammen deutlich besser.

Claude Code braucht eine echte Spezifikation

„Füge Rate Limiting hinzu“ ist zu ungenau. Benenne Algorithmus, Key-Strategie, 429-Antwort, Header, Tests, Logs und Storage für lokal und Produktion. Dieser Prompt ist direkt nutzbar:

Add rate limiting to the existing API.

Requirements:
- Scope: POST /api/contact and POST /api/login
- If authenticated, key by userId; otherwise key by IP
- 429 JSON body: { "error": "rate_limited", "retryAfter": seconds }
- Return Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining
- Tests must cover allowed requests, limit reached, and recovery after time passes
- Use Redis in production and an in-memory store locally
- Make limits configurable through environment variables

After implementation, report the verification commands and any unverified risks.

Damit sind die Akzeptanzkriterien überprüfbar. Claude Code soll nicht nur Middleware einbauen, sondern auch den Client-Vertrag stabilisieren. Kombiniere das mit dem API Testing Guide, damit 429 genauso getestet wird wie 200 oder 401.

Ausführbares Minimalbeispiel: Node.js 429 Server

Speichere den Code als rate-limit-demo.mjs und starte ihn mit Node.js 20 oder neuer. Er nutzt einen Token Bucket: Der Bucket füllt sich gleichmäßig, jeder Request verbraucht ein Token. Das erlaubt kurze Bursts und begrenzt trotzdem den Durchschnitt.

import http from "node:http";

class TokenBucket {
  constructor({ capacity, refillPerSecond }) {
    this.capacity = capacity;
    this.refillPerSecond = refillPerSecond;
    this.tokens = capacity;
    this.updatedAt = Date.now();
  }

  take(now = Date.now()) {
    const elapsed = (now - this.updatedAt) / 1000;
    this.tokens = Math.min(
      this.capacity,
      this.tokens + elapsed * this.refillPerSecond,
    );
    this.updatedAt = now;

    if (this.tokens >= 1) {
      this.tokens -= 1;
      return { allowed: true, remaining: Math.floor(this.tokens), retryAfter: 0 };
    }

    const missing = 1 - this.tokens;
    const retryAfter = Math.ceil(missing / this.refillPerSecond);
    return { allowed: false, remaining: 0, retryAfter };
  }
}

const buckets = new Map();

function clientKey(req) {
  return req.headers["x-api-key"] ?? req.socket.remoteAddress ?? "anonymous";
}

function checkLimit(req) {
  const key = clientKey(req);
  if (!buckets.has(key)) {
    buckets.set(key, new TokenBucket({ capacity: 5, refillPerSecond: 1 }));
  }
  return buckets.get(key).take();
}

const server = http.createServer((req, res) => {
  if (req.url !== "/api/demo") {
    res.writeHead(404, { "content-type": "application/json" });
    res.end(JSON.stringify({ error: "not_found" }));
    return;
  }

  const result = checkLimit(req);
  res.setHeader("X-RateLimit-Limit", "5");
  res.setHeader("X-RateLimit-Remaining", String(result.remaining));

  if (!result.allowed) {
    res.writeHead(429, {
      "content-type": "application/json",
      "Retry-After": String(result.retryAfter),
    });
    res.end(JSON.stringify({
      error: "rate_limited",
      retryAfter: result.retryAfter,
    }));
    return;
  }

  res.writeHead(200, { "content-type": "application/json" });
  res.end(JSON.stringify({ ok: true, remaining: result.remaining }));
});

server.listen(3000, () => {
  console.log("Listening on http://localhost:3000/api/demo");
});
node rate-limit-demo.mjs

In einem zweiten Terminal:

for i in 1 2 3 4 5 6 7; do
  curl -i http://localhost:3000/api/demo
done

Unter Windows PowerShell:

1..7 | ForEach-Object {
  curl.exe -i http://localhost:3000/api/demo
}

Der sechste oder siebte Request sollte 429 Too Many Requests liefern. MDN beschreibt, dass 429 einen Retry-After-Header enthalten kann. Genau dieser Header verhindert, dass Clients sofort weiterfeuern.

Redis-Implementierung für mehrere Instanzen

Die Memory-Version ist gut zum Lernen, bricht aber bei mehreren API-Instanzen. Server A kann null verbleibende Requests sehen, während Server B noch fünf sieht. Redis zentralisiert den Zähler.

Dieses Express-Beispiel nutzt ein Redis Sorted Set als Sliding Window. Ein Sliding Window zählt die letzten 60 Sekunden von jetzt aus, statt zu jeder vollen Minute hart zurückzusetzen. Das macht Grenzfälle glatter.

npm init -y
npm i express ioredis
docker run --rm --name redis-rate-limit -p 6379:6379 redis:7-alpine
import express from "express";
import Redis from "ioredis";

const app = express();
const redis = new Redis(process.env.REDIS_URL ?? "redis://127.0.0.1:6379");

const limitScript = `
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window_ms = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local member = ARGV[4]

redis.call("ZREMRANGEBYSCORE", key, 0, now - window_ms)

local count = redis.call("ZCARD", key)
if count >= limit then
  local oldest = redis.call("ZRANGE", key, 0, 0, "WITHSCORES")[2]
  local retry_ms = math.max(1, oldest + window_ms - now)
  return {0, 0, retry_ms}
end

redis.call("ZADD", key, now, member)
redis.call("PEXPIRE", key, window_ms)
return {1, limit - count - 1, 0}
`;

async function rateLimit(req, res, next) {
  const user = req.get("authorization")?.replace(/^Bearer\s+/i, "");
  const identity = user || req.ip || "anonymous";
  const key = `rl:${identity}:${req.path}`;
  const limit = Number(process.env.RATE_LIMIT_REQUESTS ?? 10);
  const windowMs = Number(process.env.RATE_LIMIT_WINDOW_MS ?? 60000);
  const now = Date.now();
  const member = `${now}:${Math.random()}`;

  const [allowed, remaining, retryMs] = await redis.eval(
    limitScript,
    1,
    key,
    limit,
    windowMs,
    now,
    member,
  );

  res.setHeader("X-RateLimit-Limit", String(limit));
  res.setHeader("X-RateLimit-Remaining", String(remaining));

  if (allowed === 1) return next();

  const retryAfter = Math.ceil(Number(retryMs) / 1000);
  res.setHeader("Retry-After", String(retryAfter));
  res.status(429).json({ error: "rate_limited", retryAfter });
}

app.use(rateLimit);

app.get("/api/search", (req, res) => {
  res.json({ data: ["claude-code", "rate-limit"], at: new Date().toISOString() });
});

app.listen(3000, () => {
  console.log("API ready on http://localhost:3000/api/search");
});
node redis-rate-limit-server.mjs
for i in $(seq 1 12); do
  curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/search
done

Wenn Claude Code die Produktionsvariante baut, muss das Redis-Ausfallverhalten explizit sein. Ein Marketingformular kann kurz fail open sein. Login, Payment oder KI-Credits sollten eher fail closed sein. Das ist eine Risikoentscheidung.

Clients müssen Retry-After respektieren

Serverseitiges Limiting ist nur die Hälfte. SDKs, Batch-Jobs und Webhook-Sender müssen Retry-After lesen und warten.

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function fetchWithRateLimit(url, options = {}, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt += 1) {
    const res = await fetch(url, options);
    if (res.status !== 429) return res;

    const retryAfter = Number(res.headers.get("retry-after") ?? "1");
    const waitMs = Math.max(1, retryAfter) * 1000;
    console.log(`429 received. Waiting ${waitMs}ms before retry.`);
    await sleep(waitMs);
  }

  throw new Error("Rate limit retry budget exhausted");
}

for (let i = 0; i < 8; i += 1) {
  const res = await fetchWithRateLimit("http://localhost:3000/api/demo");
  console.log(i + 1, res.status, await res.text());
}

Für externe API-Clients sollte der Prompt sagen: Bei 429 exponential backoff nutzen, vorhandenes Retry-After priorisieren, maximale Retries setzen und finalen Fehler loggen. Unendliche Retries machen aus normaler Begrenzung einen Incident.

Cloudflare am Edge, Nutzerlogik in der App

Cloudflare Rate Limiting Rules sind stark darin, offensichtliche Traffic-Spitzen vor dem Origin zu stoppen. Die offizielle Dokumentation beschreibt Expressions, Perioden, Thresholds, Mitigation Timeout und Actions. Das passt für Login, öffentliche Suche, Admin-Routen, KI-Generierung und Bot-Muster.

Cloudflare ersetzt aber keine Produktlogik. Free-vs-Paid-Quotas, Organisationslimits, KI-Credits, Refund Abuse oder Referral Abuse benötigen App-Daten. Praktisch sieht die Schichtung so aus:

SchichtAufgabeBeispiel
Cloudflare/WAFOffensichtliche Bursts und Bots früh stoppen/api/login per IP begrenzen
ApplicationUser, Organisation, Plan und Operation begrenzenFree: 10 Generierungen/Tag
Queue/WorkerTeure Jobs glättenE-Mail, Bilder, PDFs
Billing/MonitoringKostenanomalien erkennenSMS- und LLM-Alerts

OWASP API4 behandelt unbegrenzte CPU-, Memory-, Dateigrößen- und Drittanbieter-Ressourcennutzung als Sicherheitsrisiko. OWASP API6 behandelt automatisierten Missbrauch sensibler Business-Flows wie Kauf, Reservierung, Posting oder Referral. Rate Limiting ist also auch Umsatzschutz gegen Free-Tier-Ausnutzung, Spam, Resale, SMS-Kosten und Account-Angriffe.

Konkrete Fehlerfälle

Der erste Fehler ist ein globales Limit für alles. Profil lesen und Passwort zurücksetzen haben andere Risiken. Begrenze pro Endpoint und Operation.

Der zweite Fehler ist eine uneinheitliche 429-Antwort. HTML hier, Text dort, JSON anderswo macht Clients fragil. Standardisiere JSON-Body, Retry-After und Header.

Der dritte Fehler ist, nur erfolgreiche Requests zu zählen. Fehlgeschlagene Logins, ungültige Payloads und Passwort-Reset-Versuche für unbekannte E-Mails kosten ebenfalls und zeigen Missbrauch. Oft brauchen Fehler strengere Limits.

Der vierte Fehler ist PII in Keys. Speichere E-Mails oder Telefonnummern nicht im Klartext in Redis Keys oder Logs. Nutze Hashes und kurze TTLs.

Der fünfte Fehler sind langsame Tests. Ein 60-Sekunden-Fenster sollte CI nicht 60 Sekunden schlafen lassen. Übergib now und bewege die Zeit im Test.

Der letzte Fehler ist legitime Infrastruktur zu blockieren. Suchbots, Uptime Checks, internes Monitoring, Payment Webhooks und Partner-Callbacks brauchen teils eigene Regeln. Ausnahmen müssen eng und auditierbar sein.

Claude-Code-Review-Checkliste

Nach der Implementierung sollte Claude Code prüfen:

  • Haben alle 429 dieselbe JSON-Struktur?
  • Sind Retry-After und Remaining-Header gesetzt?
  • Passt die Key-Strategie für IP, User-ID, API-Key und Organisation?
  • Ist Redis-Ausfall fail open/fail closed definiert?
  • Werden Auth-, Validierungs- und externe API-Fehler bei Bedarf gezählt?
  • Decken Tests allowed, blocked und recovered ab?
  • Sind Admin-, Monitoring-, Webhook- und Crawler-Ausnahmen zu breit?

Das ist nicht nur Codequalität. Bei KI, SMS, E-Mail oder Payment wird ein Limitierungsfehler direkt zur Rechnung.

Consulting CTA

ClaudeCodeLab behandelt API-Implementierung, Security Review, Rate Limiting, Kostenschutz und Monitoring in Claude Code training and consulting. Bei bestehenden Next.js-, Express-, Cloudflare-Workers- oder AWS-API-Gateway-Projekten geht es darum, “welche Operation, für wen, wie oft” in Code, Tests und Logs zu übersetzen.

Für Solo-Projekte reicht zuerst die Node.js-Demo, später Redis bei mehreren Instanzen. Für Teams sollten Claude-Code-Prompt, Review-Checkliste, Environment-Variablen und Runbook zusammen dokumentiert werden, damit Grenzwerte später kontrolliert geändert werden.

Ich habe die Beispiele praktisch geprüft: Der Memory-Server gab nach wiederholten Requests 429 zurück, die Redis-Version lieferte nach Überschreiten des 10-Request-Fensters 429 mit Retry-After, und der Client wartete statt sofort weiterzufeuern. Fazit: Rate Limiting ist erst produktionsreif, wenn Response, Retry, Logs und Ausnahmen gemeinsam getestet sind.

#Claude Code #rate limiting #API #security #Node.js
Kostenlos

Kostenloses PDF: Claude-Code-Cheatsheet

E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.

Wir schützen Ihre Daten und senden keinen Spam.

Masa

Über den Autor

Masa

Engineer für praktische Claude-Code-Workflows und Team-Einführung.