Portara sits between your applications and the model providers behind them and exposes a single OpenAI-compatible endpoint. Credentials, rate limits, quotas, routing and cost accounting all live in this layer, so no upstream secret ever appears in business code. One process serves the data plane, the admin API and the web console on the same port.
- Single endpoint — the
/v1/*data plane, the/api/*control plane and the Vue console share one process and one port; a single container is a complete deployment. - OpenAI-compatible streaming — chat, completions, embeddings, responses and model listing; SSE bytes are forwarded as-is instead of being re-serialized on the proxy path.
- Channel × account routing with session affinity — an alias maps one gateway model to one or more channel/model pairs, each channel holds multiple accounts ranked by priority and weight, and a client-supplied
prompt_cache_keypins a conversation to the same(channel, account). - Balance & quota adapters — vendor balance and usage windows are read by small fetch/parse pairs, so exhausted or cooling targets are skipped before they are used.
- OpenAI ⇄ Anthropic translation — the channel decides which protocol the upstream speaks; clients only ever speak OpenAI.
- Encrypted key storage with audit — upstream keys are sealed with AES-256-GCM, and sensitive reads and writes are recorded and fail closed.
- Bilingual, hand-written UI — English and Chinese copy through i18n, custom CSS and hand-built controls, with no UI framework.
Node.js 22.12+ and pnpm 11 are required; PostgreSQL and Redis come from Compose.
cp .env.example .env # set PORTARA_MASTER_KEY and PORTARA_SESSION_SECRET
docker compose up -d postgres redis
pnpm install && pnpm build && pnpm startOpen http://127.0.0.1:52740/, sign in with the administrator account from .env, issue a key
and call the gateway with any OpenAI SDK.
Prerequisites: Node.js >= 22.12 and pnpm 11 (corepack enable && corepack prepare pnpm@11 --activate),
plus Docker with Compose v2; PostgreSQL and Redis come from Compose.
| Path | What lives there |
|---|---|
server/src/routes/ · services/ |
/v1/* data plane, /api/* control plane, routing, quota, audit |
server/src/migrations.ts |
Ordered, additive, idempotent SQL migrations |
web/src/views/ · components/ · i18n/ |
Console pages, hand-built controls and charts, EN/ZH copy |
scripts/smoke.mjs · scripts/ui/ |
End-to-end suite; headless-browser UI checks |
docker/ · Dockerfile.caddy |
Caddyfile and entrypoint; multi-stage Caddy + Node image |
Dependencies — both bind loopback only, 127.0.0.1:52744 and :52745:
docker compose up -d postgres redisTwo ways to run it — the first serves sources, the second serves built artifacts:
pnpm dev # terminal 1: tsx watch on :52740
pnpm dev:web # terminal 2: Vite on :52741, proxies /api and /v1 to :52740
pnpm build && pnpm start # single port :52740, server/dist + web/dist, no watcherspnpm start runs server/dist, not the sources: after touching server/src or web/src you
must rebuild, or a stale dist keeps answering and your change looks ignored — the easiest trap here.
Environment — cp .env.example .env, then set PORTARA_MASTER_KEY (openssl rand -base64 32,
must decode to exactly 32 bytes) and PORTARA_SESSION_SECRET (openssl rand -hex 32). The
administrator account is created on first start only and only while that username is absent;
changing PORTARA_ADMIN_PASSWORD later does not rotate it.
Checks before a PR — four gates, then two UI passes against a running instance:
pnpm typecheck && pnpm build && pnpm test # gates 1-3
pnpm smoke # gate 4: PG + Redis, isolated schema
pnpm ui:check && pnpm ui:keys # pages render; no raw i18n keys on screenpnpm smoke spawns server/dist/index.js (build first) and refuses to run unless
PORTARA_DB_SCHEMA contains smoke, so it never touches the development database. The UI checks
need playwright-core — pnpm add -D playwright-core && npx playwright-core install chromium-headless-shell, or set PLAYWRIGHT_CORE_PATH to an existing install and
CHROME_HEADLESS_SHELL to an existing browser; export PORTARA_ADMIN_PASSWORD for the sign-in.
Database changes — migrations live in server/src/migrations.ts: additive only, never edit a
published one, each must be idempotent, and a change of meaning must backfill existing rows in the
same migration. Destructive steps need a snapshot; tests always use their own schema.
Docker — docker compose --profile app up -d --build builds Dockerfile.caddy (Caddy +
console + Node) and publishes it on ${PORTARA_HTTP_PORT:-52740}; or pull
ghcr.io/crequency/portara:0.5.0.
CI — pushes to main and PRs run verify (typecheck → build → test → smoke); a vX.Y.Z
tag also builds Dockerfile.caddy and pushes it to GHCR with semver tags (0.5.0, 0.5, 0).
flowchart LR
C["Client<br/>OpenAI SDK"] --> A["Auth · Rate limit · Quota<br/>Key / RPM / Token"]
A --> R["Routing<br/>alias mapping · channel passthrough"]
R --> P["Account selection<br/>priority · weight · affinity"]
P --> T["Protocol planning<br/>OpenAI / Anthropic"]
T --> U["Upstream<br/>OpenAI-compatible · Anthropic"]
U --> L["Accounting<br/>request log · usage · audit"]
L --> C
Routing happens twice: the alias mapping yields an ordered list of channels, then account selection yields an ordered list of accounts inside the chosen channel. Failover walks those lists, and only retryable status codes move a request to the next target.
| Endpoint | Description |
|---|---|
POST /v1/chat/completions |
Chat completions, including stream: true over SSE |
GET /v1/models |
Models declared by every enabled channel |
GET /v1/usage |
Balance and usage snapshots for the calling key |
GET /healthz |
Liveness of the process |
GET /readyz |
Readiness, including PostgreSQL and Redis |
/api/* |
Admin control plane used by the console |
Other /v1/* paths pass through the same authentication, rate limiting and accounting.
- Envelope encryption — channel and account credentials are sealed with AES-256-GCM under a master key supplied through
PORTARA_MASTER_KEY. - Hashed and encrypted client keys — authentication uses a SHA-256 hash, while a separate sealed copy lets an administrator reveal a key on demand; every reveal is audited.
- Fail-closed audit — an operation that cannot be written to the audit log fails instead of proceeding silently.
- Short-lived tokens — access tokens are stateless JWTs measured in minutes; long sessions ride revocable, rotating refresh tokens.
- Additive migrations only — published migrations are never edited and corrections always arrive as new ones.
AGPL-3.0. Running a modified copy as a network service requires offering its complete corresponding source.
Copyright © 2025-2026 Crequency Open Source Organization (云矩创频开源组织)
Portara (πόρτα, "door") is the great marble door frame that is all that remains of the Temple of Apollo on Naxos: no walls, no hall, only the entrance itself. This project is meant to be the same kind of thing — every AI request passes through it, and the door itself should never be a burden.