Every flow leaves a trace.
Keep usage history for CLIProxyAPI (CPA), understand model costs, and track request performance and credential quotas. CPA Usage Keeper brings overviews, realtime diagnostics, request details, and quota history into one independently deployed dashboard.
Add Keeper to an existing CPA · Deploy CPA + Keeper · Configuration
Overview
|
Realtime Diagnostics
|
Usage Analysis
|
Request Events
|
Credentials and Quotas
|
Quota History
|
- Keep your history: persist CPA usage in SQLite with scheduled backups.
- Understand costs: analyze usage, caching, and estimated costs by model, API Key, and provider.
- Investigate requests: inspect and export request details, and explore success rates, time to first token (TTFT), and total latency.
- Track quotas: monitor credential health and remaining quotas, refresh quotas, edit priorities, and explore Codex quota history.
- Share scoped access: provide a read-only usage view for an individual CPA API Key.
CPA plugin embedding in CPAMC is also available. Deploy with Docker Compose, Homebrew, or binaries; login protection is enabled by default.
- Thanks to CLIProxyAPI (CPA) for providing the upstream CPA foundation and data source this project builds on.
- Thanks to @YouShouldBetOnMe for supporting CPA Usage Keeper.
- Thanks to the CPA discussion group for their discussions and feedback.
Keeper supports CPA v8.0.12+. Enable CPA usage statistics by setting
observability.usage.usage-statistics-enabledtotrue.
When multiple usage collectors share one CPA instance, ensure they all use subscription mode; otherwise, collection may stop or become incomplete.
Docker Compose is the recommended deployment method. Use the full stack when deploying CPA and Keeper together, or the Keeper-only stack when CPA already exists.
Docker deployment requires Docker and Docker Compose; you do not need Go, Node.js, or a source build. For an existing CPA, have its address and management key ready, and choose a Keeper login password.
| Setup | Recommended path | Architectures |
|---|---|---|
| New CPA + Keeper deployment | Docker Compose: CPA + Keeper | linux/amd64, linux/arm64 |
| Existing CPA deployment | Docker Compose: Keeper only | linux/amd64, linux/arm64 |
| Existing CPA, Docker CLI preferred | Docker | linux/amd64, linux/arm64 |
| macOS | Homebrew | amd64, arm64 |
| Linux without containers | Linux binary | amd64, arm64 |
| Windows | Windows binary | amd64, arm64 |
Login protection is enabled by default. Configure LOGIN_PASSWORD before starting Keeper, or explicitly set AUTH_ENABLED=false only when access is reliably isolated by the deployment environment.
Production-style linux/amd64 capacity measurements for sustained ingestion, Dashboard latency, CPU utilization, and Keeper cgroup peak memory are available in the Capacity Benchmark Report.
Developer reference: project structure, local setup, and tests
cmd/server/ Application entry point
internal/api/ HTTP routes and handlers
internal/app/ Application wiring and startup
internal/auth/ Sessions and access control
internal/poller/ CPA usage and metadata synchronization
internal/repository/ SQLite persistence and aggregations
internal/service/ Usage, pricing, and identity services
internal/quota/ Provider quota refresh and inspection
internal/benchmark/ Capacity suite, reports, manifests, and legacy microbenchmarks
deploy/ Deployment templates
web/ React + TypeScript frontend
- Go 1.26+
- Node.js 24+
- npm
- A running CLIProxyAPI (CPA) instance
- Copy
.env.exampleto.env, then set at leastCPA_BASE_URL,CPA_MANAGEMENT_KEY, and a privateLOGIN_PASSWORD.
cp .env.example .env
vim .env- Start the backend.
go run ./cmd/server/main.go- In another terminal, install frontend dependencies and start the development server.
npm --prefix ./web ci
npm --prefix ./web run dev -- --host 127.0.0.1Open http://127.0.0.1:5173. The frontend proxies /api to http://127.0.0.1:8080; override it with VITE_API_PROXY_TARGET when the backend uses another port.
Run the full verification baseline:
make verifyOr run checks individually:
go test ./cmd/... ./internal/...
npm --prefix ./web run test
npm --prefix ./web run lint
npm --prefix ./web run typecheck
npm --prefix ./web run build- Use the response/configured timezone. Calendar days use
internal/timeutil.CivilDateandDayBounds; rolling windows use elapsed durations. A calendar day is not always 24 hours. - Read existing rollups through the repository bucket adapters. Overview and Latency have different hourly anchors; Activity retains its existing 364-block grid and stored ends. Query fixes must not change write buckets or repartition stored aggregates.
- Reuse
internal/repository/usage_event_time_read.gofor raw-event time predicates and instant filtering. Use bounded segments for queries and the open-ended reader for recent-cache startup/reload; keep projections and lifecycle logic in callers. - Keep bucket identity as an instant or full date, never a formatted clock label. Sort timestamps by instant; use actual full-day minutes for day-bucket rates.
- Reuse
web/src/utils/usage/timeLabels.tsfor timezone parts and repeated-clock disambiguation. Preserve each chart's locale, precision, fallback and empty-point rules; add offsets only to ambiguous local clocks. - Test ordinary Shanghai dates alongside DST gaps/folds, including a browser timezone different from the response. Preserve existing chart styles and lifecycle behavior.
After startup, open http://your-server-address:8080 (http://127.0.0.1:8080 for a local deployment) and sign in with your Keeper login password. Adjust the URL if you configure a different port, HTTPS, or a base path.
Docker Compose is recommended for both a complete CPA + Keeper stack and a Keeper-only deployment.
1. Prepare the configuration files
First, download the configuration example from the official CPA repository into ./cpa/config.yaml in your deployment directory:
mkdir -p cpa/auths cpa/logs keeper
curl -fL https://raw.githubusercontent.com/router-for-me/CLIProxyAPI/main/config.example.yaml \
-o cpa/config.yamlIn the same directory, download the CPA + Keeper Compose template:
curl -fL https://raw.githubusercontent.com/Willxup/cpa-usage-keeper/main/deploy/docker-compose.full.example.yml \
-o docker-compose.ymlUse this download command for a new deployment only; edit an existing configuration directly to avoid overwriting it.
2. Configure CPA and Keeper
Edit the existing settings in cpa/config.yaml as listed below, preserving YAML nesting and indentation. Dots in the table denote nested paths, not literal YAML keys to add:
| Setting | What to configure |
|---|---|
management.allow-remote |
Set to true so Keeper can access CPA management features from another container. |
management.secret-key |
Set a private management key and use the same original key for CPA_MANAGEMENT_KEY in docker-compose.yml. |
observability.usage.usage-statistics-enabled |
Set to true to enable usage statistics. |
access.api-keys |
Replace the example keys with your own client API keys; these are separate from the CPA management key and Keeper login password. |
Keep server.host as an empty string, server.port as 8317, and oauth.auth-dir as "~/.cli-proxy-api" to match the container network and volume mounts in the template.
Edit the downloaded docker-compose.yml and fill in two values under Keeper's environment:
CPA_MANAGEMENT_KEY: use the same original management key configured in CPA.LOGIN_PASSWORD: replace the empty string""with your own Keeper login password.
Alternatively, put the settings in ./keeper/.env; the template loads it through an optional env_file entry and accepts a missing file. Values in environment take precedence, so remove the corresponding entries, including empty placeholders, to use values from the file.
3. Start and open Keeper
docker compose up -dOpen http://your-server-address:8080 and sign in with your Keeper password. Run docker compose down to stop the stack.
For a new deployment, add model credentials in CPA and make a model request to generate usage records.
CPA data is stored under ./cpa, and Keeper data is stored under ./keeper.
1. Prepare the configuration files
When CPA is already deployed, download the Keeper-only Compose template and environment configuration into a new deployment directory:
curl -fL https://raw.githubusercontent.com/Willxup/cpa-usage-keeper/main/deploy/docker-compose.example.yml \
-o docker-compose.yml
curl -fL https://raw.githubusercontent.com/Willxup/cpa-usage-keeper/main/.env.example -o .env
vim .envFor an existing deployment, edit the existing files directly to avoid overwriting your configuration.
2. Set the connection details and login password
For CPA running on the Docker host, start with:
CPA_BASE_URL=http://host.docker.internal:8317
CPA_MANAGEMENT_KEY=replace-with-your-management-key
AUTH_ENABLED=true
LOGIN_PASSWORD=Set a private LOGIN_PASSWORD before starting the container.
Whether CPA runs on the Docker host or another machine, it must allow remote management and listen on an address reachable from the Keeper container; a listener bound only to 127.0.0.1 is not reachable. Set management.allow-remote: true and check server.host.
For other network layouts, set CPA_BASE_URL to a CPA address reachable from the container. Set REDIS_QUEUE_ADDR only when the Redis/RESP address differs from the automatically derived address.
3. Start and open Keeper
docker compose up -dOpen http://your-server-address:8080 and sign in with your Keeper password. Run docker compose down to stop the stack.
Keeper data is stored under ./data by the provided template.
For either Compose setup, view Keeper logs from the deployment directory:
docker compose logs --tail=100 -f cpa-usage-keeperTo update Keeper, keep your configuration and data directories and run:
docker compose pull cpa-usage-keeper
docker compose up -d cpa-usage-keeper- Check that CPA usage statistics are enabled:
observability.usage.usage-statistics-enabledmust betrue. - Ensure Keeper can reach
CPA_BASE_URLand thatCPA_MANAGEMENT_KEYmatches the CPA management key. - Check that CPA has received new model requests. If data is still missing, use the log command above to look for connection or authentication errors.
Use the same .env values as the Keeper-only Compose setup when you prefer docker run:
docker run -d \
--name cpa-usage-keeper \
--add-host=host.docker.internal:host-gateway \
-p 8080:8080 \
-v "$(pwd)/keeper:/data" \
--env-file .env \
ghcr.io/willxup/cpa-usage-keeper:latestHomebrew is the recommended macOS installation method:
brew tap Willxup/cpa-usage-keeper
brew install cpa-usage-keeperSet CPA_BASE_URL, CPA_MANAGEMENT_KEY, and a private LOGIN_PASSWORD, then start the service:
vim "$(brew --prefix)/etc/cpa-usage-keeper.env"
brew services start cpa-usage-keeperUpgrade and service commands:
brew services list
brew services restart cpa-usage-keeper
brew update
brew upgrade cpa-usage-keeperData is stored under $(brew --prefix)/var/cpa-usage-keeper; logs are written under $(brew --prefix)/var/log/.
Download the linux_amd64 or linux_arm64 archive from Releases, then extract and run it:
mkdir -p cpa-usage-keeper
tar -xzf ./cpa-usage-keeper_*_linux_*.tar.gz -C cpa-usage-keeper --strip-components=1
cd cpa-usage-keeper
cp .env.example .env
vim .env
./cpa-usage-keeperThe Linux package includes a service template. Run these commands from the extracted package directory:
sudo cp cpa-usage-keeper.service /etc/systemd/system/cpa-usage-keeper.service
sudo sed -i "s|__CPA_USAGE_KEEPER_DIR__|$(pwd)|g" /etc/systemd/system/cpa-usage-keeper.service
sudo systemctl daemon-reload
sudo systemctl enable --now cpa-usage-keepersudo systemctl status cpa-usage-keeper
sudo journalctl -u cpa-usage-keeper -f
sudo systemctl restart cpa-usage-keeperThe binary supports optional startup flags:
cpa-usage-keeper --env /path/to/keeper.env # Use a specific environment file.
cpa-usage-keeper --host 127.0.0.1 # Override APP_HOST for this process.
cpa-usage-keeper -v # Print the build version and exit; --version is also supported.Download the windows_amd64 or windows_arm64 ZIP package from Releases and extract it. In PowerShell, open the extracted package directory and run:
Copy-Item .env.example .env
notepad .env
.\cpa-usage-keeper.exeSet CPA_BASE_URL, CPA_MANAGEMENT_KEY, and a private LOGIN_PASSWORD before starting. Authentication is enabled by default; set AUTH_ENABLED=false explicitly only for an isolated deployment.
Copy the example config:
cp .env.example .envFor a first deployment, set the CPA address, CPA management key, and Keeper login password. Most other settings can keep their defaults. Read the relevant sections when you need a domain, HTTPS, or a base path.
| Variable | Required | Default | Description |
|---|---|---|---|
CPA_BASE_URL |
Yes | - | URL used by the Keeper server to call CPA. In Docker Compose this is usually http://cli-proxy-api:8317, and it can be a private address or container service name |
CPA_MANAGEMENT_KEY |
Yes | - | CPA management key used to read CPA management APIs |
| Variable | Required | Default | Description |
|---|---|---|---|
APP_HOST |
No | all interfaces | Keeper HTTP listen host; native deployments can set 127.0.0.1 for local-only access |
APP_PORT |
No | 8080 |
Keeper HTTP listen port |
APP_BASE_PATH |
No | root path | Keeper subpath prefix, such as /keeper; empty means / |
CPA_PUBLIC_URL |
No | current browser origin root | Public CPA URL for the "Back to CPA" link and CPAMC frame trust |
TRUSTED_PROXY_CIDRS |
No | local loopback only | Additional reverse-proxy CIDRs allowed to provide X-Forwarded-For, separated by commas |
- The
--hoststartup flag overridesAPP_HOST. When neither is set, Keeper preserves its existing behavior and listens on all available network interfaces. - For Docker/Compose, keep
APP_HOSTempty. To restrict access to the Docker host, publish the port as127.0.0.1:8080:8080. APP_BASE_PATHmust be empty or start with/;/cpa/is normalized to/cpa.CPA_BASE_URLis the server-side CPA address and may use a private host or Docker service name.CPA_PUBLIC_URLcontrols browser navigation and cross-origin CPAMC frame trust. Leave it empty for same-origin/management.html, or set an explicit public CPA URL when domains, ports, or paths differ.- Keeper trusts
X-Forwarded-Foronly from local loopback andTRUSTED_PROXY_CIDRS. Direct clients cannot change their login-rate-limit source with this header. Configure only the exact proxy address or network; universal CIDRs are rejected.
For cross-origin CPAMC embedding, CPA_PUBLIC_URL must be a complete http:// or https:// URL with a host. Relative paths affect navigation only.
| Variable | Required | Default | Description |
|---|---|---|---|
AUTH_ENABLED |
No | true |
Enable login protection |
LOGIN_PASSWORD |
When auth is enabled | - | Login password |
CPA_REQUEST_LOG_ACCESS_ENABLED |
No | false |
Allow administrators to view and download CPA request logs through Keeper; corresponding logs must exist in CPA and may contain request or response data |
AUTH_SESSION_TTL |
No | 168h |
Login session lifetime |
| Variable | Required | Default | Description |
|---|---|---|---|
TZ |
No | Asia/Shanghai |
Timezone used for statistics and display; Today, daily totals, page timestamps, log timestamps, and daily cleanup are calculated in this timezone |
REQUEST_TIMEOUT |
No | 30s |
Timeout for CPA HTTP requests and Redis queue operations |
TLS_SKIP_VERIFY |
No | false |
Skip TLS certificate verification for CPA HTTPS and Redis queue TLS; enable only with self-signed certificates |
Scheduled Auth Files quota refresh is configured from the gear button in the Auth Files inspection dialog. The setting is stored in the local SQLite database and does not require the page to stay open.
| Variable | Required | Default | Description |
|---|---|---|---|
QUOTA_REFRESH_WORKER_LIMIT |
No | 10 |
Maximum Auth Files quota refresh concurrency for manual and scheduled refresh, capped at 100 |
QUOTA_UPSTREAM_RESPONSES_ENABLED |
No | false |
Cache each credential's latest raw upstream quota responses and return them through quota task/cache APIs for browser Network debugging; responses may contain account data |
| Variable | Required | Default | Description |
|---|---|---|---|
REDIS_QUEUE_ADDR |
No | Derived from CPA_BASE_URL |
An explicit value takes precedence. When empty, use the hostname and explicit port from CPA_BASE_URL, or 8317 if no port is specified. Set host:port for a separate address |
REDIS_QUEUE_TLS |
No | false |
Set to true to force TLS for the Redis queue; when no queue address is specified, TLS follows the protocol of CPA_BASE_URL by default. false does not override automatically inferred TLS settings. |
REDIS_QUEUE_BATCH_SIZE |
No | 10000 |
Maximum queue records per pull |
REDIS_QUEUE_IDLE_INTERVAL |
No | 1s |
Empty queue check interval |
| Variable | Required | Default | Description |
|---|---|---|---|
WORK_DIR |
No | ./data |
Application work directory; database, logs, and backups default to app.db, logs/, and backups/ under it |
LOG_LEVEL |
No | info |
Log level |
LOG_FILE_ENABLED |
No | true |
Write persistent log files |
LOG_RETENTION_DAYS |
No | 7 |
Combined-log history days, plus the current day; 0 disables cleanup. Error-only logs keep 30 history days plus the current day |
BACKUP_ENABLED |
No | true |
Enable scheduled database backups |
BACKUP_INTERVAL |
No | 24h |
Database backup interval |
BACKUP_RETENTION_DAYS |
No | 7 |
Backup retention days |
USAGE_RAW_RETENTION_DAYS |
No | 0 |
Total raw-request retention in days; only archives are deleted. 0 retains archives forever; >=90 enables cleanup. Negative values and 1–89 fall back to 0 with a warning without preventing startup |
Keeper archives raw request records older than 90 local calendar days daily at 04:30 in the configured timezone. Archives are retained permanently by default. With USAGE_RAW_RETENTION_DAYS>=90, daily maintenance deletes expired archived records in batches after archiving, using the request timestamp and local calendar days in the configured timezone. The 90-day hot-table window remains unchanged. Normal pages do not directly query archived records; historical aggregates and backups keep their own retention policies. Deleted raw records cannot support later historical recalculation. Deletion may not immediately shrink the SQLite file: freed space can be reused and is reclaimed by the existing conditional compaction policy.
When file logging is enabled, cpa-usage-keeper-YYYY-MM-DD.log contains all emitted levels. Error, fatal, and panic entries are also copied to cpa-usage-keeper-error-YYYY-MM-DD.log, which keeps the previous 30 local calendar dates plus the current date.
Keeper creates a database backup before migrating v1 data. If migration fails and that backup is verified, the logs include English rollback instructions and its backup_path.
- Stop Keeper v2 and preserve the failed database together with any
-waland-shmfiles. - Copy the pre-upgrade backup to
<new WORK_DIR>/app.dbin a separate data directory, renaming the backup copy toapp.db. Keep the original backup intact; do not mix the copy with files from the failed database. - Install Keeper v1 and set
WORK_DIRto that new directory. With the default Docker setup, mount the new directory at/data, so the restored file is/data/app.dbinside the container. Do not open the partially migrated database with v1.
This returns data to the backup point. Usage received afterward is not included. If no verified backup is available, locate and validate a known pre-upgrade backup before attempting rollback.
| Variable | Required | Default | Description |
|---|---|---|---|
TLS_ENABLED |
No | false |
Let Keeper serve HTTPS/TLS directly |
TLS_CERT_FILE |
Required when TLS is enabled | - | HTTPS certificate file path |
TLS_KEY_FILE |
Required when TLS is enabled | - | HTTPS private key file path |
Usually, HTTPS should be terminated at nginx, Caddy, or another reverse proxy. Set TLS_ENABLED=true only when the Keeper process must serve HTTPS directly, and provide TLS_CERT_FILE and TLS_KEY_FILE; relative paths are resolved against the .env file directory.
Security and data notes:
- Browser APIs redact key-like fields, but the SQLite database and its unencrypted backups contain original data.
- Authentication is enabled by default. If it is explicitly disabled, restrict Keeper access at the deployment boundary; terminate public HTTPS at a reverse proxy.
- Login session hashes persist in SQLite until logout or
AUTH_SESSION_TTLexpiry. - CPAMC uses a separate embed session: an
HttpOnlycookie when available, or a per-tab header token in browser session storage as a fallback. - Same-origin embedding works by default. For cross-origin embedding, set
CPA_PUBLIC_URLto the public CPA/CPAMC origin used forframe-ancestors. - Redis inbox messages are retained through the current day after success or for 7 days after failure.
When serving under /cpa, set APP_BASE_PATH=/cpa and keep the prefix in your reverse proxy:
location /cpa/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}The loopback Nginx configuration above works without additional Keeper settings. If the reverse proxy reaches Keeper from a container or another host, or a CDN such as Cloudflare sits in front of the reverse proxy, add the exact proxy networks, for example TRUSTED_PROXY_CIDRS=172.18.0.0/16.
When CPA and Keeper share a browser origin, CPA_PUBLIC_URL can be omitted and "Back to CPA" uses /management.html. For another domain, port, or path, set the public CPA URL:
CPA_PUBLIC_URL=https://cpa.example.comThis project is open source under the MIT License.