Claude Code hinter einem LLM-Gateway: Die Credential entscheidet, wer zahlt, und ohne sie bekommt der Proxy deinen claude.ai-Login
Zwölf Beta-Werte pro Request mit Abo-Login, zehn mit Gateway-Token, und das Paar, das verschwindet, sagt mehr als die Tabellen in der Doku. Was ein Gateway unverändert durchreichen muss, welcher Default still teurer wird und warum ein Pinning per allowedProviders Entwicklern auch ihren eigenen Debugging-Listener nimmt.
Inhalt
Zwei Umgebungsvariablen entscheiden, was passiert, wenn Claude Code über ein LLM-Gateway läuft. ANTHROPIC_BASE_URL legt fest, wohin die Requests gehen. Eine Gateway-Credential, also ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY oder ein apiKeyHelper, legt fest, wer dafür zahlt. Die meisten Setup-Anleitungen behandeln beides als einen Schritt, dabei sind es zwei Modi. Mit der Base URL allein zahlt das Abo, und der Proxy bekommt bei jedem Request deinen claude.ai-Login: ein Debugging-Modus für den eigenen Rechner einer einzelnen Person. Mit einer Gateway-Credential zahlt eine Organisation pro Token, und zwei Werte verschwinden aus dem Traffic, die OAuth-Beta und die Beta für den einstündigen Cache. Ein Team-Gateway gehört in den zweiten Modus.
Um den Unterschied auf der Leitung zu sehen, habe ich Claude Code 2.1.303 auf einen kleinen Listener auf localhost gerichtet, der jeden Request protokolliert und mit einem Fehler antwortet, sodass nichts nach oben weitergeht. Drei Durchläufe: nur Base URL, dann Base URL plus ein Dummy-Gateway-Token, dann dieselbe Konfiguration mit eingeschalteten Gateway-Hint-Headern. Die Protokolldetails unten stammen aus der Gateway-Doku, die Beobachtungen aus diesen drei Logs, und die Empfehlungen sind meine eigene Lesart von beidem.
Was ändert sich, wenn Claude Code mit einem LLM-Gateway spricht?
Das Routing ändert sich in jedem Fall, und eine Funktion auch: Remote Control ist aus, sobald ANTHROPIC_BASE_URL auf einen anderen Host als den von Anthropic zeigt, selbst wenn du dich mit claude.ai anmeldest. Abrechnung, Limits und der Rest ändern sich nur, wenn eine Gateway-Credential aktiv ist. Mit der Base URL allein bleibt ein gespeicherter claude.ai-Login die Credential, es gelten also die Limits und die Abrechnung des Plans, und das Gateway ist ein Router mit Log.
Die Tabelle geht von einem Rechner aus, der mit claude.ai angemeldet ist, ohne weitere Credential oder Provider und mit Standard-Cache-Einstellungen:
| Nur Base URL | Base URL + Gateway-Credential | |
|---|---|---|
| Credential auf der Leitung | der gespeicherte claude.ai-Login | die Credential des Gateways |
| Abrechnung | dein Abo | pro Token, an den Inhaber der Provider-Credential hinter dem Gateway |
| Nutzungslimits des Abos | gelten | gelten nicht |
| Standard-TTL des Prompt-Cache, Hauptkonversation | eine Stunde, innerhalb der Plan-Nutzung | fünf Minuten |
| Remote Control | aus | aus |
| Spracheingabe | laut Doku verfügbar (hier nicht getestet) | aus |
| claude.ai-Connectors | in meinem Lauf keine Warnung | aus, mit Warnung beim Start |
Die Connectors-Zeile stammt aus meinem Lauf, nicht aus der Doku. Mit gesetztem Dummy-Token gab claude -p aus:
claude.ai connectors are disabled because ANTHROPIC_API_KEY or another auth source is set and takes precedence over your claude.ai login
Die Cache-Zeile ist ein Default, kein Limit. Laut der Seite zum Prompt Caching fordert Claude Code die einstündige TTL von sich aus nur bei einem Claude-Abo innerhalb der Plan-Nutzung an; mit API-Key, Cloud-Provider oder Usage Credits bekommt die Hauptkonversation fünf Minuten. Die Stunde kannst du mit promptCacheTtl: "1h" in den Settings oder CLAUDE_CODE_PROMPT_CACHE_TTL= zurückholen (Claude Code 2.1.242 oder neuer), und das Gateway muss dann den Beta-Wert weiterleiten, der sie trägt. Anthropics eigenes Claude apps gateway, das im claude-Binary steckt und den Abo-Login ebenfalls abschaltet, bietet die einstündige TTL gar nicht an. Lässt du den Default stehen, schreibt eine lange Session mit Pausen über fünf Minuten ihren Cache bei jeder Rückkehr neu.
Wie verbindest du Claude Code mit einem bestehenden Gateway?
Hol dir Base URL und Credential von dem, der das Gateway betreibt, teste beides mit curl und setze dann zwei Variablen. Hat ein Admin sie schon über Managed Settings verteilt, startet claude ohne Login-Bildschirm. Laut der Connect-Seite zeigt /status dann eine Zeile Anthropic base URL plus eine Zeile Auth token oder API key mit der verwendeten Variable, und eine Zeile Login method mit einem claude.ai-Konto heißt, dass die Credential nie angekommen ist.
Die Credential-Variable muss zu dem Header passen, den das Gateway liest. ANTHROPIC_AUTH_TOKEN geht als Authorization: Bearer raus, ANTHROPIC_API_KEY als x-api-key und ein apiKeyHelper-Wert in beiden. Hat dir niemand gesagt, welche es ist, fang mit dem Bearer-Token an. Der Test aus der Doku:
export ANTHROPIC_BASE_URL=https://llm-gateway.example.com
export ANTHROPIC_AUTH_TOKEN=sk-gateway-key
curl -sS -w '\n%{http_code}\n' -X POST "$ANTHROPIC_BASE_URL/v1/messages" \
-H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model": "claude-sonnet-4-6", "max_tokens": 1, "messages": [{"role": "user", "content": "."}]}'Ein 401 heißt meist falsche Variable oder widerrufener Key, kann aber auch bedeuten, dass die Provider-Credential des Gateways beim Upstream scheitert; die Logs des Gateways unterscheiden die beiden Fälle. Ein Fehler „unknown model“ heißt, der Test ist bestanden: Das Gateway hat dich authentifiziert, bevor es sich den Modellnamen angesehen hat.
Sobald es funktioniert, schieb die beiden Werte in den env-Block von ~/.claude/settings.json, nicht in dein Shell-Profil. Shell-Exports erreichen Hintergrund-Agents nicht zuverlässig, und der Wert aus der Settings-Datei gewinnt, wenn beide dieselbe Variable setzen. Leg die Credential nie in die .claude/settings.json eines Projekts, die wird committet. Die VS-Code-Extension will dasselbe Paar in claudeCode., und die Desktop-App ignoriert beides und liest ihre eigene Konfiguration für Third-Party-Inference.
Was schickt Claude Code an ein LLM-Gateway?
In meinen Läufen zuerst eine Warm-up-Probe HEAD /api/hello ohne Credential (laut Doku entfällt sie, wenn ein HTTP-Proxy oder ein Client-Zertifikat konfiguriert ist), dann POST /v1/ mit einem Session-ID-Header, der vollständigen anthropic-beta-Liste und dem ganzen Body der Messages API. Das hier ist eine gekürzte Version meines Listeners. Er behält die Header-Namen, kürzt die beiden Credential-Header auf ein Präfix von sieben Zeichen plus Länge, protokolliert das Modell, die Top-Level-Keys des Body und den Anfang des ersten system-Blocks und antwortet immer mit 503:
import json, sys
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
def redact(name, value):
if name.lower() in ("authorization", "x-api-key"):
token = value.split(" ")[-1]
return f"<redacted prefix={token[:7]} len={len(token)}>"
return value
class Handler(BaseHTTPRequestHandler):
def handle_any(self):
n = int(self.headers.get("content-length") or 0)
body = self.rfile.read(n) if n else b""
rec = {"method": self.command, "path": self.path,
"headers": {k: redact(k, v) for k, v in self.headers.items()}}
try:
j = json.loads(body)
rec["model"] = j.get("model")
rec["body_keys"] = sorted(j)
rec["system0"] = (j.get("system") or [{}])[0].get("text", "")[:60]
except (ValueError, AttributeError):
pass
with open("requests.jsonl", "a") as f:
f.write(json.dumps(rec) + "\n")
self.send_response(503)
self.end_headers()
do_GET = do_POST = do_HEAD = handle_any
def log_message(self, *args):
pass
ThreadingHTTPServer(("127.0.0.1", int(sys.argv[1])), Handler).serve_forever()Geschwärzt werden nur diese beiden Header. Setzt du also ANTHROPIC_CUSTOM_HEADERS mit einem Secret darin, landet es unverändert in der Datei. Starte den Listener mit python3 listen.py 8765, stell sicher, dass kein env-Block in deinen Settings-Dateien eine Base URL oder eine Credential setzt (ein Wert aus der Settings-Datei schlägt den Export und würde den Lauf ans echte Gateway schicken), und führ in einem zweiten Terminal aus:
ANTHROPIC_BASE_URL=http://127.0.0.1:8765 timeout 60 claude -p "say ok" --model haiku < /dev/nullFür den zweiten Lauf kommt ANTHROPIC_AUTH_TOKEN= in dieselbe Zeile, für den dritten zusätzlich CLAUDE_CODE_GATEWAY_HINT_HEADERS=. Claude Code wiederholt einen 503, also protokolliert jeder Lauf mehrere Kopien desselben Requests; eine reicht. Der erste Modell-Request des Laufs nur mit Base URL, gekürzt:
POST /v1/messages?beta=true model claude-haiku-5-5
Authorization: Bearer <redacted prefix=sk-ant- len=108>
User-Agent: claude-cli/2.1.303 (external, sdk-cli)
X-Claude-Code-Session-Id: f85023e8-...
anthropic-version: 2023-06-01
anthropic-beta: oauth-2025-04-20, interleaved-thinking-2025-05-14,
thinking-token-count-2026-05-13, context-management-2025-06-27,
prompt-caching-scope-2026-01-05, claude-code-20250219,
per-turn-control-2026-07-01, mid-conversation-tool-changes-2026-07-01,
inline-tools-2026-09-15, advisor-tool-2026-03-01, effort-2025-11-24,
extended-cache-ttl-2025-04-11
body keys: context_management, max_tokens, messages, metadata, model,
output_config, stream, system, thinking, tools
system[0]: x-anthropic-billing-header: cc_version=2.1.303...Mit dem Dummy-Gateway-Token trug derselbe Request zehn Beta-Werte statt zwölf. oauth-2025-04-20 fehlte, was zu erwarten ist, und ebenso extended-cache-ttl-2025-04-11, was zum dokumentierten Wechsel des Defaults von einer Stunde auf fünf Minuten passt. Der Listener hat nie eine echte Antwort geliefert, das zeigt also den Request, nicht, was gecacht oder abgerechnet wurde.
Die Header zur Zuordnung pro Agent, die die Doku beschreibt, x-claude-code-agent-id und bei verschachtelten Agents x-claude-code-parent-agent-id, tauchen nur in Requests von Subagents auf, und in diesen Läufen wurden keine gestartet. Die Routing-Hints sind hinter einer eigenen Base URL standardmäßig aus, weil ein Proxy, der unbekannte Header ablehnt, sonst den Request scheitern lässt. Mit CLAUDE_CODE_GATEWAY_HINT_HEADERS= (Hints brauchen Claude Code 2.1.273 oder neuer, die Prompt-ID 2.1.283) kamen in meinem dritten Lauf x-claude-code-request-class: main und x-claude-code-prompt-id dazu. Die Doku nennt vier weitere, für Agent-Typ, Compaction und Tool-Laufzeiten, die nur bei diesen Ereignissen erscheinen. Keiner davon enthält Prompt-Text.
Dieser erste system-Eintrag ist der Attributionsblock. Die Claude API auf api.anthropic.com entfernt ihn, wenn er unverändert als erster System-Block ankommt; jeder andere Upstream bekommt ihn als Prompt-Text. Ein Gateway, das das system-Array umsortiert oder zu einem einzigen String zusammenfasst, schiebt den Block in den Prompt und in den Cache-Key. Eines, das anderen System-Text in diesen ersten Eintrag einfügt, ist noch schlimmer: Die API behandelt den zusammengeführten Eintrag als Attribution und verwirft ihn komplett. Muss das Gateway System-Inhalte umbauen, setz stattdessen CLAUDE_CODE_ATTRIBUTION_HEADER= auf den Clients.
Sieht ein Claude-Code-Gateway deinen claude.ai-Login?
Im Modus nur mit Base URL: ja. Der Bearer-Token in jedem Modell-Request meines ersten Laufs war kein Gateway-Key. Ohne API-Key auf dem Rechner ist es der gespeicherte claude.ai-Login, deshalb fährt die Beta oauth-2025-04-20 mit, und deshalb sagt die Doku Gateway-Betreibern, diesen Wert weiterzuleiten, sonst antwortet der Upstream mit 401. Wer den Proxy betreibt, bekommt mit jedem Request die Credential deines Abos.
Auf dem eigenen Rechner, mit einem Listener wie oben, der nichts weiterleitet, ist genau das der Weg, zu sehen, was deine Sessions senden, ohne fremde Konten anzufassen. Als Team-Design ist es etwas anderes. Ein geteiltes Gateway in diesem Modus sammelt die persönlichen Logins aller Entwickler und bedient den Traffic einer Organisation über individuelle Consumer-Pläne. Die Usage Policy, die am 12. November in Kraft tritt, nennt „services that route requests through consumer subscriptions“ unter den nicht erlaubten Wegen, Zugang bereitzustellen; das habe ich im Vergleich der Usage Policy durchgegangen. Ein Gateway, das weiß, wer anfragt, Budgets vergibt und Audit-Logs führt, braucht eigene Credentials, und die Abrechnung pro Token an ein Organisationskonto kommt mit ihnen.
Ein Gateway mit eigenen Credentials kostet mehr als die Rechnung. Claude Code in Slack und Cloud-Sessions deckt es gar nicht ab; muss der gesamte Traffic über das Gateway laufen, müssen diese Oberflächen für die betroffenen Nutzer aus sein. Das Gateway routet nur Modell-Requests: Versions-Checks, Telemetrie, der Fast-Mode-Check und der Domain-Check von WebFetch gehen weiter woandershin, solange du sie nicht abschaltest oder durchlässt. Und das Gateway wird zu Software, die du gegen jedes Claude-Code-Release pflegst.
Was so ein Gateway neben den Kontrollen bringt, ist eine bessere Einheit für die Kosten. x-claude-code-session-id gruppiert die Requests einer Session, ohne Bodies zu parsen, und die Agent-ID-Header zerlegen eine Session, die Subagents startet, in ihre Teile. Summen pro Entwickler sagen nur, wer Geld ausgegeben hat; so siehst du, welche Art von Aufgabe teuer ist. Die Doku warnt: Eine Agent-ID identifiziert einen Agenten, nie eine Person.
Was muss ein LLM-Gateway weiterleiten, damit Claude-Code-Funktionen laufen?
Geht es an einen Upstream im Anthropic-Format: alles, was mit anthropic- beginnt, den Request-Body unverändert und den Stream so, wie er ankommt. Laut Protokollseite sind das offene Listen. Claude Code fügt mit Releases anthropic-beta-Werte und Body-Felder hinzu, ein Gateway mit Allowlist für das, was es heute sieht, bricht also die nächste Funktion am Tag ihres Erscheinens. Ein Gateway, das Requests im Anthropic-Format annimmt, sie aber an Amazon Bedrock oder Google Cloud weiterleitet, hat die umgekehrte Aufgabe: Es übersetzt das Schema, oder es bietet den Clients stattdessen das eigene Format dieses Providers an. Jede halb weitergeleitete Funktion scheitert auf ihre eigene Art:
- Beta- und Body-Paare. Entfernst du einen Beta-Header, reichst aber sein Body-Feld durch, antwortet der Upstream mit
400, oftExtra inputs are not permitted. Context Management, Beta-Tool-Felder undoutput_confighängen alle so zusammen. Erst wenn beide Hälften fehlen, schaltet sich die Funktion still ab. - Prompt Caching. Fallen
cache_control-Marker weg oder verrutschen, oder wird Block-Content zu Strings, gibt es keinen Fehler: Jeder Turn wird als ungecachter Input abgerechnet. Das ist hier am schwersten zu bemerken, und mit der Diagnose aus dem Artikel Was deinen Prompt-Cache kaputt gemacht hat findest du es. - Streaming. Ein Gateway, das Antworten puffert, lässt Claude Code hängen, und eines, das SSE-
ping-Events entfernt, kann während einer langen Denkpause den Idle-Timeout auslösen. - Response-Header. Leite
anthropic-ratelimit-unified-*undx-should-retryunverändert weiter, gibretry-afterin Sekunden zurück und reich Fehler-Bodies so durch, wie sie sind. Claude Code erkennt eine abgelehnte Fähigkeit am Wortlaut des Upstreams. Die Rate-Limit-Header speisen außerdem die Anzeige der Plan-Nutzung, dieselben Zahlen wie in denrate_limits-Feldern der Statusline.
Ein Fehlerbild geht nicht auf das Konto des Gateways. Eine Web Application Firewall davor kann 403 mit HTML-Body liefern, während das Gateway nichts protokolliert, weil Prompts von Claude Code XML-artige Tags und Quellcode enthalten, die Body-Regeln gegen Cross-Site-Scripting auslösen. Ein kurzer curl-Test besteht, eine echte Session scheitert. Nimm /v1/messages von der Body-Inspektion aus.
Kannst du nicht alles weiterleiten, stoppt CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS= die meisten Vorab-Funktionen. Eine Kompatibilitätsgarantie ist das nicht: Der OAuth-Wert, erweiterter Kontext, Interleaved Thinking, Effort und das adaptive thinking-Feld bleiben, ebenso alles, was ein Entwickler über ANTHROPIC_BETAS oder CLAUDE_CODE_EXTRA_BODY hinzufügt.
Wie bindet allowedProviders Claude Code an ein Gateway?
Es verweigert jede Session, die woandershin zeigt. Setz allowedProviders in den Managed Settings auf ["customEndpoint"] und pinne die Adresse des Gateways im env-Block derselben Datei. Claude Code akzeptiert ANTHROPIC_BASE_URL dann nur mit genau diesem Wert und lehnt eine direkte Verbindung zu Anthropic, einen Cloud-Provider oder den eigenen Proxy eines Entwicklers ab, beim Start, beim Login und beim nächsten API-Call, falls sich mitten in der Session etwas ändert. Das braucht Claude Code 2.1.285 oder neuer. Unter Linux ist die Datei /etc/, und der darin genannte Helper ist ein Skript, das der Admin installiert und das nur die Gateway-Credential des Entwicklers ausgibt:
{
"allowedProviders": ["customEndpoint"],
"env": {
"ANTHROPIC_BASE_URL": "https://llm-gateway.example.com"
},
"apiKeyHelper": "/usr/local/bin/get-gateway-key"
}Eine verwaltete ANTHROPIC_BASE_URL überschreibt schon den Shell-Export eines Entwicklers. Was allowedProviders dazu bringt, ist das Schließen der anderen Türen: Provider-Variablen wie CLAUDE_CODE_USE_BEDROCK, eine direkte Anmeldung, eine zweite Base URL. Das ist der Unterschied, für den ich in A prompt is not an invariant argumentiert habe: Die Regel sitzt dort, wo der Harness sie durchsetzt. Lass forceLoginMethod, forceLoginOrgUUID und forceLoginGatewayUrl aus den Managed Settings weg, wenn du eine Gateway-Credential verteilst. Jede davon blockiert die Credential beim Start, und Entwickler kommen nicht weiter.
Ob du sperrst, ist eine Governance-Frage, und die Sperre hat Kosten, die die Doku nicht ausbuchstabiert. Einen gepinnten Rechner habe ich nicht getestet, aber laut Settings-Referenz würde der localhost-Listener von oben dort abgelehnt, weil er die Base URL ändert. Ein Entwickler kann seinen Endpoint dann nicht mehr gegen einen Listener tauschen, um zu sehen, was eine Session sendet. Wenn er stattdessen in den Logs des Gateways debuggen soll, gib ihm dort Lesezugriff auf seine eigenen Sessions.
Was solltest du vor dem Rollout eines Claude-Code-Gateways prüfen?
Leg zuerst den Credential-Modus fest, denn er bestimmt, wer die Rechnung bekommt: Base URL allein für das eigene Debugging einer Person, eine Gateway-Credential für alles Geteilte. Leg dann die Cache-TTL bewusst fest, statt die fünf Minuten zu erben. Schick eine echte Session durchs Gateway, nicht nur curl, und prüf /status, die Zeit, bis die erste lange Antwort zu streamen beginnt, und die Cache-Read-Werte über ein paar Turns. Richte Claude Code einmal auf einen protokollierenden Listener und lies das gute Dutzend anthropic-beta-Werte, das es sendet. Dein Gateway muss alle durchlassen, plus die, die das nächste Release dazubringt.
01 / Diskussion
Hier gibt es keine Kommentarspalte. Diskussionen laufen auf X.
02 / Weitere Artikel
Claude Code Fork-Subagents in claude -p: was 2.1.302 für Headless-Runs geändert hat
Angeboten heißt nicht benutzt: Ein Modell im Headless-Modus wählt den Fork vielleicht nie, und die offizielle Doku beschreibt noch den alten Standard. Wie du prüfst, welchen Subagent deine Pipeline genutzt hat, warum ein Fork im Hintergrund einem Parser zwei Antworten liefert und wo sich das Abschalten lohnt.
Claude-Code-Statusline und ccusage: die nativen Rate Limits und ein 5-Stunden-Block, der zu einer anderen Zeit endet
Zwei Tools beantworten dieselbe Frage mit unterschiedlichen Belegen: Der Server meldet eine Reset-Zeit, ein Log-Parser leitet eine ab. Liegen sie Stunden auseinander, glaub dem Server und behalte den Parser für die Historie. Der Dollarbetrag ist im Abo ein Wechselkurs.
Claude Code vs Cursor: sieben Arten, wie Entwickler die Arbeit aufteilen, seit keines der beiden Tools an eine Oberfläche gebunden ist
Beide Hersteller haben die alte Grenze zwischen Editor und Terminal von ihrer Seite aus aufgelöst. Was sie noch trennt, ist unauffälliger: ein Konfigurationsordner, den Cursor live liest, während Claude Code die Cursor-Regeln einmalig importiert, und Berechtigungssysteme, die die Deny-Regeln des jeweils anderen ignorieren.