TL;DR: Claude Code's ANTHROPIC_BASE_URL env var lets you redirect every request to any compatible backend in one shell line. Almost no one does it, but it unlocks caching, rate-limit pooling, multi-vendor routing, audit logging, and billing routing without rewriting any code. Here's the 2-line setup, the things that quietly break, and the production checklist for actually running it.
The 2-line setup
export ANTHROPIC_BASE_URL=https://your-proxy.example.com
export ANTHROPIC_API_KEY=your-key
claude
That's it. The Anthropic SDK (which Claude Code is built on) doesn't care where it's sending requests, as long as the response comes back in Anthropic Messages format. It'll append /v1/messages automatically, forward all your anthropic-version and anthropic-beta headers, and stream SSE the same way.
Same trick works with Cursor, Cline, Continue, and anything else that builds on the Anthropic SDK.
Why would you do this?
I've found five patterns that justify a custom backend:
1. Caching middleware
Anthropic's prompt cache feature requires manual cache_control markers that most teams skip. A proxy can inject them server-side on every system message. For a Claude Code session with a 20-30K-token system prompt and 50+ turns, this cuts your bill by 60-80%.
2. Rate-limit pooling
Most teams have 3-5 API keys spread across projects. Without a proxy, each is rate-limited independently — you can hit 429 on one while another sits idle. A proxy can round-robin across keys, exposing one virtual key with the combined rate budget.
3. Multi-vendor abstraction
Want to route claude-haiku-4-5 calls to one provider and claude-opus-4-7 calls to another (maybe one offers Haiku throughput discounts)? A proxy inspects the model field and routes accordingly. Your Claude Code config doesn't change.
4. Audit logging
Claude Code makes a lot of requests. Without instrumentation, you have no idea what each session cost. A proxy can log per-request metadata (model, tokens, latency, session id) to a warehouse.
5. Billing routing
Different team members hitting different cost centers? A proxy can map API keys to billing tags and forward usage to finance.
What breaks (and what to watch for)
The 2-line setup works for the happy path. In practice, here's what bites you.
Streaming behavior
Anthropic returns chunked SSE for stream: true. If your proxy buffers responses or doesn't flush each chunk immediately, you'll see massive perceived latency — Claude Code will spin while waiting for the full response.
Fix: pass through SSE chunks without buffering. In Caddy, that means flush_interval -1. In Cloudflare Workers, use TransformStream with explicit .write() per chunk. In Node, set noDelay: true on the response socket.
Header forwarding
Anthropic's SDK sends headers like:
anthropic-version-
anthropic-beta(for prompt cache, batches, etc.) -
x-api-key(auth) -
x-stainless-*(SDK telemetry)
If your proxy strips any of these before forwarding to the real upstream, certain features break silently:
- Strip
anthropic-version→ SDK gets default-version response which may not match its expectations - Strip
anthropic-beta→ prompt cache headers ignored, you wonder why caching isn't working - Strip
x-stainless-*→ harmless
Rule of thumb: forward everything except host and authorization (since you're rewriting those).
Tool use iteration loops
Claude Code uses tool calling heavily. A typical session has 30-50 tool call rounds, each one a separate API call with the full conversation history.
If your proxy is rate-limited at 60 RPM per source IP, a single Claude Code session can blow through it in 30 seconds.
Fix: rate-limit per API key, not per source IP. And budget for high burst rates (200+ RPM during heavy tool-call sessions).
Request body inspection
If you want to inject cache_control or rewrite request bodies, you have to parse the JSON, modify, re-serialize. Be careful: large requests (50K+ tokens of context as JSON-encoded strings) take noticeable CPU time to parse.
Minimal injection in Node:
const body = JSON.parse(rawBody);
// Inject cache_control on the first system message
if (body.system && typeof body.system === 'string') {
body.system = [{
type: 'text',
text: body.system,
cache_control: { type: 'ephemeral' },
}];
}
const newRawBody = JSON.stringify(body);
For Claude Code's typical traffic this adds <5ms per request. For very large workloads, consider stream-parsing.
Streaming error handling
Anthropic sends error SSE events mid-stream when something fails (rate limit, content filter, etc.). Most naive proxies forward the body as opaque bytes, which works. If your proxy tries to be "smart" and parse the SSE, you have to handle error events without breaking the connection.
The safe path: don't parse SSE in your proxy unless you absolutely have to.
A minimal production checklist
If you're building this proxy yourself:
- [ ] TLS termination (Let's Encrypt or Cloudflare front)
- [ ] Pass-through SSE streaming (no buffering)
- [ ] Per-key rate limiting (not per-IP)
- [ ] Forward all headers except
host/authorization - [ ] Handle JSON request body modification gracefully
- [ ] Log per-request metadata without storing message bodies (privacy)
- [ ] Retry on upstream 5xx with exponential backoff
- [ ] Health check endpoint that doesn't proxy
- [ ] Graceful degradation when upstream is down (return 502 fast, don't hang)
- [ ] CORS headers if you want browser clients to hit it directly (TypingMind etc.)
That's not "2 lines" anymore. The 2-line part is the SDK config; the checklist is the actual cost of running it in production.
The side benefit no one talks about
When you put a proxy in front of Claude Code, you get observability for free. You can see:
- Every request and its token count
- Which sessions are expensive
- Which prompts repeat (cache candidates)
- Tool-call patterns
- Latency distribution per model
- Time-of-day spend curves
Anthropic's dashboard tells you the bill. Your proxy tells you why. For any team running Claude Code at scale, that's often more valuable than the cost optimization itself.
When NOT to do this
A few cases where a custom backend is more trouble than it's worth:
- Single developer running
claudeonce a day — direct API is fine - Team < 5 people with simple usage patterns
- No engineering bandwidth to maintain a proxy
- Using Anthropic-specific beta features that might break in a proxy chain
The break-even point is roughly:
- You have > 3 API keys to manage, OR
- Monthly Claude bill > $500, OR
- You need cross-team usage analytics, OR
- You're integrating with multiple LLM vendors
Below that, just use the real Anthropic API and pocket the engineering time.
If you don't want to build it
I built MidRelay precisely because this checklist took 6 weeks to get right and I figured other teams shouldn't repeat it. Hosted proxy with both Anthropic and OpenAI surfaces, prompt cache injection on by default, per-key usage logs, CORS enabled for browser clients.
Pointing Claude Code at it is the 2-line setup at the top of this post.
But honestly: the techniques here work whether you use MidRelay, build your own, or pick any of the other gateways. The interesting thing is almost nobody knows you can point Claude Code at a custom backend, even though Anthropic explicitly documented it. If this post does nothing else but make you realize that capability exists, that's worth your reading time.
If you've built something similar, drop a comment with what bit you in production — I'm collecting patterns for a follow-up post on multi-vendor LLM routing.
Top comments (0)