# Migrating Claude Code to a custom backend in 2 lines (and what to actually watch for)

> Source: <https://dev.to/midrelay/migrating-claude-code-to-a-custom-backend-in-2-lines-and-what-to-actually-watch-for-1e0g>
> Published: 2026-06-06 04:52:03+00:00

**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.

```
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.

I've found five patterns that justify a custom backend:

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%.

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.

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.

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.

Different team members hitting different cost centers? A proxy can map API keys to billing tags and forward usage to finance.

The 2-line setup works for the happy path. In practice, here's what bites you.

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.

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:

`anthropic-version`

→ SDK gets default-version response which may not match its expectations`anthropic-beta`

→ prompt cache headers ignored, you wonder why caching isn't working`x-stainless-*`

→ harmlessRule of thumb: forward everything except `host`

and `authorization`

(since you're rewriting those).

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).

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:

``` js
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.

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.

If you're building this proxy yourself:

`host`

/`authorization`

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.

When you put a proxy in front of Claude Code, you get **observability for free**. You can see:

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.

A few cases where a custom backend is more trouble than it's worth:

`claude`

once a day — direct API is fineThe break-even point is roughly:

Below that, just use the real Anthropic API and pocket the engineering time.

I built [MidRelay](https://midrelay.com) 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.*
