Transparent reverse proxy for Claude Code API calls to api.anthropic.com.
anthropic-proxy sits between Claude Code (or any Anthropic API client) and api.anthropic.com, forwarding all requests over HTTPS with HTTP/2. It provides request metrics, token tracking, a live dashboard, and an optional secret scrubber that detects and pseudonymizes credentials before they reach the upstream API.
# Install dependencies (requires uv)
make install
# Generate a self-signed TLS certificate
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt \
-days 365 -nodes -subj "/CN=localhost"
# Run locally
make runThe proxy listens on https://127.0.0.1:8443 by default. Point your client at this address instead of https://api.anthropic.com.
All settings are controlled via environment variables prefixed with ANTHROPIC_PROXY_. Pydantic Settings handles parsing and validation.
| Variable | Default | Description |
|---|---|---|
ANTHROPIC_PROXY_HOST |
0.0.0.0 |
Bind address |
ANTHROPIC_PROXY_PORT |
8443 |
HTTPS listen port |
ANTHROPIC_PROXY_DASHBOARD_PORT |
9090 |
Plain HTTP port for the dashboard |
ANTHROPIC_PROXY_SHUTDOWN_TIMEOUT |
30.0 |
Seconds to wait for active requests to drain on shutdown |
| Variable | Default | Description |
|---|---|---|
ANTHROPIC_PROXY_LOG_LEVEL |
info |
Python log level (debug, info, warning, error) |
ANTHROPIC_PROXY_LOG_REQUESTS |
false |
Log every proxied request/response |
ANTHROPIC_PROXY_LOG_SLOW_THRESHOLD |
5.0 |
Seconds; requests slower than this are logged at WARNING |
| Variable | Default | Description |
|---|---|---|
ANTHROPIC_PROXY_UPSTREAM_BASE_URL |
https://api.anthropic.com |
Upstream API base URL |
ANTHROPIC_PROXY_UPSTREAM_CONNECT_TIMEOUT |
10.0 |
TCP connect timeout (seconds) |
ANTHROPIC_PROXY_UPSTREAM_READ_TIMEOUT |
300.0 |
Read timeout (seconds) |
ANTHROPIC_PROXY_UPSTREAM_WRITE_TIMEOUT |
30.0 |
Write timeout (seconds) |
ANTHROPIC_PROXY_UPSTREAM_POOL_TIMEOUT |
10.0 |
Connection pool timeout (seconds) |
ANTHROPIC_PROXY_UPSTREAM_MAX_CONNECTIONS |
20 |
Max concurrent connections to upstream |
ANTHROPIC_PROXY_UPSTREAM_MAX_KEEPALIVE_CONNECTIONS |
10 |
Max keepalive connections in pool |
| Variable | Default | Description |
|---|---|---|
ANTHROPIC_PROXY_SCRUB_MODE |
off |
off or pseudonymize |
ANTHROPIC_PROXY_SCRUB_HMAC_KEY |
(empty) | 64-char hex key for HMAC pseudonymization |
ANTHROPIC_PROXY_SCRUB_MAX_BODY_SIZE |
10485760 |
Max request body size to scrub (bytes; 10 MB) |
The scrubber intercepts POST request bodies, detects secrets and identifiers using regex-based detectors, and replaces them with deterministic placeholders before forwarding to the upstream API.
Set two environment variables:
ANTHROPIC_PROXY_SCRUB_MODE=pseudonymize
ANTHROPIC_PROXY_SCRUB_HMAC_KEY=<64-char hex key>python3 -c "import secrets; print(secrets.token_hex(32))"The scrubber runs 16 detectors in priority order. Earlier detectors take precedence on overlapping matches.
| # | Detector | Example Match | Tier |
|---|---|---|---|
| 1 | anthropic_key |
sk-ant-api03-abc... |
SECRET |
| 2 | openai_key |
sk-proj-abc123... |
SECRET |
| 3 | aws_access_key |
AKIAIOSFODNN7EXAMPLE |
SECRET |
| 4 | github_token |
ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
SECRET |
| 5 | google_api_key |
AIzaSyA1234567890abcdefghijklmnopqrstuv |
SECRET |
| 6 | stripe_key |
sk_live_abc123def456ghi789... |
SECRET |
| 7 | private_key |
-----BEGIN RSA PRIVATE KEY----- |
SECRET |
| 8 | jwt |
eyJhbGci...eyJzdWIi...signature |
SECRET |
| 9 | db_connection_string |
postgres://user:pass@host/db |
SECRET |
| 10 | bearer_token |
Bearer eyJhbGciOiJIUz... |
SECRET |
| 11 | basic_auth |
Basic dXNlcjpwYXNz... |
SECRET |
| 12 | sonarqube_token |
sqp_a1b2c3d4e5f6... |
SECRET |
| 13 | jenkins_api_token |
11abcdef0123456789abcdef01234567 |
SECRET |
| 14 | ansible_vault |
$ANSIBLE_VAULT;1.1;AES256 |
SECRET |
| 15 | hashicorp_vault_token |
hvs.ABCDEFghijklmnop123456 |
SECRET |
| 16 | email |
user@example.com |
IDENTIFIER |
Secrets (counter-based, per request): Replaced with <PLACEHOLDER_N> where N increments per type within a single request.
sk-ant-api03-abcdef... -> <ANTHROPIC_KEY_1>
ghp_xxxxxxxxxxxx... -> <GITHUB_TOKEN_1>
Identifiers (HMAC-based, deterministic): Replaced with <PLACEHOLDER_HMAC:HEXHEX> using a truncated HMAC-SHA256 digest. The same input always produces the same placeholder (given the same key), enabling correlation across requests.
user@example.com -> <EMAIL_HMAC:A3F1B2C0>
- JSON skip-set keys:
model,role,type,stop_sequences,temperature,top_p,max_tokens,top_k,stream,metadata,input_schema. Values under these keys are never scrubbed. - Tool definition keys:
nameanddescriptionare additionally skipped insidetoolsarrays. - Image data: Base64 image payloads (
{"type": "base64", "data": "..."}) are left untouched. - Non-JSON bodies: Bodies that fail JSON parsing are passed through unchanged.
- Oversized bodies: Bodies exceeding
SCRUB_MAX_BODY_SIZE(default 10 MB) are passed through unchanged. - Non-POST requests: Only POST request bodies are scrubbed.
| Endpoint | Port | Description |
|---|---|---|
GET /health |
8443 | Returns {"status": "ok"} |
GET /metrics |
8443 | JSON snapshot of request counts, error rates, bytes, tokens, scrub stats |
GET /metrics/stream |
8443, 9090 | SSE stream of metrics snapshots (every 1.5s) |
GET /dashboard |
8443, 9090 | Live HTML dashboard |
* /{path} |
8443 | Catch-all reverse proxy to upstream |
The live dashboard is a single-page HTML app served at /dashboard on both the main HTTPS port and the plain HTTP dashboard port (default 9090). It connects via SSE to /metrics/stream and displays:
- Requests per second and output tokens per second (time-series charts, last 60 samples)
- Error rate, active requests, uptime
- Data throughput (bytes in/out) and token totals (input/output)
- Status code breakdown table
- Alert banner for high error rate (>10%) or high active requests (>10)
An Ansible role is included under deploy/ansible/. It handles:
- Creating a dedicated system user (
anthropic-proxy) - Syncing source code to
/opt/anthropic-proxy/app/ - Installing
uvand runninguv sync - Deploying a
.envfile and systemd service unit - Configuring UFW firewall rules for both ports
- Health-checking the service after startup
# Deploy to hosts defined in deploy/ansible/inventory.ini
make deploy| Target | Description |
|---|---|
make install |
Install dependencies with uv sync |
make run |
Run proxy locally with debug logging and self-signed TLS |
make deploy |
Deploy via Ansible |
make logs |
Tail remote service logs |
make status |
Show remote systemd service status |
make health |
Curl the /health endpoint |
make metrics |
Curl the /metrics endpoint |
uv run pytestsrc/anthropic_proxy/
__init__.py
config.py # Settings and validation (pydantic-settings)
main.py # FastAPI app, proxy logic, metrics, dashboard server
scrubber.py # Secret detection and pseudonymization
dashboard_html.py # Inline HTML/JS/CSS for the live dashboard
tests/
test_scrubber.py
deploy/ansible/
playbook.yml
inventory.ini
roles/anthropic_proxy/
tasks/main.yml
templates/ # .env and systemd unit templates
defaults/main.yml
handlers/main.yml
- A client sends a request to
anthropic-proxyover HTTPS. - The proxy reads the request body. If scrubbing is enabled and the request is a POST, the scrubber parses the JSON body, walks the structure (skipping known-safe keys and image data), and replaces detected secrets with deterministic placeholders.
- The proxy forwards the request to
api.anthropic.comvia an HTTP/2 connection pool, streaming the response back to the client. - On 529 (overloaded) responses, the proxy retries up to 3 times with exponential backoff (1s, 2s, 4s).
- Metrics (request counts, status codes, bytes, tokens, scrub hits) are recorded and exposed via
/metricsand the SSE stream powering the dashboard.