Multi-tenant LLM Gateway + Web Console based on Next.js + SQLite.
better-sqlite3)channels)models) + weighted random load balancingusers) with admin/user role splitkeys) for gateway authchat_logs) with model/channel/status/token/latencysettings.registration_enabled)GET /api/v1/modelsPOST /api/v1/chat/completionsnpm install npm run dev
Open http://localhost:3000.
Optional, with defaults:
JWT_ACCESS_SECRET (default: dev-access-secret-change-me)JWT_REFRESH_SECRET (default: dev-refresh-secret-change-me)JWT_ACCESS_EXPIRES_SECONDS (default: 900)JWT_REFRESH_EXPIRES_SECONDS (default: 604800)SQLite DB path:
data/gateway.dbTables are auto-initialized on first run:
channelsmodelsuserskeyssettingschat_logsPOST /api/auth/register
settings.registration_enabledadminPOST /api/auth/loginPOST /api/auth/refreshGET /api/auth/mePOST /api/auth/change-passwordPOST /api/auth/logoutWeb pages use JWT Bearer from browser localStorage.
Login/register uses username + password (no email/name field).
Username only allows English letters and numbers ([A-Za-z0-9]).
/api/admin/*): admin only/api/user/*): authenticated user, scoped to own resources/api/v1/*): API key auth (Authorization: Bearer sk-gw-...)/admin/channels (provider base URL + provider API key)/admin/modelscurl -sS http://localhost:3000/api/v1/models \
-H 'Authorization: Bearer sk-gw-xxxx'
curl -sS http://localhost:3000/api/v1/chat/completions \
-H 'Authorization: Bearer sk-gw-xxxx' \
-H 'Content-Type: application/json' \
-d '{
"model": "deepseek-v3.2",
"messages": [{"role": "user", "content": "hello"}]
}'
TPM for non-stream response prefers upstream usage.total_tokens when available.
For stream response (stream=true), usage is estimated before forwarding.
Rate limit is in-memory and single-instance only. Admin log endpoint:
GET /api/admin/logs/chat (summary + recent chat records)