Files
nessi 792f85fc8b feat: add organization sessions mode for Devin self-serve usage tracking
Add `DEVIN_MODE` configuration supporting `enterprise` (default) and `organization_sessions` modes. Organization sessions mode uses `/v3/organizations/{org_id}/sessions` API with `ViewOrgSessions` permission, suitable for non-Enterprise organizations. Sums session ACUs consumed within configurable rolling window (1-365 days via `DEVIN_SESSION_WINDOW_DAYS`). Mode does not show quota, percentage, credit, or reset fields as these are unav
2026-09-18 12:04:13 +02:00

12 KiB

AI Usage Dashboard

AI Usage Dashboard is a small FastAPI monitoring kiosk for the official local Codex app-server mechanism and Devin API v3 Enterprise consumption endpoints. Provider refreshes are independent: an unavailable provider never hides a successful result from the other provider.

Requirements

  • Python 3.12
  • Codex CLI installed and authenticated locally when Codex data is enabled
  • Devin API v3 Enterprise access and a service user with ViewAccountConsumption when Devin data is enabled
  • Chromium/Chrome only for the optional kiosk launcher
  • Docker and Compose are optional

Install and run directly

Unix:

python3.12 -m venv .venv
. .venv/bin/activate
python -m pip install -r requirements.txt
cp .env.example .env
python -m app.main

python -m app.main is the simplest direct command and honors HOST and PORT from .env. The explicit Uvicorn alternative is:

python -m uvicorn app.main:app --host 0.0.0.0 --port 8080

Windows PowerShell:

py -3.12 -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install -r requirements.txt
Copy-Item .env.example .env
python -m app.main

python -m app.main honors HOST and PORT from .env. The explicit Uvicorn alternative is:

python -m uvicorn app.main:app --host 0.0.0.0 --port 8080

Open http://localhost:8080. The application also runs in deterministic demo mode with DEMO_MODE=true and no provider credentials.

Configuration

Copy .env.example to .env. Unknown environment variables are ignored and names are case-insensitive.

Variable Default Meaning
HOST 0.0.0.0 Bind host when using a launcher that reads settings
PORT 8080 Application port
CODEX_ENABLED true Enable the local Codex provider
CODEX_COMMAND codex One executable path; it is never shell-split
CODEX_TIMEOUT 8 Codex request timeout in seconds
DEVIN_ENABLED true Enable Devin
DEVIN_API_KEY empty Devin Bearer token, kept as a secret value
DEVIN_API_URL https://api.devin.ai Devin API base URL
DEVIN_MODE enterprise enterprise billing consumption or organization_sessions rolling session usage
DEVIN_ORG_ID empty Organization scope, required by organization sessions mode
DEVIN_USER_ID empty Enterprise user scope; optional user filter in organization sessions mode
DEVIN_SESSION_WINDOW_DAYS 30 Rolling organization-session window from 1 to 365 days
DEVIN_TIMEOUT 8 Devin HTTP timeout in seconds
PROVIDER_CACHE_TTL 8 Successful provider cache duration in seconds
REFRESH_INTERVAL 10 Browser refresh period in seconds
TIMEZONE Europe/Vienna ZoneInfo name used for reset timestamps
DEMO_MODE false Use deterministic local demo values for both cards

Provider mechanisms

Codex

The dashboard invokes the official local CLI exactly as codex app-server --listen stdio://, without a shell. It sends the official line-delimited initialize, initialized, and account/rateLimits/read messages and parses primary and secondary windows, credits, and individual limits. Install and log in to the official CLI first:

codex login

The app-server interface is official, but is documented as a development/debug interface and may change. If the executable is not on the service PATH, configure CODEX_COMMAND with its absolute path. ChatGPT plan rate limits are distinct from OpenAI API billing. CODEX_API_KEY is intentionally not used or included because an API key does not expose ChatGPT Codex plan limits.

Devin

The provider has two official Devin modes. enterprise uses the API v3 Enterprise consumption endpoints: it selects the active billing cycle from /v3/enterprise/consumption/cycles, then requests daily consumption using user scope, organization scope, or the account endpoint in that precedence order. It can show true billing-cycle usage and limits when available. Enterprise consumption requires a service user with ViewAccountConsumption; reading the organization max limit may require extra organization-read permissions.

organization_sessions uses GET /v3/organizations/{org_id}/sessions with a cog_ service-user key, DEVIN_ORG_ID, and the ViewOrgSessions permission. It is suitable for non-Enterprise/self-serve organizations when that endpoint is permitted. The mode sums each returned session's cumulative acus_consumed for sessions whose created_at falls inside the rolling N-day query. It is not billing-cycle usage, does not apportion a long session's ACUs by date, and cannot show self-serve quota remaining, credit balance, percentage, or reset because Devin does not expose those values through this API. The endpoint's created_after filter defines which sessions are included.

Example setup:

DEVIN_ENABLED=true
DEVIN_MODE=organization_sessions
DEVIN_API_KEY=cog_your_service_user_key
DEVIN_ORG_ID=org-your-id
DEVIN_USER_ID=
DEVIN_SESSION_WINDOW_DAYS=30

A successful 200 response from /v3/organizations/{org_id}/sessions?first=1 confirms that the organization-session endpoint is permitted for the configured service user. Do not claim real-time freshness beyond vendor behavior: the dashboard cache and refresh schedule only control when it asks the vendors again.

Security and behavior

  • The Devin Bearer token is read from environment settings. Codex uses the official CLI's local credential store instead of an API key setting. Neither credential reaches the browser, and neither is returned, logged, or included in error messages.
  • Upstream error bodies and process stderr are discarded; users see only a generic provider error.
  • Provider calls have independent timeouts and successful results use a monotonic TTL cache. Only successful results are cached.
  • The browser makes one /api/usage request immediately and repeats using the response refresh interval. A network failure keeps the previous cards visible and marks the header connection state.
  • GET /health returns exactly {"status":"ok"}.
  • GET /api/usage returns normalized Codex and Devin usage, server_time, demo_mode, and refresh_interval; it sends Cache-Control: no-store.
  • GET / serves the dashboard HTML with Cache-Control: no-store. Static assets are under /static.

Docker Compose

Create .env, then build and run:

cp .env.example .env
docker compose up --build -d
curl http://localhost:8080/health

The container uses Python 3.12 slim, a non-root user, read-only root storage, a /tmp tmpfs, no-new-privileges, and a healthcheck. docker-compose.yml uses restart: unless-stopped and maps port 8080. The image does not bundle the Codex CLI or a personal Codex credential store: Docker works out of the box for demo mode and Devin, while real Codex should normally run directly on the authenticated host. An operator may deliberately build and securely provision the official CLI and credential store, but credentials should never be baked into an image.

Kiosk mode

start-kiosk.sh accepts DASHBOARD_URL (default http://localhost:8080) and searches in this order: chromium, chromium-browser, google-chrome, google-chrome-stable. It launches the first available browser with:

--kiosk --noerrdialogs --disable-infobars --disable-session-crashed-bubble

The script exits clearly if no supported browser is installed.

systemd dashboard service

Install the project at /opt/usage-kiosk, create /opt/usage-kiosk/.env, and adjust User if necessary:

[Unit]
Description=AI Usage Dashboard
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=usagekiosk
Group=usagekiosk
WorkingDirectory=/opt/usage-kiosk
EnvironmentFile=/opt/usage-kiosk/.env
ExecStart=/opt/usage-kiosk/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8080
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

Save as /etc/systemd/system/usage-kiosk.service, then run sudo systemctl daemon-reload, sudo systemctl enable --now usage-kiosk, and inspect sudo journalctl -u usage-kiosk if needed.

systemd browser launch

For systems where the display manager and X authority are ready before the browser unit starts, save this as /etc/systemd/system/usage-kiosk-browser.service:

[Unit]
Description=AI Usage Dashboard Chromium Kiosk
After=display-manager.service usage-kiosk.service
Requires=usage-kiosk.service

[Service]
Type=simple
User=usagekiosk
WorkingDirectory=/opt/usage-kiosk
Environment=DISPLAY=:0
Environment=XAUTHORITY=/home/usagekiosk/.Xauthority
ExecStart=/opt/usage-kiosk/start-kiosk.sh
Restart=on-failure
RestartSec=5

[Install]
WantedBy=graphical.target

Enable it with sudo systemctl daemon-reload and sudo systemctl enable --now usage-kiosk-browser. The DISPLAY, XAUTHORITY, user home, and display-manager readiness vary by distribution and graphical setup; a desktop-session autostart entry is preferable when systemd starts too early for the X session.

Kiosk X session/autostart

For a dedicated graphical user, install the launcher at /opt/usage-kiosk/start-kiosk.sh, make it executable, and use an X session autostart entry such as ~/.config/autostart/usage-kiosk.desktop:

[Desktop Entry]
Type=Application
Name=Usage Dashboard Kiosk
Exec=/opt/usage-kiosk/start-kiosk.sh
Terminal=false
X-GNOME-Autostart-enabled=true

A window-manager session may instead run /opt/usage-kiosk/start-kiosk.sh from its session startup. Configure automatic graphical login for the kiosk user only on a physically secured display. Boot ordering is: network, usage-kiosk.service, graphical session, then Chromium kiosk. Keep the dashboard service bound to the required network interfaces and use a firewall or reverse proxy when it is not on a trusted display network.

Troubleshooting

  • Codex error: verify codex is installed, codex login completed, and CODEX_COMMAND points to one executable. Run the CLI manually outside the dashboard to confirm its availability.
  • Devin Enterprise error: verify Enterprise entitlement, service-user ViewAccountConsumption permission, token scope, API URL, and configured user or organization scope. A 401/403/404 on optional organization metadata only removes the max-limit fields.
  • Devin organization sessions 403: verify that DEVIN_MODE=organization_sessions, DEVIN_ORG_ID, the cog_ service-user key, and ViewOrgSessions are correct. Confirm access with /v3/organizations/{org_id}/sessions?first=1; this mode does not use Enterprise consumption permissions.
  • No Devin limit: Enterprise consumption remains valid, but no numeric limit is invented when the organization max limit is unavailable. Organization sessions mode intentionally has no quota, percentage, credit, or reset fields.
  • Connection error in the header: the browser could not complete the dashboard request; existing provider rows remain on screen and retries continue on the configured interval.
  • Stale-looking values: successful provider results are cached for PROVIDER_CACHE_TTL; vendor processing and reporting delays are outside this dashboard's control.
  • Container health failure: inspect docker compose logs dashboard, verify .env, and test http://localhost:8080/health.
  • Kiosk failure: confirm one of the four supported Chromium executable names is on PATH, then check DASHBOARD_URL and the graphical session environment.

Development checks

python -m pytest -q
python -m compileall app
docker compose config