proxyagent docs

Run any agent on any machine — with no API key on the machine.

proxyagent is a secure, self-hosted proxy. It holds your real provider keys in one place; every machine that runs an agent gets only a scoped, revocable pa_ token. The real key never leaves the proxy.

What it is

Every agent harness honours a *_BASE_URL env var. Point that base URL at proxyagent and hand the agent a machine token as its "API key". proxyagent authenticates the token, checks its scope/budget, swaps in the real key, forwards upstream, and logs the call.

  remote machine                proxy (you host)          upstream
┌───────────────┐  pa_ token  ┌─────────────────┐ real key ┌──────────┐
│ claude / codex │ ─────────►  │ proxyagent serve │ ───────► │ Anthropic│
│  (no real key) │ ◄─────────  │ scope·budget·log │ ◄─────── │  OpenAI  │
└───────────────┘   stream    └─────────────────┘          └──────────┘

1 · Install

pip install proxyagent
# or, isolated as a CLI tool:
uv tool install proxyagent

2 · Run the proxy

Run this on a box you control — it's the only place real keys live. It prints an admin token and serves this dashboard.

proxyagent serve
# → Admin token: pa_admin_…   ·   Dashboard: __ORIGIN__
Reveal the admin token anytime with proxyagent admin-token. Paste it into the dashboard login to manage keys, tokens, routing and activity.

3 · Add a real provider key

Three ways — pick one. The key is stored encrypted (set PROXYAGENT_SECRET_KEY) or read from the environment; it's never returned or logged.

# a) environment (simplest):
export ANTHROPIC_API_KEY=sk-ant-…
export OPENAI_API_KEY=sk-…

# b) CLI (stored, encrypted):
proxyagent provider add anthropic --key sk-ant-…
proxyagent provider add openai    --key sk-…

# c) the dashboard → Access keys → + Create access key (API key / OAuth / Bedrock / Azure / Vertex)

A provider is a pool: add several keys (and auth types) and proxyagent rotates + fails over across them on 429/5xx.

4 · Mint a machine token

This is what the remote machine holds. Scope it, give it a TTL/budget, revoke it anytime.

proxyagent token new macbook-01 --scope "anthropic:claude-*" --budget 5.00
# → pa_… (shown once)

Or mint from the dashboard's Machine tokens tab (it shows a ready-to-run curl and a copy-.env snippet).

5 · Run an agent — token + prompt, that's it

This is the whole point: hand the SDK a token and a prompt, and it launches a real coding agent (the actual Claude Code / Codex CLI) on this machine against that prompt — with no API key here. Every model call the agent makes routes through the proxy, which holds the real key.

# pip install proxyagent — then:
import proxyagent
proxyagent.run("build a SwiftUI todo app and run the tests",
               token="pa_…", proxy="__ORIGIN__")
# → launches Claude Code here, keyless. harness="codex" for Codex,
#   command="my-agent {goal}" for any custom agent.

Supported agents (install the CLI you want)

npm i -g @anthropic-ai/claude-code     # Claude Code  → harness="claude-code" (default)
npm i -g @openai/codex                  # Codex        → harness="codex"   (or: brew install codex)

proxyagent drives the real CLI locally and wires it to the proxy for you — Claude Code via ANTHROPIC_BASE_URL; Codex via a one-off model provider (it ignores OPENAI_BASE_URL). Both are verified end-to-end: every call lands on the proxy, keyless.

From the CLI

PROXYAGENT_TOKEN=pa_… proxyagent run codex \
  --goal "fix the failing tests" --proxy __ORIGIN__
PROXYAGENT_TOKEN=pa_… proxyagent run claude-code --goal "ship it" --proxy __ORIGIN__

Claude Code by hand (just env)

export ANTHROPIC_BASE_URL=__ORIGIN__/anthropic
export ANTHROPIC_API_KEY=pa_…        # the machine token, NOT the real key
claude -p "ship it"
# (Codex needs a model-provider override — just use `proxyagent run codex`.)

SDK — manage the proxy programmatically

Mint tokens, manage credentials, and host the proxy from Python:

import proxyagent
app   = proxyagent.create_app()                     # ASGI app — embed in your service
admin = proxyagent.Admin("__ORIGIN__", "pa_admin_…")
tok   = admin.mint("laptop", scope=["anthropic:claude-*"], ttl_seconds=3600)

# …then run an agent with that token (the headline call):
proxyagent.run("build the app", token=tok, proxy="__ORIGIN__")

Raw HTTP / curl

curl __ORIGIN__/anthropic/v1/messages \
  -H "x-api-key: pa_…" \
  -d '{"model":"claude-3-5-haiku","max_tokens":100,
       "messages":[{"role":"user","content":"hello"}]}'

OpenAI-shaped providers use /<provider>/v1/chat/completions; Anthropic uses /anthropic/v1/messages. List routable models at /v1/models.

Offline mode (no keys, no spend)

Map every model to the built-in mock and the whole pipeline (auth, scope, budget, logging) runs end-to-end with zero keys — perfect for local dev, demos and CI.

proxyagent alias set '*' mock
curl __ORIGIN__/anthropic/v1/messages -H "x-api-key: pa_…" \
  -d '{"model":"mock","max_tokens":50,"messages":[{"role":"user","content":"hi"}]}'

Proxied tools & the agentic loop

proxyagent can hold your tool keys too. Send x-proxyagent-tools: on and it injects tool defs, then (non-streaming) runs the whole loop server-side: the model asks for a tool → proxyagent executes it (its key stays here) → feeds the result back → repeats until a final answer.

export TAVILY_API_KEY=tvly-…                 # web_search uses this; agents never see it
curl __ORIGIN__/anthropic/v1/messages \
  -H "x-api-key: pa_…" -H "x-proxyagent-tools: on" \
  -d '{"model":"claude-3-5-haiku","max_tokens":300,
       "messages":[{"role":"user","content":"what happened today?"}]}'
# response header x-proxyagent-tool-steps: N  ·  cap with x-proxyagent-tool-steps-max

Budgets, limits & security

proxyagent token new ci --budget 5.00            # per-token $ cap → 402 when hit
proxyagent token new ci --scope "anthropic:claude-*" --ttl 3600 --rate 60
export PROXYAGENT_PROVIDER_BUDGETS='{"anthropic":200}' # per-provider $ ceiling
export PROXYAGENT_PROVIDER_RATE_LIMITS='{"openai":1000}'
export PROXYAGENT_BUDGET_WEBHOOK=https://hooks.you.com/… # alert on cap crossing

Tokens are stored hashed, scoped (provider:model globs), expiring, revocable, rate-limited, and optionally IP-locked (allowed_ips CIDRs). Real keys are read from env / stored encrypted and never logged.

Deploy

docker compose up -d                 # proxy at :8080; admin token in logs
docker compose --profile postgres up -d   # shared Postgres backend
# point at Postgres directly:
export PROXYAGENT_DATABASE_URL=postgresql://user:pass@host/db

Health: GET /healthz (liveness) · GET /readyz (DB-ping readiness, 503 if down). Metrics: GET /metrics (Prometheus, incl. a latency histogram).

Endpoint reference

EndpointAuthWhat
POST /<provider>/v1/chat/completionsmachine tokenOpenAI-shaped proxy
POST /<provider>/v1/messagesmachine tokenAnthropic-shaped proxy
GET /v1/modelsmachine tokenroutable model catalog
GET /healthz · /readyznoneliveness / readiness
GET /metricsadmin*Prometheus metrics
/admin/tokens · /admin/providersadminmanage tokens / credentials
GET /admin/summaryadminMarkdown status report

Environment variables

VarPurpose
ANTHROPIC_API_KEY, OPENAI_API_KEY, …real upstream keys (env source)
PROXYAGENT_ADMIN_TOKENfixed admin token (else auto-generated)
PROXYAGENT_SECRET_KEYFernet key → encrypt stored creds at rest
PROXYAGENT_DATABASE_URLPostgres DSN (default: local SQLite)
PROXYAGENT_CACHE_TTLresponse cache TTL seconds (0 = off)
PROXYAGENT_PROVIDER_BUDGETS / _RATE_LIMITSper-provider $ ceiling / req-per-min
PROXYAGENT_MAX_BODY_BYTESreject larger request bodies (413)
PROXYAGENT_CORS_ORIGINSallow browser clients (CORS)
PROXYAGENT_BUDGET_WEBHOOK / _EVENT_WEBHOOK / _WEBHOOK_SECRETalerts + lifecycle events (HMAC-signed)
PROXYAGENT_LOG_RETENTION_DAYSauto-trim the audit log on boot

proxyagent · Apache-2.0 · github.com/teddyoweh/proxyagent