CAPI is a self-hosted AI gateway rewritten as a single Go binary. It exposes OpenAI-compatible APIs, workspace API keys, provider routing, usage/billing, local file storage, async video task polling, health checks, backups, and an embedded web console.
The runtime has no Node.js or Bun dependency.
clients
│
├─ OpenAI Chat / Responses / Anthropic Messages / Gemini / Images / Video
▼
Go HTTP server
│
├─ Auth + Workspace
├─ Local routing + audit + redaction
├─ Router (priority + weight + cooldown)
├─ Provider adapters
├─ Usage / negative-allowed wallet ledger
├─ File storage
└─ Video worker
│
├─ SQLite
└─ data/files
The gateway follows the architectural lesson from Magpie: wire protocols are kept separate from providers and routing. CAPI remains server-oriented and multi-tenant rather than copying Magpie's local desktop state model.
Requires Go 1.23+.
go mod tidy
make devOpen http://127.0.0.1:3210.
On first startup with a previous Bun database, CAPI creates a SQLite snapshot
under data/backups/legacy-*/capi.sqlite, archives the original tables as
legacy_*, and imports accounts, sessions, workspaces, wallets, API keys,
channels, usage and video tasks into the Go schema in one transaction. Existing
IDs and API key hashes are preserved; wallet quota units are converted to USD
micros. Existing Argon2id passwords remain valid and are upgraded after a
successful login. A failed import rolls back the schema changes. Legacy-only
configuration remains in the archived tables; the Go channel uses the first
configured upstream key.
The first registration creates a personal workspace and wallet. The administrator email is managed in the database under Admin > System settings.
Use the web console or:
curl -X POST http://127.0.0.1:3210/api/workspaces/$WORKSPACE_ID/channels \
-b capi_session=... \
-H 'Content-Type: application/json' \
-d '{
"name":"OpenAI",
"protocol":"openai",
"base_url":"https://api.openai.com/v1",
"api_key":"sk-...",
"models":["gpt-5.6"],
"priority":10,
"weight":1
}'If models is omitted, CAPI asks the provider's live model endpoint and stores the discovered IDs. Enabled channel model IDs are the source of truth for /v1/models and the web Models page.
Supported user-facing routes:
GET /v1/modelsPOST /v1/chat/completionsPOST /v1/responsesPOST /v1/messages(Anthropic Messages, including streaming and tool use)POST /v1beta/models/{model}:generateContentPOST /v1beta/models/{model}:streamGenerateContent(Gemini SSE, including function calling)POST /v1/images/generationsPOST /v1/images/editsPOST /v1/videosGET /v1/tasks/{id}POST /v1/filesGET /v1/filesPOST /v1/systemoneGET /v1/me/balanceGET /v1/me/usage
API keys are workspace-scoped. Wallet balances intentionally may become negative; billing is settled after upstream usage is known.
For a requested model CAPI:
- selects enabled platform or workspace channels that advertise the model;
- takes the highest priority tier;
- chooses by configured weight;
- temporarily rests channels after network, rate-limit, credit, auth, or upstream failures;
- retries another available channel.
Conversation affinity is supported through OpenAI prompt_cache_key or X-CAPI-Session. Recent routing decisions are exposed from GET /api/workspaces/{wid}/routes, including candidates, attempts and the selected channel.
Native openai, anthropic and gemini channels participate in the same router. CAPI translates messages, streaming text, tool/function calls and tool results across these protocols.
Use POST /v1/systemone with the llm.systemone scope to call TypeSafe AI / JEV or a compatible System One provider. The TypeSafe preset maps typesafe-ai/jev to jev-latest at https://api.typesafe.ai/v1/systemone. Channels support systemonePath and systemoneProtocol (generic or typesafe). The old /v1/evaluate endpoint has been removed.
Workspace automatic routing and input security audits use local rules. They do not call an external evaluator; audits record masked evidence without blocking requests.
Enable redaction in Admin > System settings to replace common secrets and personal identifiers with stable local placeholders before requests leave CAPI. Placeholders are restored on normal and streamed responses.
CAPI can use a ChatGPT account that is already signed in through the official Codex CLI as a separate chatgpt-subscription channel. It is intentionally kept distinct from normal OpenAI API-key channels.
Import the contents of Codex CLI's auth.json into a workspace:
POST /api/workspaces/{workspace_id}/chatgpt-subscription
Content-Type: application/json
{
"auth_json": { "...": "contents of Codex auth.json" },
"priority": 20,
"weight": 1
}CAPI refreshes the OAuth token when needed, discovers the account's Codex models and exposes them as codex/<model>. Requests use the Responses API through the ChatGPT Codex backend.
Current allowance and reset windows are available from:
GET /api/workspaces/{workspace_id}/chatgpt-subscription/{channel_id}/quotaThe response includes the plan, primary/secondary allowance windows, reset times and reset credits when the account reports them.
capi doctor
capi migrate
capi backup/api/healthzis liveness./api/readyzchecks SQLite and persistent file storage, and reportsdatabase/sqlpool counters (pool.max_open,pool.wait_count,pool.wait_duration_ms, …) so read/write queueing is observable in production.- logs are structured JSON via
slog. - The alert webhook configured in Admin > System settings receives de-duplicated readiness failures.
capi backupuses SQLiteVACUUM INTOand copies stored files into timestampeddata/backups/directories. If SMTP credentials are configured, it also copies the matchingsmtp.keyencryption key into the backup with owner-only permissions; keep the backup directory protected because it can decrypt that credential. No externalsqlite3executable is required.- SQLite connection pool: WAL mode allows concurrent readers plus one writer. The default pool opens
clamp(GOMAXPROCS, 4, 16)connections, so short local reads (API key lookup, channel scan, settings, pricing) overlap instead of queueing behind a single connection; every connection runs the same PRAGMAs (WAL,foreign_keys,busy_timeout(5000)). All transactions take the write lock immediately (_txlock=immediate), so concurrent writers queue on the busy timeout rather than failing withdatabase is locked, and multi-step billing (reservation, settlement, wallet ledger) stays atomic. SetCAPI_DB_MAX_CONNS(minimum 1) to override the default on memory-constrained deployments; the migration/backup/import flows are unchanged.
docker compose up -d --buildThe container is a small distroless runtime with one persistent /app/data volume.
With the server running:
make smokeThe smoke verifies health, readiness, registration/login, session cookies, and workspace persistence. Provider calls are intentionally not made by default.
Build capi-admin as a server-side tool for direct SQLite administration. It does not expose an HTTP admin endpoint or require an administrator login. Run it through SSH on the server; it creates a database backup before every mutation.
./capi-admin list
./capi-admin set-role capi@jisuhudong.com admin
./capi-admin set-password capi@jisuhudong.com 'new-password'
./capi-admin set-balance capi@jisuhudong.com 10The deployed binary uses CAPI_DATA_PATH or the default data directory. Override the database location with --data-path. Passwords should be supplied only over an SSH session and may be visible in local shell history when passed as an argument.
Runtime configuration is stored in SQLite and loaded from the database at request or task execution time. The only server environment variable is CAPI_DATA_PATH, which selects the persistent data directory. The HTTP listen address is read from the database but requires a process restart to take effect because it controls socket creation.
The React console source lives in web/; Vite generates
internal/webui/dist/ locally, and the release build embeds it into the Go
binary. Generated assets are ignored by Git. Commit the frontend source and
lockfile, never the generated distribution. See web/migration.json for the
page inventory and verification status.
Running a release binary needs no Node runtime. Building or developing the frontend requires Node 22.12+:
make web-build
make buildmake dev builds the frontend and starts Go. For hot reload, start make dev and
make web-dev in separate terminals.
The frontend dev server on port 3211 proxies same-origin API requests to Go on 3210.
Docker and CI build the frontend before compiling Go. Commit frontend source and
the lockfile; do not commit generated assets. A plain go build omits the React
console; use make build or the Docker build for a release binary with the UI.
cmd/capi/ binary entry point
internal/auth/ passwords, sessions, API tokens
internal/protocol/ wire protocol normalization / translation
internal/provider/ channel and model registry
internal/router/ priority, weights, cooldown/fallback
internal/redact/ request masking and response restoration
internal/server/ control plane + /v1 APIs
internal/store/ SQLite + migrations
internal/worker/ async video polling and cleanup
internal/ops/ logging, alerts, backup
internal/webui/ embedded web console
web/ React frontend source and migration inventory
- The intended deployment is still a single CAPI process. Routing cooldown and affinity state are in memory; durable usage, tasks and account configuration stay in SQLite.
- ChatGPT subscription channels are Responses-only and are namespaced as
codex/*; they are not presented as generic OpenAI API credits. - Provider-specific features outside the shared text/tool/function-call surface may still pass through only when client and upstream protocols match.