Skip to content

Repository files navigation

CPA-Claude

A native Claude (Anthropic) + OpenAI Codex API proxy. Fans out client requests across multiple upstream credentials (OAuth + API keys) on two independent HTTP endpoints — one per provider, shared pool infrastructure — with per-user sticky slot allocation, per-credential proxies, persistent usage tracking, and automatic API-key fallback. Admin panel, client tokens, request log, pricing, and stats are shared across providers; credentials are strictly separated per upstream.

Codex OAuth validation pending. The ChatGPT-backend proxy (/v1/responses path) is implemented end-to-end — header set, body sanitization, usage extraction, plan-tier model listing — mirroring CLIProxyAPI's upstream implementation. It has not yet been validated in production with a real ChatGPT Plus/Pro subscription token; the auth-layer test paths (token exchange, refresh, JWT parsing) are exercised, but full request/response parity against chatgpt.com/backend-api is pending a real-token smoke. If you hit an unexpected 400, please open an issue with the error body.

Credit. This project is a derivative of CLIProxyAPI (MIT). The original supports many AI providers (Gemini, Codex/OpenAI, Qwen, Kimi, iFlow, Antigravity, Vertex, Claude, …) and ships a management UI, TUI, translator layer, and multi-protocol entry points. CPA-Claude keeps the Claude + Codex passthroughs and adds slot-based concurrency, per-credential SOCKS/HTTP proxies, persistent usage tracking, and per-provider endpoint routing. The Anthropic OAuth refresh flow, the Codex OAuth flow + JWT parsing, and the uTLS Chrome transport were borrowed from the upstream project — huge thanks to its authors.

Features

  • Two endpoints, one fleet — Claude on :8317 (/v1/messages) and Codex on :8318 (/v1/chat/completions, /v1/responses). Each can be enabled/disabled independently; the admin panel mounts on whichever is "primary" (Claude by default).
  • Native Claude passthrough — no protocol translation for the Anthropic path, pure passthrough to api.anthropic.com/v1/messages (SSE streaming preserved).
  • Slot-based concurrency per OAuth file — each OAuth credential has a max_concurrent cap. A client session that makes a request within the last 10 minutes (configurable) occupies one slot. Idle clients release their slot automatically.
  • Per-credential upstream proxy — every OAuth file may set its own proxy_url, useful when different accounts must egress from different IPs.
  • Persistent usage tracking — input/output/cache-read/cache-write token counts per credential survive restarts in state.json.
  • API-key fallback — when every OAuth credential is saturated, quota- exceeded, or dead, requests fall through to the unlimited API-key pool.
  • Codex passthrough — OpenAI-format /v1/chat/completions and /v1/responses on the Codex endpoint. API-key credentials forward to api.openai.com; OAuth (ChatGPT Plus/Pro/Team) credentials forward to chatgpt.com/backend-api/codex/responses with the session/account headers the Codex CLI sends. /v1/models synthesizes the plan-tier catalog per subscription.
  • Per-client weekly budgets — each access token can have a weekly_usd cap that applies across both providers. Spend is tracked by ISO week and costed with a built-in pricing table covering Claude (Haiku 4.5, Opus 4.6/4.7, Sonnet 4.6) and OpenAI (gpt-5, gpt-5-mini, gpt-5-nano, gpt-5.x codex tiers, gpt-4o/mini) — cache-read and cache-create priced separately. Exceeded → 429 until the next Monday.
  • Per-request JSONL log — one line per terminal request (who, which credential, model, tokens, cost, status, duration). Daily-rotated file, default 30-day retention.
  • uTLS (Chrome) — bypasses Anthropic's TLS fingerprinting.
  • Proxy schemeshttp://, https://, socks5://, socks5h:// all supported, both with and without uTLS.

Install

One-liner (Linux/macOS, amd64/arm64) — installs to /usr/local/bin:

curl -fsSL https://raw.githubusercontent.com/wjsoj/CPA-Claude/main/install.sh | bash

Pin a version or change prefix:

curl -fsSL https://raw.githubusercontent.com/wjsoj/CPA-Claude/main/install.sh \
  | bash -s -- --version v0.1.0 --prefix ~/.local

Re-run the same command to upgrade.

From source:

go install github.com/wjsoj/CPA-Claude/cmd/server@latest
# binary will be named "server"; rename to cpa-claude or adjust PATH usage.

Quick start

cp config.example.yaml config.yaml
# edit config.yaml, populate ./auths/*.json, then:
cpa-claude -config config.yaml

OAuth credential file format

./auths/<any-name>.json:

{
  "type": "claude",
  "access_token": "sk-ant-oat01-...",
  "refresh_token": "sk-ant-ort01-...",
  "expired": "2026-05-01T12:00:00Z",
  "email": "alice@example.com",
  "label": "alice",
  "proxy_url": "http://10.0.0.1:3128",
  "max_concurrent": 5,
  "disabled": false
}
  • access_token is auto-refreshed on every request if within 5 minutes of expired. The refreshed token (and new expired) is written back to the file atomically.
  • max_concurrent: 0 means unlimited for this OAuth (not usually what you want — set it to match the account's practical parallelism).

Endpoints

Method Path Notes
POST /v1/messages Streaming (stream:true) OK
POST /v1/messages/count_tokens Pass-through
GET /status Auth pool + usage snapshot
GET /healthz Liveness
GET /admin/ Web admin panel (if configured)

Admin panel

Set admin_token in config.yaml and open http://<host>:<port>/admin/. The panel is a single embedded HTML page (Preact + Tailwind via CDN) that lets you:

  • view every OAuth / API-key credential with live slot usage, quota status, expiry, and accumulated token usage;
  • toggle disabled, edit max_concurrent and proxy_url, rename labels;
  • force-refresh an OAuth token, clear a quota-exceeded flag;
  • upload a new OAuth JSON file or delete one.
  • Sign in with Claude — initiates the Anthropic OAuth flow (PKCE) in a new tab, lets you paste the callback URL back, exchanges the code for tokens, and saves a new credential file. Optional proxy for the token exchange.

API keys are read-only in v1 — edit config.yaml and restart to change them.

Request log format

Each line in logs/requests-YYYY-MM-DD.jsonl:

{"ts":"2026-04-14T10:23:45.123Z","client":"alice-laptop",
 "client_token":"sk-cli…yyyy","auth_id":"alice.json","auth_label":"alice",
 "auth_kind":"oauth","model":"claude-sonnet-4-6",
 "input_tokens":1234,"output_tokens":567,
 "cache_read_tokens":100,"cache_create_tokens":50,
 "cost_usd":0.01155,"status":200,"duration_ms":1842,
 "stream":true,"path":"/v1/messages","attempts":1}
  • client is the name from access_tokens (or the budget label as fallback), empty if neither set.
  • client_token is masked (first 6 + last 4 chars).
  • attempts > 1 when an initial credential was quota-flagged and the request fell through to another one.
  • One line per terminal outcome (success or final 4xx/5xx). Intermediate retries are not logged as separate entries.

Parse with e.g. jq -c 'select(.client=="alice-laptop" and .model!="unknown") | [.ts,.model,.cost_usd] | @tsv' logs/requests-*.jsonl.

Compatibility with upstream CLIProxyAPI

OAuth JSON files produced by the original CLIProxyAPI — both the manual claude setup-token output and files created by its own login flow — drop into auth_dir unchanged. Extra keys (id_token, last_refresh, etc.) are preserved on save. The only caveat: files imported from upstream have no max_concurrent, so they load as unlimited (). Set a cap in the admin panel if you want slot-based routing to take effect.

Clients authenticate with Authorization: Bearer <token> matching one of the access_tokens in config.yaml. Each distinct token is one "client session" and occupies one OAuth slot while active.

Routing / fallback semantics

On each request for a client token:

  1. If that client already has a sticky OAuth assignment and that OAuth is healthy (not disabled, not quota-exceeded), reuse it.
  2. Otherwise pick the healthy OAuth with the fewest active sessions that still has spare max_concurrent capacity.
  3. If no OAuth has capacity, pick any usable API key.
  4. If the upstream returns 401/403/429/529, the credential is flagged (quota-exceeded where applicable) and the request is retried on a different credential (up to 4 attempts total).

License

MIT. See LICENSE for the full text and the attribution note to CLIProxyAPI.

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages