Claude Code can be pointed at any gateway that speaks the Anthropic Messages format. zer0pii's POST /v1/messages route is that format (source: apps/gateway/api/v1/proxy.py), so the setup is two environment variables plus, optionally, a third for your Anthropic key.
Coverage boundary, stated first
This works only in API-key mode. If you use Claude Code with a claude.ai subscription login (Pro, Max, or a Team seat), the gateway is not in the path in any meaningful sense:
- Anthropic's documentation states that setting
ANTHROPIC_BASE_URLon its own "doesn't replace the subscription. Requests still route through the gateway, but a saved claude.ai login remains the active credential" (code.claude.com/docs/en/llm-gateway, section "Subscriptions and gateways", read 2026-09-22). In that configuration the request carries an OAuth token the gateway cannot substitute, and the traffic is billed and governed by the subscription. - Claude Code hooks cannot rewrite the prompt before it is sent or the reply after it arrives, so masking cannot be added to a subscription session from inside Claude Code either.
When a gateway credential is set, the subscription is not used: "the credential replaces the subscription login for that session, and the subscription's usage limits don't apply. That traffic is billed per token to whoever owns the credential the gateway forwards" (same page). So the zer0pii path is for teams whose Claude Code usage is billed to an Anthropic Console (API) account, or who are willing to move to that.
Setup
Variable names below are from code.claude.com/docs/en/llm-gateway-connect (read 2026-09-22).
1. Get a zer0pii key
Console, API keys (/console/keys), Issue key. Copy it now; it is shown once.
2. Pick the credential variable
Claude Code offers two, and they land in different headers:
| Variable | Sent as | Use with zer0pii? |
|---|---|---|
ANTHROPIC_AUTH_TOKEN |
Authorization: Bearer <value> |
Yes. zer0pii reads its key from Authorization: Bearer zpii_... (source: apps/gateway/core/auth.py). |
ANTHROPIC_API_KEY |
x-api-key: <value> |
No. The gateway does not read its own key from x-api-key; you would get 401 INVALID_API_KEY. |
The doc's own rule of thumb matches: "Your gateway team said 'bearer token' or 'Authorization header'" means ANTHROPIC_AUTH_TOKEN.
3. Set the base URL and the credential
The base URL is the gateway root without /v1. Claude Code appends /v1/messages itself (the connect page's verification command posts to $ANTHROPIC_BASE_URL/v1/messages).
macOS / Linux:
export ANTHROPIC_BASE_URL=https://api.zer0pii.com
export ANTHROPIC_AUTH_TOKEN=zpii_...
Windows PowerShell:
$env:ANTHROPIC_BASE_URL = "https://api.zer0pii.com"
$env:ANTHROPIC_AUTH_TOKEN = "zpii_..."
Or persistently, in ~/.claude/settings.json (the doc says a settings-file env value wins over a shell export):
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.zer0pii.com",
"ANTHROPIC_AUTH_TOKEN": "zpii_..."
}
}
Self-hosted: replace https://api.zer0pii.com with your gateway's origin.
4. Give the gateway your Anthropic key
Two options; pick one.
a. The gateway holds it (recommended for teams). Your admin sets UPSTREAM_ANTHROPIC_API_KEY in the gateway's .env (source: .env.example, apps/gateway/core/providers.py). Developers never handle the Anthropic key. Nothing more to set on the developer's machine.
b. Per developer, per request. Claude Code can attach extra headers with ANTHROPIC_CUSTOM_HEADERS (one Name: Value per line; documented on the same connect page). zer0pii reads the provider key from X-zer0pii-Upstream-Key (source: proxy.py):
export ANTHROPIC_CUSTOM_HEADERS="X-zer0pii-Upstream-Key: sk-ant-..."
In settings.json, separate multiple headers with \n inside the string. The same variable is where you would add X-zer0pii-Conversation-ID or X-zer0pii-Policy-Group if you use them.
5. Verify
Start claude from the same shell, send any prompt, then run /status. On the Status tab the doc says to check two lines: Anthropic base URL should show the gateway, and an Auth token line naming ANTHROPIC_AUTH_TOKEN confirms the gateway credential is active "rather than a saved claude.ai login". If a Login method line names a claude.ai account instead, you are still on the subscription and nothing is masked: run /logout to clear the saved login, as the doc advises.
Then follow verify.md to prove masking end to end.
Known limits of this path
Read before rolling out; each is a fact about today's gateway, not a promise.
anthropic-versionis fixed by the gateway. The gateway sendsanthropic-version: 2023-06-01to Anthropic (source:core/providers.py,auth_headers) and does not forward theanthropic-versionoranthropic-betaheaders Claude Code sets. Anthropic's compatibility guide asks gateways to forwardanthropic-betaverbatim because some Claude Code capabilities ride on it (code.claude.com/docs/en/llm-gateway-protocol, read 2026-09-22). Expect features that depend on a beta header to be unavailable through zer0pii until the gateway forwards it. If a feature you rely on stops working behind the gateway, this is the first thing to check./v1/messages/count_tokensis not served. Claude Code lists it as optional. Token counting that depends on it will fall back or fail; inference does not./v1/modelsdiscovery is not served. Claude Code may query it at startup to extend the/modelpicker; the doc says discovery is optional. Built-in models still work.- The
?beta=truequery string Claude Code adds to/v1/messagesis accepted and dropped; the gateway forwards to.../v1/messageswithout it. - Code is not masked. Variable names, package names, code blocks and syntax are left alone by design; only a value that looks like a credential is redacted (project rule,
CLAUDE.md, "Code resilience"). - Long prompts. The contextual name detector runs on text up to a length your admin configures on the gateway (default 2000 characters per scanned field, see
.env.example); beyond that only the pattern-based detectors run, and the response headerX-zer0pii-Modestill readsstandard. Structured identifiers (emails, phones, IDs, cards, keys) are caught at any length.