token-proxy
Multi-provider API token proxy: an authentication gateway with key rotation and request logging, providing unified access for AI clients such as Claude Code.
Why
Claude Code only sends Authorization: Bearer <token> to custom endpoints, but some upstreams (e.g. opencode go) only accept x-api-key and enforce risk controls on the Claude Code User-Agent. token-proxy acts as a middle layer that:
- Authentication — multi-user (username + key) with three auth methods, ready for teams
- Header conversion — Bearer → the header the upstream requires (
x-api-key, etc.) - User-Agent spoofing — bypass upstream risk controls on AI-client user agents
- Key rotation — multiple upstream keys rotated round-robin to spread quotas
- Multiple upstreams — adapter-based support for anthropic / openai-compatible endpoints, fully configurable
- Full logging — complete request/response details stored in SQLite, with token usage & cost per request
Features
- SQLite-backed management (users / backends / key pools / routes / models / aliases / admins) — single
token-proxy.<env>.db, WAL mode, high performance - Zero-DB hot path — auth/routing/backends/models are served from an in-memory snapshot that hot-reloads within ~1s of any admin change (no restart)
- Usage & cost tracking — input/output tokens and cost parsed from streaming and non-streaming responses, with per-model pricing
- Model management — catalog, pricing, aliases (client names rewritten to real model names), enable/disable, and per-model backend routing
- Auto model sync — the catalog auto-syncs from OpenCode Zen / Go every 12 hours (or on demand via a one-click button in the console), keeping models per backend with official pricing when available
- Request log archiving — expired logs are gzip-archived instead of deleted (downloadable
.db.gz/.db, selectable time range, super-admin only), keeping history without growing the main database - Web admin console (
/admin) — dashboard, users, backends, routes, models, request logs, settings, audit log, and maintenance; fully internationalized (English primary, 中文 available) - Environment isolation — dev / test / prod each with its own config file, port and database, selected via
.env - pm2 cluster — production deployment with 2 instances by default
Architecture
Client (Claude Code / codex / ...)
│ auth: Bearer user[@backend]:key (Basic / x-api-key also supported)
▼
token-proxy (Fastify, prod :3501)
│ ① parse auth {user, key, upstream}
│ ② pick adapter by route (anthropic / openai / anthropic-to-openai)
│ ③ header conversion + UA spoofing + key round-robin + model resolution
▼
Upstream (opencode go / DeepSeek / OpenAI-compatible...)
Data:
config config.<env>.json (project root / XDG config)
database token-proxy.<env>.db (WAL, users/backends/routes/models/logs)
text logs logs/token-proxy.YYYY-MM-DD.log (per day)
admin console http://<host>:<port>/admin
Quick start
Requirements
- Node.js >= 24 (
volta install node@24.16.0recommended)
Install & run
Option 1 — CLI global install
npm install -g token-proxy
token-proxy init # create config (prints the initial admin password once)
token-proxy set-password # set the super-admin password (scrypt hash)
token-proxy start # start in the background
token-proxy logs # tail request logs
token-proxy status # show status
token-proxy stop # stop
Option 2 — from source
npm install
npm run dev # dev with hot reload (port 3502)
npm test # run all tests
# production with pm2 (cluster, 2 instances, port 3501)
pm2 start ecosystem.config.cjs
pm2 logs token-proxy
Environments
The run environment comes from .env (TOKEN_PROXY_ENV, default dev), copied from .env.example. Each environment is isolated:
| Env | Config file | Database | Default port |
|---|---|---|---|
| prod | config.prod.json |
token-proxy.prod.db |
3501 |
| dev | config.dev.json |
token-proxy.dev.db |
3502 |
| test | config.test.json |
token-proxy.test.db |
3503 |
Config files live in the project root (source mode) or ~/.config/token-proxy/ (CLI global mode). First boot migrates any legacy config.json users/backends/routes into the database.
Client setup (Claude Code)
export ANTHROPIC_BASE_URL="http://<host>:<port>/v1"
export ANTHROPIC_AUTH_TOKEN="username:your-api-key"
Token format: username[@backend]:key — the optional @backend overrides the backend bound to the route.
Configuration
config.json (or config.<env>.json) only holds static settings:
{
"server": { "host": "0.0.0.0", "port": 3502, "apiPrefix": "/v1", "basePath": "" },
"admin": { "username": "admin", "passwordHash": "$scrypt$16384,8,1$..." },
"security": { "sessionTtlHours": 168 },
"auth": { "methods": ["bearer", "basic", "apiKey"] },
"logging": {
"directory": "./logs",
"retentionDays": 365,
"detailRetentionDays": 30,
"enableDetailDatabase": true,
"enableTextLog": true
},
"database": { "path": "" }
}
Everything else — users, backends, key pools, routes, models, aliases, admins — is managed from the web console (/admin), stored in SQLite, and applied without restart.
Web console
Open http://<host>:<port>/admin and sign in with the super-admin credentials from config.json. Features:
- Home — welcome page with system overview and quick-start snippets
- Dashboard — request volume, success rate, latency, token & cost trends (7/30 days), top users, status distribution, recent requests
- Users — create/disable users, reset keys (shown once), quotas, per-user usage stats
- Backends — upstream connection info, key pool management, connectivity tests
- Routes — entry path → protocol adapter → backend
- Models — catalog, pricing, aliases, per-model backend routing, enable/disable
- Requests — paginated logs with multi-field filters and request/response body details
- Settings — auth methods, log retention, body storage, sub-admins, audit log, VACUUM
Language (English / 中文) is switchable in the sidebar and persisted in the browser.
CLI
token-proxy start | stop | restart | status
token-proxy logs [N]
token-proxy stats
token-proxy init
token-proxy set-password
token-proxy migrate # import legacy per-day log databases
token-proxy backup # online SQLite backup
Development
npm run dev # hot reload on port 3502
npm run check # syntax check
npm test # all tests (unit + integration)
npm run cli -- --help
Structure:
src/
├── index.js # entry: Fastify assembly, forwarding, lifecycle
├── config.js # config hot-reload + validation (server/admin/security/logging/database)
├── env.js # environment (.env) + per-env defaults
├── daemon.js # process daemon (pid file + detached)
├── db/ # SQLite: schema/migration, records, legacy import
├── store/ # in-memory snapshot + 1s hot-reload polling
├── usage.js # token usage & cost parsing
├── auth/ # inbound auth (bearer / basic / x-api-key)
├── upstream/ # routing, backend selection, adapters, protocol conversion
├── logger/ # text summary + SQLite detail logs
└── admin/ # web console: sessions, REST API, static SPA (i18n)
Security
- Super-admin password is stored in
config.jsonas a scrypt hash (never plaintext); set it withtoken-proxy set-password - The server refuses to start without admin credentials (prevents an open login)
- Upstream keys are stored locally in SQLite (file mode
0600) and masked in the UI; log in with caution on shared hosts - Login rate-limiting (5 failures locks for 5 minutes) and full audit logging
- For public deployments, put nginx + HTTPS in front