docs/dashboard-api.md
codexbar serve exposes a versioned, display-oriented snapshot of CodexBar usage data for dashboard clients:
GET /dashboard/v1/snapshot
Authorization: Bearer YOUR_TOKEN
The route is gated by a static bearer token and fails closed: without a configured token every request answers 401. The token is only ever read from the Authorization header — a query-string parameter named token is never accepted. Every response on the dashboard route — including all 401s and error responses — carries Cache-Control: no-store.
On the default loopback bind, /usage and /cost are unchanged and unauthenticated. On a non-loopback bind the same token gates all data routes: /usage, /cost, and /dashboard/v1/snapshot each require Authorization: Bearer YOUR_TOKEN, so account data never leaves the machine unauthenticated. /health is always open (it carries only a status and version string, useful for liveness probes).
# Generate a strong token
openssl rand -hex 32
# Preferred: environment variable (argv leaks via `ps`)
CODEXBAR_DASHBOARD_TOKEN=YOUR_TOKEN codexbar serve
# Also accepted, but visible in the process list
codexbar serve --dashboard-token YOUR_TOKEN
CODEXBAR_DASHBOARD_TOKEN wins over --dashboard-token when both are set.serve with a new value.--host accepts localhost or an IPv4 address; the socket layer does not support IPv6 binds.Transport is plain HTTP. There is no TLS in codexbar serve, which means:
/usage, /cost, and /dashboard/v1/snapshot behind the same token, a passive observer sees your account data but an active client without the token gets 401 on every data route. /health is the only unauthenticated route off-loopback.Deployments, from safest to least safe:
Loopback only (default). codexbar serve binds 127.0.0.1; nothing leaves the machine. Rejects non-loopback Host headers, so browser-based DNS-rebinding attacks cannot reach it either.
TLS-terminating reverse proxy. Keep the loopback bind and put a proxy in front. Caddy example:
dashboard.example.com {
handle /dashboard/v1/* {
reverse_proxy 127.0.0.1:8080 {
header_up Host 127.0.0.1
}
}
respond 404
}
Caddy provisions certificates automatically. The route matcher exposes only the authenticated dashboard API, while the upstream Host rewrite satisfies the loopback server's rebinding check. The token travels inside TLS from the client to the proxy and only crosses the loopback interface in cleartext.
Trusted network segment, cleartext accepted. Bind a LAN address directly:
CODEXBAR_DASHBOARD_TOKEN=... codexbar serve --host 0.0.0.0 --allow-plain-http
A non-loopback --host refuses to start without a token, and refuses to start without --allow-plain-http — passing that flag is the explicit, operational acceptance that cleartext bearer transport is fine on this network. The token then gates all data routes, and the server logs a one-line warning at startup.
The server compares tokens in constant time (fixed-length SHA-256 digest comparison), so timing does not leak a matching prefix. That protects the comparison, not the transport: on plain HTTP the token is still readable in transit.
Missing, malformed, or wrong credentials produce:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer
Cache-Control: no-store
Content-Type: application/json; charset=utf-8
{"error":"unauthorized"}
Snapshot requests share the serve cache and coordination machinery used by /usage and /cost:
--refresh-interval seconds, keyed by the loaded provider config, so toggling providers does not require a restart.--request-timeout bounds each request with 504 Gateway Timeout.The snapshot is a stable display contract, not a raw dump of provider internals. Identity is always redacted: email local parts are hidden while domains and plan labels are kept.
{
"schemaVersion": 1,
"generatedAt": "2026-07-16T12:00:00Z",
"staleAfterSeconds": 180,
"host": {
"codexBarVersion": "0.37.2",
"refreshIntervalSeconds": 60
},
"providers": [
{
"id": "codex",
"name": "Codex",
"enabled": true,
"source": "oauth",
"status": {
"level": "ok",
"label": "Operational",
"updatedAt": "2026-07-16T11:59:00Z"
},
"identity": {
"accountEmail": "[email protected]",
"plan": "Pro 20x"
},
"windows": [
{
"kind": "session",
"label": "Session",
"usedPercent": 28,
"remainingPercent": 72,
"resetAt": "2026-07-16T17:15:00Z"
}
],
"credits": {
"remaining": 112.4,
"unit": "credits"
},
"cost": {
"todayUSD": 1.04,
"last30DaysUSD": 18.22
},
"display": {
"accentColor": "#49A3B0",
"sortKey": 0,
"priority": "normal"
},
"error": null,
"updatedAt": "2026-07-16T11:59:45Z"
}
]
}
schemaVersion: Dashboard API schema version.generatedAt: Snapshot generation timestamp.staleAfterSeconds: Client-side staleness hint.host.codexBarVersion: CodexBar version when available.host.refreshIntervalSeconds: Server response cache interval.providers[].id: Provider identifier.providers[].name: Provider display name.providers[].enabled: Whether the provider is enabled in CodexBar config.providers[].source: Source used for the provider data.providers[].status: Provider service status when available (level: ok | warning | critical | unknown).providers[].identity: Redacted account email and plan label, or null.providers[].windows: Session, weekly, tertiary, or provider-specific rate windows.providers[].credits: Remaining credits or balance when available.providers[].cost: Local cost data when available.providers[].display: UI hints for ordering and coloring.providers[].error: Provider error payload when the latest fetch failed.providers[].updatedAt: Best-known update timestamp for the provider row.