Sidecar plugin for CLIProxyAPI that adds:
- per-API-key expiration management
- durable usage snapshot persistence
- restart recovery for usage statistics
- Telegram bot operations for key and usage management
- multi-user identity RBAC for HTTP and Telegram
- built-in Web UI (
/web) with user/admin role views - purchase request workflow and usage control rules
This plugin is built as a companion extension for the upstream project:
- Stores API key metadata in SQLite (
status,expires_at,note,audit). - Periodically computes active keys and syncs them to CLIProxyAPI via:
PUT /v0/management/api-keys
- Periodically fetches usage snapshot from:
GET /v0/management/usage/exportand persists it.
- Restores usage after restarts using:
POST /v0/management/usage/import
- Exposes HTTP operations API and optional Telegram bot commands.
remote-management.secret-keymust be configured.usage-statistics-enabled: true(otherwise usage export is empty/minimal).- Sidecar must be able to reach CLIProxyAPI management endpoint.
Required:
CLIPROXY_MGMT_BASE_URL(example:http://127.0.0.1:8317, Docker example:http://host.docker.internal:8317)CLIPROXY_MGMT_KEY
Recommended:
APIM_DATA_DIR(default./data)APIM_DB_PATH(default${APIM_DATA_DIR}/apim.db)APIM_HTTP_ADDR(default127.0.0.1:8390)APIM_HTTP_AUTH_TOKEN(optional bearer token for sidecar HTTP API)APIM_AUTH_SESSION_TTL(default24h)APIM_AUTH_COOKIE_NAME(defaultapim_session)APIM_AUTH_COOKIE_SECURE(defaultfalse, settruein production HTTPS)APIM_ADMIN_EMAIL/APIM_ADMIN_PASSWORD(optional idempotent bootstrap admin account for email login)APIM_KEY_SYNC_INTERVAL(default30s)APIM_USAGE_SYNC_INTERVAL(default2m)APIM_RECOVERY_CHECK_INTERVAL(default60s)
Telegram (optional):
TELEGRAM_BOT_TOKENTELEGRAM_ALLOWED_CHAT_IDS(comma-separated int64)TELEGRAM_ALLOWED_USER_IDS(comma-separated int64)TELEGRAM_POLL_INTERVAL(default3s)
Main-project update tracking/control:
APIM_UPDATE_CHECK_ENABLED(defaulttrue)APIM_UPDATE_CHECK_TIME(default04:00, UTC)- current CLIProxyAPI version is auto-detected from management response header
X-CPA-VERSION APIM_MANAGEMENT_LATEST_VERSION_URL(default/v0/management/latest-version)- semver comparison is used (
latest > currentonly) APIM_UPDATE_AUTO_APPLY_ENABLED(defaultfalse; when enabled, apply is triggered automatically afterupdate_available)APIM_UPDATE_APPLY_COMMAND(optional custom apply command)APIM_UPDATE_ALLOW_CUSTOM_COMMAND(defaultfalse; must be true to allowAPIM_UPDATE_APPLY_COMMAND)APIM_AUTO_UPDATE_ENABLED(defaulttrue)APIM_AUTO_UPDATE_SERVICE(defaultcliproxy)APIM_AUTO_UPDATE_COMPOSE_FILE(optional absolute compose file path)APIM_AUTO_UPDATE_WORKING_DIR(optional working directory for compose lookup)
Security hardening:
APIM_HTTP_UPDATE_REQUIRE_AUTH(defaulttrue; blocks/update/checkand/update/applywhenAPIM_HTTP_AUTH_TOKENis empty)APIM_TELEGRAM_DENY_HIGH_RISK_WHEN_ALLOWLIST_EMPTY(defaulttrue; deny high-risk write/update commands if Telegram allowlist is empty)
Recovery hardening:
APIM_DB_RECOVER_ON_CORRUPT(defaulttrue; backup corrupt DB and recreate)APIM_RECOVERY_SNAPSHOT_SCAN_LIMIT(default10; fallback scan depth for recovery snapshots)
Identities are stored in SQLite table auth_identities:
provider:httportelegramsubject:http: SHA-256 of tokentelegram: Telegram user id string
role:adminoruseremail: required foruser, optional foradminstatus: currently active on upsert
Email/password login users are stored in auth_users (email, role, password_hash, status, ...).
Browser/API sessions are stored in auth_sessions with session_hash only (plain session token is never persisted).
Authorization:
healthzis public.- HTTP protected endpoints resolve principal in this order:
- session cookie (
APIM_AUTH_COOKIE_NAME) - legacy token (
Authorization/X-APIM-Token):- bootstrap token (
APIM_HTTP_AUTH_TOKEN) => admin principal - mapped identity in
auth_identities
- bootstrap token (
- session cookie (
- Telegram requires allowlist pass + identity mapping.
Permission matrix:
admin:- HTTP: all existing management endpoints +
/identities - HTTP v1: all
/api/v1/admin/* - Web UI:
/web/admin - Telegram: all management commands
- HTTP: all existing management endpoints +
user:- HTTP:
GET /me - HTTP v1:
/api/v1/session,/api/v1/user/*,/api/v1/purchase-requests*(self only) - Web UI:
/web/user - Telegram:
/help,/start,/me(self only)
- HTTP:
Admin:
/help/key_add <key> <email> <ttl|expires_at> [note]/key_extend <key> <ttl|expires_at>/key_disable <key>//key_enable <key>//key_delete <key>/key_list [active|expired|all]/usage_key <key> [24h|7d|duration]/usage_top [24h|7d|duration]/me <email> [24h|7d|duration]/recharge <email> [plan_or_note](placeholder)/sync_now/usage_sync_now/status/update_check/update_apply
User:
/help/start/me [24h|7d|duration]
TTL examples:
24h7d2026-03-01T00:00:00Znever
Legacy endpoints (kept for compatibility):
GET /healthzGET /statusPOST /sync_nowPOST /usage/sync_nowPOST /update/checkPOST /update/applyGET /keys?filter=all|active|expiredPOST /keys(supportsowner_email)DELETE /keys?key=<api-key>PATCH /keys/statusPATCH /keys/expiryGET /usage/top?since=24h|7d|<duration>GET /usage/key?key=<api-key>&since=24h|7d|<duration>GET /account/query?email=<email>&since=24h|7d|<duration>(admin)GET /me?since=24h|7d|<duration>(user self, also available to admin if email is bound)GET /identities?provider=http|telegram(admin)POST /identities(admin)DELETE /identities?provider=...&subject=...(admin)POST /recharge/request(now creates a purchase request record)
Web/UI API v1:
- Auth/session:
POST /api/v1/auth/register(email+password, createsrole=useronly)POST /api/v1/auth/login(email+password, sets HttpOnly session cookie)POST /api/v1/auth/logout(revoke current session + clear cookie)GET /api/v1/auth/me(resolved principal)GET /api/v1/session(compatibility alias for resolved principal)
- User (
requireUserand server-side self-scope):GET /api/v1/user/keys?since=24h|7d|<duration>GET /api/v1/user/usage?since=24h|7d|<duration>POST /api/v1/purchase-requestsGET /api/v1/purchase-requests/mine?limit=<n>
- Admin (
requireAdmin):GET /api/v1/admin/usage/overview?since=24h|7d|<duration>GET /api/v1/admin/usage/users?since=24h|7d|<duration>&limit=<n>GET /api/v1/admin/usage/users/{email}?since=24h|7d|<duration>GET|PATCH /api/v1/admin/purchase-requestsGET|POST|PATCH /api/v1/admin/usage-controlsPOST /api/v1/admin/usage-controls/evaluate-nowGET|POST|DELETE /api/v1/admin/identities(forwarded to legacy)GET|POST|DELETE /api/v1/admin/keysand/api/v1/admin/keys/*(forwarded to legacy)
Built-in Web UI routes:
GET /web/GET /web/login(unified login/register page)GET /web/userGET /web/adminGET /web/static/*
Web UI behavior:
- Login/register happens on
/web/login. - Successful login checks
/api/v1/auth/meand auto-routes by role:admin -> /web/adminuser -> /web/user
user/adminpages validate role and redirect to matching page or/web/login.- Logout button calls
/api/v1/auth/logout.
For browser/Web UI, authenticate with session cookie set by /api/v1/auth/login.
For legacy script/API authentication, pass token via:
Authorization: Bearer <token>orX-APIM-Token: <token>
Notes:
APIM_HTTP_AUTH_TOKENacts as a bootstrap admin token for compatibility.- Additional token principals are managed via
/identities. - For
provider=http,/identitiesaccepts plaintext token insubjectand stores SHA-256 only.
When APIM_HTTP_UPDATE_REQUIRE_AUTH=true and APIM_HTTP_AUTH_TOKEN is empty:
/update/checkand/update/applyare rejected (403)- this enforces explicit auth configuration before exposing update actions
Usage controls are local policy rules evaluated against persisted usage snapshots:
- scope:
global|user|key - thresholds:
max_requestsand/ormax_tokensoverwindow_seconds - actions:
audit_only: write events/audit onlydisable_key: disable target keydisable_user_keys: disable all keys under a target emaildisable_all_keys: emergency disable all keys
When action disables keys, sidecar syncs active key set to CLIProxyAPI through existing /v0/management/api-keys reconciliation.
For Web/UI responses, remaining is computed only when a matching enabled control exists.
If no control applies, remaining fields are null and UI should treat as unconfigured/unlimited.
cliproxy-access-manager now ships a built-in web UI at /web (login/user/admin pages). You can still use HTTP API or Telegram bot for automation.
# 0) set your base URL and admin token
export APIM_BASE_URL="https://apimanager.example.com"
export APIM_TOKEN="<your_apim_http_auth_token_or_admin_identity_token>"
# 1) health check (public)
curl -i "$APIM_BASE_URL/healthz"
# 2) admin status
curl -i -H "Authorization: Bearer $APIM_TOKEN" "$APIM_BASE_URL/status"
# 3) trigger immediate reconciliation
curl -i -X POST -H "Authorization: Bearer $APIM_TOKEN" "$APIM_BASE_URL/sync_now"
# 4) list keys
curl -i -H "Authorization: Bearer $APIM_TOKEN" "$APIM_BASE_URL/keys?filter=all"
# 5) add a key
curl -i -X POST \
-H "Authorization: Bearer $APIM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"key":"demo-key-001","owner_email":"user@example.com","ttl":"7d","note":"demo"}' \
"$APIM_BASE_URL/keys"
# 6) disable a key
curl -i -X PATCH \
-H "Authorization: Bearer $APIM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"key":"demo-key-001","status":"disabled"}' \
"$APIM_BASE_URL/keys/status"
# 7) create HTTP user identity (RBAC)
curl -i -X POST \
-H "Authorization: Bearer $APIM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"provider":"http","subject":"user-http-token","role":"user","email":"user@example.com"}' \
"$APIM_BASE_URL/identities"
# 8) list identities
curl -i -H "Authorization: Bearer $APIM_TOKEN" "$APIM_BASE_URL/identities?provider=http"
# 9) user self query (with user token)
curl -i -H "Authorization: Bearer user-http-token" "$APIM_BASE_URL/me?since=24h"
# 10) admin query by email
curl -i -H "Authorization: Bearer $APIM_TOKEN" "$APIM_BASE_URL/account/query?email=user@example.com&since=7d"Notes:
- You can also pass token with
X-APIM-Token: <token>. - For
provider=http,subjectaccepts plaintext token in request; only SHA-256 is stored. GET /is not an API endpoint; use/healthzor other explicit paths.
Example (real smoke run):
# 1) bootstrap admin token can access admin endpoint
curl -i -H "Authorization: Bearer bootstrap-smoke-token" http://127.0.0.1:18439/status
# -> 200
# 2) unauthenticated request is rejected
curl -i http://127.0.0.1:18439/status
# -> 401
# 3) create admin/user HTTP identities
curl -i -H "Authorization: Bearer bootstrap-smoke-token" -H "Content-Type: application/json" \
-d '{"provider":"http","subject":"admin-http-token","role":"admin","email":"admin@example.com"}' \
http://127.0.0.1:18439/identities
# -> 200
curl -i -H "Authorization: Bearer bootstrap-smoke-token" -H "Content-Type: application/json" \
-d '{"provider":"http","subject":"user-http-token","role":"user","email":"user@example.com"}' \
http://127.0.0.1:18439/identities
# -> 200
# 4) user token cannot access admin-only endpoint
curl -i -H "Authorization: Bearer user-http-token" http://127.0.0.1:18439/keys
# -> 403
# 5) user token can query self summary
curl -i -H "Authorization: Bearer user-http-token" "http://127.0.0.1:18439/me?since=24h"
# -> 200
# 6) user token cannot manage identities
curl -i -H "Authorization: Bearer user-http-token" "http://127.0.0.1:18439/identities?provider=http"
# -> 403
# 7) unknown token is rejected
curl -i -H "Authorization: Bearer no-such-token" http://127.0.0.1:18439/status
# -> 401go mod tidy
go build ./cmd/managerIf host does not have Go installed, use Docker build in deploy/.
For proxy networks (as you provided):
- HTTP proxy:
http://127.0.0.1:20171 - SOCKS5 proxy:
socks5://127.0.0.1:20170
When building inside Docker, use host-reachable proxy endpoints (for many Linux Docker setups):
HTTP_PROXY=http://172.17.0.1:20171HTTPS_PROXY=http://172.17.0.1:20171
cd deploy
cp .env.production.example .env
mkdir -p data
# optional proxy for runtime
# export HTTP_PROXY=http://127.0.0.1:20171
# export HTTPS_PROXY=http://127.0.0.1:20171
# export ALL_PROXY=socks5://127.0.0.1:20170
docker compose -f docker-compose.deploy.yml up -dNotes:
- Uses image from
APIM_IMAGE(default:moocher4097/cliproxy-access-manager:latest). - Telegram is optional. Configure in
.env:TELEGRAM_BOT_TOKENTELEGRAM_ALLOWED_CHAT_IDSTELEGRAM_ALLOWED_USER_IDS
- Daily main-project update check runs at
APIM_UPDATE_CHECK_TIMEin UTC (default04:00). - If
APIM_UPDATE_APPLY_COMMANDis empty,/update_applywill auto-run:docker compose -f <compose-file> pull <service>docker compose -f <compose-file> up -d <service>with defaultsservice=cliproxy, and compose file auto-detected from common paths.
cd deploy
cp .env.production.example .env
mkdir -p data
# choose one proxy mode when needed for build:
# HTTP proxy mode
export HTTP_PROXY=http://127.0.0.1:20171
export HTTPS_PROXY=http://127.0.0.1:20171
# OR SOCKS5 proxy mode
# export ALL_PROXY=socks5://127.0.0.1:20170
# optional go module proxy inside builder
export GOLANG_PROXY=https://proxy.golang.org,direct
docker compose -f docker-compose.apim.yml up -d --buildUse deploy/.env.production.example as the recommended production template.
Use deploy/cliproxy-access-manager.service and put secrets in:
/etc/default/cliproxy-access-manager
- SQLite WAL mode is enabled for better durability.
- Sync and recovery operations are idempotent via persisted hashes.
- If Go toolchain is not installed on host, build via Dockerfile in
deploy/.