API And Metrics
The HTTP API is mostly read-only. It is intended for local dashboards, agent
wrappers, Prometheus, and operator smoke tests. The only mutating v1 endpoint is
the protected manual direct-quota refresh action. Unknown JSON fields may be
added in 0.x releases; clients should ignore fields they do not understand.
OpenAPI 3.1 contract: openapi.json. The versioned /api/v1/*
paths are the public integration surface. Older unversioned /api/* JSON paths
were removed during the 0.x API cleanup.
The API is not an auth boundary. --allowed-host and the built-in Host /
Origin checks are request-origin guards, not login or authorization. Keep the
monitor on 127.0.0.1 or protect it with an authenticated reverse proxy before
exposing it to a LAN or public network.
The browser Privacy toggle is display-only. It masks email addresses and
token-like strings in the rendered page, but API and metrics endpoints keep
their original payloads for wrappers, agents, Prometheus, and smoke tests.
For the data-flow and secret-redaction model behind these endpoints, see the security self-audit and threat model.
Endpoints
GET /healthz: process liveness only. It does not verify CPA, management key, auth-dir, SQLite, or benchmark import health.GET /api/v1/status: full dashboard snapshot used by the browser UI. The top-level envelope is stable; deeptabspayloads are UI-oriented and additive during0.xreleases.GET /api/v1/stats: narrow Stats tab payload with live CPA details, CPA cumulative counters, SQLite history summary, data sources, and backup hints.GET /api/v1/stats/series: SQLite history-backed Stats time-series workbench payload. It supports token/request curves, grouping, smoothing preferences, and exporting the current view as JSON or CSV.GET /api/v1/recommendations: routing-score ranked account list for wrappers and agents. This is the account-selection contract for automation; wrappers should use it instead of scraping deep/api/v1/status.tabs.*UI payloads.GET /api/v1/health: aggregated operational health for account actions, monitor data sources, and recent changes.POST /api/v1/quota/refresh: protected manual direct-quota refresh. The default browser UI sends an in-pageX-Codex-Quota-Monitor-Refresh-Token; legacymanual_quota_refresh_auth = "management-key"deployments instead sendAuthorization: Bearer <management-key>, and"none"disables this action check for trusted local deployments. It queues a full direct quota refresh for primary, recovery, and discovery accounts, skips accounts outside the direct-quota policy, and has no legacy/api/*alias. Sending{"scope":"disabled-diagnostics"}probes operator-disabled Codex accounts once for diagnostics while keeping them disabled, non-routable, and excluded from capacity. The latest disabled probe result is retained across normal automatic quota refreshes until the next disabled-diagnostics probe or process restart.GET /metrics: Prometheus text metrics.
When the process is started with --demo, these same endpoints serve built-in
sample data on 127.0.0.1:4516 by default, and /api/v1/status includes
"demo": true with source = "demo". Demo mode does not read auth files,
management keys, SQLite history, benchmark summaries, or CPA endpoints.
Redacted Examples
/api/v1/recommendations:
{
"contractVersion": "1.1",
"sampledAt": "2026-04-20T12:00:00+00:00",
"source": {"kind": "live", "text": "Live via CLIProxyAPI + direct Codex quota", "gatewayOk": true},
"routingPolicy": {
"scoreMax": 100,
"capacityWeightUnit": "plus",
"hardBlocks": [
"routeEligible=false",
"tone=bad",
"status disabled/unavailable/invalid/auth invalid/cooldown",
"exhausted quota windows"
]
},
"summary": "3 routable accounts · 1 blocked account · top score 86",
"routableCount": 3,
"blockedCount": 1,
"topScore": 86,
"items": [
{
"selector": {
"authName": "auth-redacted.json",
"authIndex": "acct-redacted",
"accountKey": "acct-redacted"
},
"authIndex": "acct-redacted",
"accountKey": "acct-redacted",
"planKind": "plus",
"title": "account-redacted@example.invalid",
"badge": "Plus",
"summary": "Active · 5h 80% · weekly 80%",
"routingScore": 86,
"routingEligible": true,
"routingBlockedReasons": [],
"scoreComponents": {"capacity": 36, "health": 25, "plan": 5, "load": 10, "reclaim": 10},
"capacityWeight": 1.0,
"quotaWindows": [
{"id": "5h", "state": "known", "percent": 80, "resetsInSeconds": 3600},
{"id": "week", "state": "known", "percent": 80, "resetsInSeconds": 86400}
],
"reclaimEligible": true
}
]
}
/api/v1/health:
{
"title": "Health",
"state": "warn",
"summary": "1 item needs review.",
"pill": "1 action",
"actions": [
{
"kind": "threshold",
"tone": "bad",
"title": "5h capacity below threshold",
"badge": "Threshold",
"summary": "0.40 Plus < 0.50 Plus"
}
],
"sources": [],
"recentChanges": [],
"actionCount": 1
}
Stable Fields
/api/v1/status:available,source,sourceText,displayTimezone,displayTimezoneLabel,summary,tabs,recommendations.summary.activityLineis the display-safe request/token activity line. Fast/Routing gateway policy state is intentionally not part of the public status payload./api/v1/recommendations:contractVersion,sampledAt,source,routingPolicy,routableCount,blockedCount,topScore,items.source.kindis the snapshot source,source.textis the human-readable source summary, andsource.gatewayOkmirrors the gateway liveness signal.- Recommendation item:
selector,authIndex,accountKey,planKind,title,badge,summary,detail,routingScore,routingEligible,routingBlockedReasons,scoreComponents,reasons,capacityWeight,quotaWindows,reclaimEligible,reclaimWindow.routingBlockedReasonsis empty for routable accounts and contains stable reason slugs such asauth_invalid,operator_disabled,cpa_retry_after,status_invalid,cooldown, or exhausted quota windows for blocked accounts.quotaWindowscarries sanitized direct-quota state only: window id, state, percent, reset timestamps, remaining text, and stale status.capacityWeightis expressed in Plus units: Plus and Team are1.0, Prolite is5.0, Pro is20.0, and untracked, operator-disabled, or deactivated accounts are0.0. Recommendation payloads intentionally omit access tokens, management keys, auth-file paths, download URLs, and raw auth JSON. - Reset rows:
account,remainingText,timeText,timezoneLabel,resetAt,resetsInSeconds,displayResetSource. /api/v1/stats:title,summary,live,cumulative,history,backup,sources,footnote.liveis built only from timestamped CPA details;cumulativeis the latest CPA cumulative snapshot;historyis monitor SQLite data when--state-dbis enabled. The interactive timeline lives at/api/v1/stats/series; the tab payload keeps summary and source status only./api/v1/stats/series:title,summary,defaults,options,query,available,bucketSeconds,range,totals,series,breakdown,export./api/v1/health:title,state,summary,pill,actions,sources,recentChanges,actionCount. Operator-disabled accounts are excluded from routing and capacity, but they are not health actions unless CPA also reports an unavailable or invalid auth state. Disabled account probe results appear on the status account payload asdisabledProbeand are diagnostic-only. Normal quota refreshes keep the last disabled probe result instead of resetting it to unknown.
/api/v1/recommendations and /api/v1/health are the recommended
wrapper-facing contracts. Account selection automation should use
/api/v1/recommendations and treat deep /api/v1/status.tabs.* payloads as UI
implementation detail unless the fields are explicitly documented here or in
openapi.json.
Stats Series
/api/v1/stats/series is the stable machine-readable surface behind the Stats
workbench. It reads deduplicated timestamped usage details from the monitor
SQLite database, so it is empty until the process runs with --state-db and CPA
has returned usage detail rows with timestamps.
Supported query parameters:
range:1h,6h,24h,7d,30d, orall; default24h.metric:tokensorrequests; defaulttokens.group:total,account,client,model, orapi; defaulttotal.clientis the recommended shared-client view when--client-labels-fileis configured. Unmapped clients use deterministic short fingerprints instead of rawapiName/api_namevalues. The legacyapigrouping keeps the raw CPA API name for compatibility and can expose sensitive values.top: highest-usage item limit from1to20; default8. Grouped views use it for chart lines and breakdown rows;totalkeeps one chart line while still using it for the breakdown.smooth: defaulttrue;0,false,off, andnodisable smoothing.mode:bucketfor bucket totals, orratefor per-minute rate; defaultbucket.format: omit for JSON, or usecsvfortext/csv; charset=utf-8.
CSV exports use these columns:
timestamp,series_id,series_label,requests,tokens,failed,request_text,token_text,failure_rate
The browser stores Stats controls such as range, metric, grouping, smoothing,
and display mode in localStorage. Those preferences are display state; they
do not mutate the API payload or monitor SQLite history.
Client labels are loaded from a JSON file:
{
"clients": [
{"apiName": "sk-example-from-cpa", "label": "Alice"},
{"api_name": "rotated-api-name", "label": "Alice"}
]
}
Pass it with --client-labels-file /path/to/client-labels.json, or set
CODEX_QUOTA_MONITOR_CLIENT_LABELS_FILE. Keep the file with runtime secrets;
do not put real API names in world-readable docs or Nix store text.
Prometheus
Use examples/prometheus/prometheus.yml for scrape config and
examples/prometheus/alerts.yml for starter alerts. Useful metrics include:
codex_quota_monitor_snapshot_availablecodex_quota_monitor_snapshot_source{source=...}codex_quota_monitor_gateway_upcodex_quota_monitor_health_action_countcodex_quota_monitor_routable_accountscodex_quota_monitor_top_routing_scorecodex_quota_monitor_capacity_known_plus_units{window=...}codex_quota_monitor_capacity_tracked_plus_units{window=...}
CLI Helpers
codex-quota-monitoris the canonical executable;cqmis an equivalent short alias.codex-quota-monitor print-config: prints resolved non-secret config.codex-quota-monitor --demo: serves built-in sample data on port4516by default for a zero-secret dashboard preview.codex-quota-monitor doctor: checks CPA reachability, management-key access, config-file loading, auth-dir permissions, SQLite state path, bindability, and risky listen addresses.codex-quota-monitor doctor --json: emits machine-readable check results.codex-quota-monitor init: prints or writes starter config, systemd, and Docker artifacts.codex-quota-monitor redact: creates redacted copies of JSON, JSONL, CSV, Markdown/text, and SQLite support artifacts before sharing them.codex-quota-monitor export-history --state-db /path/to/history.sqlite3 --output backup.json: writes a native JSON backup of monitor SQLite history.codex-quota-monitor import-history --state-db /path/to/history.sqlite3 backup.json: imports a native JSON backup idempotently.
CLI flags override environment variables, and environment variables override TOML config files. Use --config /path/to/config.toml or CODEX_QUOTA_MONITOR_CONFIG; use --config off to ignore config files.
Use --allowed-host / CODEX_QUOTA_MONITOR_ALLOWED_HOSTS when a browser must
reach the dashboard through a non-loopback hostname or IP; values are
comma-separated authorities such as monitor.lan,192.0.2.10. Omit the port to
allow that host with any browser-facing port, or include :4515 to require a
specific Host/Origin port. This allowlist does not authenticate viewers.
For reverse proxies, use the browser-facing hostname such as quota.example,
not the loopback upstream. The v1 browser UI expects to live at the root path of
that hostname; path-prefix deployments are not supported.