Field notes for Claude Code users

Claude Code and ANTHROPIC_BASE_URL: routing the agent through a custom endpoint

One environment variable moves every Claude Code request off api.anthropic.com and onto a proxy, a corporate gateway, or a local model server. The variable itself is simple. The thing almost every guide leaves out is the part that actually trips people up: the variable is read exactly once, when the process starts, so a running agent will route to the old endpoint until you restart it. This page covers the variable, that lifecycle gotcha, and how a desktop host wires the same variable behind a toggle.

M
Matthew Diakonov
8 min read

Direct answer (verified 2026-05-15)

Set the ANTHROPIC_BASE_URL environment variable to the base URL of your endpoint. Claude Code reads it at startup and sends every API request there instead of to Anthropic. Pair it with ANTHROPIC_AUTH_TOKEN (sent as the Bearer token) or ANTHROPIC_API_KEY so the endpoint can authenticate the request. Configure it in your shell, in ~/.claude/settings.json under the env key, or have the host process inject it. The official reference describes the variable as “override the API endpoint to route requests through a proxy or gateway.”

Authoritative source: code.claude.com/docs/en/env-vars.

The variable, and the three places it can live

ANTHROPIC_BASE_URL does one thing: it changes the host Claude Code talks to. Everything else, the agent loop, the tool surface, the prompts, the model selection, is unchanged. The official environment-variable reference is precise about scope: the variable is to “override the API endpoint to route requests through a proxy or gateway.” It does not carry a credential. It does not pick a model. It is the address, nothing more.

There are three supported places to set it. The simplest is the shell you launch claude from:

# shell profile (~/.zshrc, ~/.bashrc) or the current terminal
export ANTHROPIC_BASE_URL="YOUR_ENDPOINT_BASE_URL"
export ANTHROPIC_AUTH_TOKEN="YOUR_ENDPOINT_TOKEN"
claude

The second is ~/.claude/settings.json, which applies the value to every session and is the cleanest way to roll a gateway out to a whole team. The official docs note you can set these variables in settings.json under the env key to apply them to every session:

// ~/.claude/settings.json
{
  "env": {
    "ANTHROPIC_BASE_URL": "YOUR_ENDPOINT_BASE_URL",
    "ANTHROPIC_AUTH_TOKEN": "YOUR_ENDPOINT_TOKEN"
  }
}

The third is process-level injection: whatever launches Claude Code puts the variable into the child process environment directly. That is how an editor extension or a desktop wrapper does it, and it is the case the rest of this page is about.

One nuance worth knowing before you ship a gateway to a team: the docs state that when ANTHROPIC_BASE_URL points at a non-first-party host, MCP tool search is disabled by default. Tool search relies on the upstream forwarding tool_reference blocks, and a proxy may strip them. If yours forwards them intact, you can turn it back on with ENABLE_TOOL_SEARCH=true.

The part the other guides skip: the variable is read once

Here is the failure mode that sends people in circles. You set ANTHROPIC_BASE_URL, you test it, it works. Later you change the URL, or you turn the gateway off, and Claude Code keeps hitting the old endpoint. You re-check your shell profile, your settings.json, everything looks right. Nothing is broken. The variable is just doing exactly what environment variables do.

Environment variables are copied into a process at the moment it is spawned. From that point the process holds its own private snapshot. Editing your shell profile, exporting a new value in another terminal, or rewriting settings.json does not reach into an already-running process. Claude Code reads ANTHROPIC_BASE_URL at startup and keeps that value until it exits. A change only takes effect on the next launch.

For the bare CLI that is mild: you quit and relaunch claude. For anything that runs Claude Code as a long-lived background process, it is the whole design problem. The host cannot just update a value; it has to tear down and respawn the child. Here is the sequence, including the step where an edit silently does nothing:

ANTHROPIC_BASE_URL across the process lifecycle

Shell or hostClaude Code processCustom endpointspawn: ANTHROPIC_BASE_URL frozen into process envevery request goes to the base URLstreamed completions backedit the URL while running: not seenrestart: respawn with the new valuerequests now route to the new endpoint

The red message is the one that costs people an afternoon. There is no error, no warning, no log line. The edit lands in a file or a shell, the running process keeps its old snapshot, and the only signal you get is that traffic is still hitting the host you thought you changed.

How a desktop wrapper wires the same variable

Fazm is a native macOS app I work on. It runs the real Claude Code agent loop, not a reimplementation, by speaking ACP (the Agent Client Protocol) to a Node bridge that owns the Claude Code process. Because the bridge spawns that process, it also owns the environment, which means a custom endpoint becomes a setting instead of a shell ritual. The source is MIT-licensed at github.com/m13v/fazm, so every claim below has a file and a line number.

Anchor fact, traceable in the repo

The setting is a toggle plus a text field in Settings → Advanced → AI Chat, labelled Custom API Endpoint. The field carries a placeholder hint, an example proxy address on port 8766, and the help text reads: route API calls through a custom endpoint, for example a local LLM bridge, corporate proxy, or GitHub Copilot bridge. Leave it empty to use the default Anthropic API. That UI lives in Desktop/Sources/MainWindow/Pages/SettingsPage.swift and writes to the customApiEndpoint key.

When the bridge spawns the Claude Code process, it reads that key and injects the environment variable. The whole mechanism is four lines in Desktop/Sources/Chat/ACPBridge.swift:

// Desktop/Sources/Chat/ACPBridge.swift, line 527
// Custom API endpoint (allows proxying through Copilot,
// corporate gateways, etc.)
if let customEndpoint = defaults.string(forKey: "customApiEndpoint"),
   !customEndpoint.isEmpty {
  env["ANTHROPIC_BASE_URL"] = customEndpoint
}

That is the same ANTHROPIC_BASE_URL you would export by hand, set on the process environment of the real Claude Code agent. No fork of the loop, no proxy of its own, just the variable placed where the CLI already looks for it.

The lifecycle problem from the previous section does not disappear, it gets handled. Editing the field or toggling it off calls a function named restartBridgeForEndpointChange() in ChatProvider.swift. It stops the ACP bridge so the next query respawns the Claude Code process with the updated environment. The code comment is blunt about why: “Stop the ACP bridge so it picks up the new custom API endpoint on next start.” That is the tear-down-and-respawn step that the red arrow in the diagram demands, done for you instead of left as a manual quit-and-relaunch.

So the practical difference is not magic. It is that the variable, the three-place configuration, and the mandatory restart are collapsed into one toggle. You type a URL, the host injects ANTHROPIC_BASE_URL and restarts the bridge; you clear the URL, it injects nothing and restarts again, back to the default API.

What the thing on the other end has to be

ANTHROPIC_BASE_URL only moves the address. It does not translate anything. Claude Code keeps speaking the Anthropic Messages API, so whatever answers at that address has to speak it too. That rules out pointing the variable straight at a plain OpenAI-style chat endpoint: the request shapes do not match, and you need a translation shim in between. The checklist for an endpoint that will actually work:

Custom endpoint requirements

  • Speaks the Anthropic Messages API: accepts POST to the messages path with server-sent-event streaming
  • Is reachable from your machine, over HTTP or HTTPS, at the exact base URL you set
  • Accepts the credential Claude Code sends, set via ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY
  • Preserves tool_use and tool_result blocks if you want the agent to run tools, not just chat
  • Returns the JSON shape Anthropic returns, or sits behind a shim that converts another provider into it

A corporate gateway built for Anthropic traffic, a regional endpoint, or a bridge that adapts another model into the Messages API all satisfy this. A local server such as LM Studio works once it is running an Anthropic-compatible API surface and has a model loaded. The variable does not care which of those it is; it just hands the request to the address and trusts the address to answer correctly.

When it breaks: reading the error from the right layer

A custom endpoint adds a layer, and the layer can fail on its own terms. The most confusing case is when the endpoint is reachable, so the connection succeeds, but the server behind it is not ready. With a local model server the classic version of this is an error that says, in effect, no models loaded, use the load command. That is not a Claude Code bug and not an Anthropic outage. The HTTP server answered; it just has nothing to serve yet.

Raw, that error reaches you as an opaque API failure and people blame the wrong layer. Fazm intercepts it. When a custom endpoint is configured, the bridge inspects the upstream error text and, if it sees markers like “no models loaded” or “lms load”, it rewrites the message into something actionable, along these lines:

Your custom API endpoint (<your endpoint>) reported no model
is loaded. Load a model in your local server (e.g. LM Studio ->
Developer -> Load Model), or turn off Custom API Endpoint in
Settings -> Advanced -> AI Chat to use Fazm's built-in Claude.

It does the same for connection-refused failures: if the endpoint is unreachable, the message names the endpoint and points you at the toggle to fall back to the default API. The logic is in the same ACPBridge.swift file, in the error path that runs only when customApiEndpoint is set. The principle generalizes whether or not you use a wrapper: when a custom endpoint misbehaves, separate the three layers before you debug.

Connection refused or DNS failure is a network or address problem, check the base URL. A 401 or 403 is a credential problem, check ANTHROPIC_AUTH_TOKEN against what the endpoint expects. A 400 about request shape, or an error mentioning no model, is the endpoint or the server behind it, not Claude Code. And if a change you made seems ignored entirely, it is the lifecycle gotcha: the process has not been restarted.

Routing Claude Code through a gateway and want it to be a toggle?

Twenty minutes on how Fazm injects ANTHROPIC_BASE_URL into the real Claude Code agent process, handles the restart the variable lifecycle needs, and translates custom-endpoint errors into something you can act on.

Frequently asked questions

What is the difference between ANTHROPIC_BASE_URL and ANTHROPIC_API_KEY?

They are independent. ANTHROPIC_BASE_URL changes where Claude Code sends requests: set it and every API call goes to that host instead of api.anthropic.com. ANTHROPIC_API_KEY changes how the request is authenticated: it is sent as the x-api-key header and, when present, overrides your Claude subscription. A custom endpoint usually needs both, plus often ANTHROPIC_AUTH_TOKEN, which the official docs describe as a custom value for the Authorization header that gets prefixed with Bearer. Routing without a matching credential gives you a connection that opens and then fails the first request with a 401 or 403.

Why does Claude Code ignore my ANTHROPIC_BASE_URL change?

Because environment variables are read once, when the process starts. Claude Code reads ANTHROPIC_BASE_URL at startup and keeps that value for the life of the process. If you edit your shell profile, change ~/.claude/settings.json, or export a new value in a different terminal while an agent is mid-session, the running process never sees it. You have to fully exit and relaunch claude. Any tool that wraps Claude Code as a long-lived subprocess has the same constraint: it has to kill and respawn the child process for a new base URL to take effect.

Does ANTHROPIC_BASE_URL work with my Claude Pro or Max subscription?

It depends on what is on the other end. ANTHROPIC_BASE_URL only changes the destination host. If your custom endpoint is a transparent proxy that forwards to Anthropic and you are still authenticated through your subscription login, your usage continues to draw from your plan. If the endpoint is a gateway that injects its own API key, or a local model server, then you are no longer using your subscription at all for those requests, you are using whatever that endpoint bills against. Read the endpoint, not the variable, to know which plan a request consumes.

Why did MCP tool search stop working after I set a custom endpoint?

This is documented behavior. The official Claude Code environment-variable reference states that when ANTHROPIC_BASE_URL is set to a non-first-party host, MCP tool search is disabled by default. The reason is that tool search depends on the upstream forwarding tool_reference blocks, which a proxy may strip or not understand. If your proxy does forward those blocks intact, you can re-enable it by setting ENABLE_TOOL_SEARCH=true. If it does not, leaving tool search off is the safe default.

Can I route Claude Code through GitHub Copilot or a corporate proxy?

Yes, as long as the thing on the other end speaks the Anthropic Messages API. ANTHROPIC_BASE_URL is the supported way to send Claude Code traffic through a company-managed proxy, a regional endpoint, an AI gateway, or a bridge that adapts another provider. The agent loop does not change. What changes is the network path and, often, the billing and the logging. The endpoint has to accept POST requests to the messages path, stream server-sent events back, and preserve tool_use and tool_result blocks if you want the agent to call tools.

How do I set a custom endpoint without editing shell files?

Use a host that exposes it as a setting. Fazm, the native macOS app this site documents, has a Custom API Endpoint toggle under Settings, Advanced, AI Chat. You type the URL into a text field, Fazm stores it under the customApiEndpoint key, and when it next spawns the Claude Code agent process it injects env ANTHROPIC_BASE_URL with that value. Toggling the field off clears it and restarts the bridge so the next request goes back to the default Anthropic API. No .zshrc edit, no settings.json edit, and the restart that the variable lifecycle requires happens for you.

What does a “no models loaded” error from a custom endpoint mean?

It means Claude Code reached your endpoint and the endpoint answered, but the server behind it has no model ready to serve. This is common with local model servers like LM Studio: the HTTP server is up, so the connection succeeds, but you have not loaded a model into it yet. The fix is on the server side, load a model, not in Claude Code. Fazm detects this specific failure (it watches for the strings “no models loaded” and “lms load” in the upstream error) and rewrites the raw API error into a message that tells you to load a model or turn the custom endpoint off, so you do not waste time blaming the wrong layer.

How did this page land for you?

React to reveal totals

Comments ()

Leave a comment to see what others are saying.

Public and anonymous. No signup.