LazyToken Enterprise — Administrator Guide
Everything an administrator needs to install LazyToken, enroll a developer fleet, manage policy and access, apply a license, and read the dashboard. Commands and endpoints below are the ones the product actually implements; see for the full API surface and server/openapi.yaml for the agent↔server wire protocol.docs/CONTRACT.md
Conventions in this guide
- The binary is
ltk; the product is LazyToken. - API errors are always
{ "error": { "code", "message" } }. - All admin write actions are recorded in the audit log.
1. Architecture in one paragraph #
LazyToken has three parts. The agent (ltk) runs on each developer machine, filters coding-agent terminal output before it reaches the model, and reports numbers only to your server. The server (Node/Fastify/PostgreSQL) ingests those metrics, signs and serves policy, enforces licensing softly, and runs the daily rollup and monthly ROI reports. The dashboard (React SPA, served by the same server) is where you read ROI, teams, opportunities, security events and fleet status. The server is self-hosted — you own the data; the vendor has no tenant and no access.
2. Installation #
Three supported paths, from simplest to strictest.
2.1 Docker Compose (MVP / pilots) #
From the repo (deploy/docker-compose.yml):
cd deploy
cp .env.example .env
# Edit .env — at minimum:
# POSTGRES_PASSWORD=<strong>
# JWT_SECRET=$(openssl rand -hex 32)
# LICENSE_PUBLIC_KEY=<base64 ed25519 pubkey> # optional until you install a license
docker compose up -d
curl http://localhost:8080/healthz # {"status":"ok"}
The compose stack is PostgreSQL 16 + the server (which also serves the dashboard and runs the rollup job in-process). It publishes port 8080.
2.2 Kubernetes / Helm (GA) #
Chart at deploy/helm. Create secrets out-of-band, then install:
kubectl create namespace lazytoken
kubectl -n lazytoken create secret generic lazytoken-secrets \
--from-literal=JWT_SECRET="$(openssl rand -hex 32)" \
--from-literal=SSO_ENCRYPTION_KEY="$(openssl rand -hex 32)"
helm install lazytoken ./deploy/helm -n lazytoken -f my-values.yaml
kubectl -n lazytoken rollout status deploy/lazytoken
The chart runs database migrations as a pre-install/pre-upgrade Helm hook Job before the server starts, ships an optional bundled PostgreSQL, an HPA, an Ingress, and a NetworkPolicy (networkPolicy.enabled=true) that technically enforces the zero-egress posture. Review deploy/helm/values.yaml for all knobs.
2.3 Air-gapped #
The strictest deployment — zero internet. Images are carried in as a tar, the license is a signed file (no license server to phone home), and a NetworkPolicy restricts server pods to DNS + in-cluster PostgreSQL. Full runbook: .deploy/AIR-GAPPED.md
The server makes no unsolicited outbound calls. The only optional egress paths are OIDC SSO (your IdP), and monthly-report delivery (SMTP / Slack) — both off by default, and only to endpoints you explicitly configure.
Prerequisites & caveats (read before production) #
JWT_SECRETis required in production — the server refuses to boot without it.- PDF ROI reports are rendered with Chromium via Playwright. If Chromium is not installed on the server image,
GET /v1/reports/monthly.pdfreturns503until you runnpx playwright install chromium. - Agent installer signing is not yet turned on. The packaging scripts under
deploy/packaging/(MSI/pkg/deb/rpm) build the installers, but production requires EV code signing (Windows), Apple notarization (macOS), and GPG signatures (Linux). Treat signing as a release-engineering step to complete before a regulated rollout. - SSO is implemented but should be validated against your specific IdP before go-live (see §7) — OIDC and SAML are wired, but each IdP has quirks.
3. First-admin bootstrap #
You need exactly one admin to start; everyone else is created from the dashboard.
Via environment (Docker/Helm): set BOOTSTRAP_ADMIN_EMAIL, BOOTSTRAP_ADMIN_PASSWORD, and BOOTSTRAP_ORG_NAME. On first boot, while no users exist, the server creates the org + admin. It is a no-op once any user exists.
Via CLI (local/dev):
cd server
npm run bootstrap:admin -- --email admin@acme.com --password '<strong>' --org "Acme"
Bootstrapping also creates the org's Ed25519 policy-signing keypair (the private key never leaves the server) and a signed default policy.
Log in to get a 12-hour session token:
curl -sX POST http://localhost:8080/v1/auth/login \
-H 'content-type: application/json' \
-d '{"email":"admin@acme.com","password":"<strong>"}'
# -> { "token": "<jwt>", "user": {...} }
Use that token as Authorization: Bearer <jwt> for the admin API. GET /v1/auth/me returns the current identity.
4. Licensing #
Licenses are offline, Ed25519-signed JSON bound to {org, seats, tier, expiry}. There is no license server and no activation call. Enforcement is soft: over-seat or expired produces warnings in GET /v1/license/status plus audit events — ingest and policy are never blocked. The one hard limit: new device enrollments fail with 403 SEAT_LIMIT when seats are exhausted; already-enrolled devices keep working.
Vendor side (issue a key):
cd server
npm run gen:license -- keygen # one-time; keep the private key offline
npm run gen:license -- create --key <priv> --org "Acme" \
--seats 50 --tier enterprise --expiry 2027-06-30
npm run gen:license -- verify --pub <pub> --license <key>
Customer side: configure LICENSE_PUBLIC_KEY (base64 raw 32-byte pubkey) on the server, then install the key once:
PUT /v1/admin/license { "license_key": "<the signed key>" }
Check status any time: GET /v1/license/status.
5. Enrolling devices #
5.1 Create an enroll token #
Admin creates an org-scoped enrollment token (optionally with a max-uses limit and expiry):
POST /v1/admin/enroll-tokens { "max_uses": 200, "expires_at": "2026-12-31T00:00:00Z" }
# -> { "token": "ORG-XXXXXXXXXXXX", ... }
List / revoke via GET /v1/admin/enroll-tokens and DELETE /v1/admin/enroll-tokens/{id}.
5.2 Enroll a machine #
On the developer machine (or via MDM):
ltk enroll --server https://lt.company.internal --token ORG-XXXXXXXXXXXX
ltk status # enrollment, server, active policy, reporting state
ltk enroll stores the device JWT + refresh token + pinned policy public key in the OS keychain (service lazytoken) — never in files — and fetches the initial signed policy. From then on the agent reports metrics asynchronously and syncs policy on its own.
Useful agent commands:
| Command | What it does |
|---|---|
ltk enroll --server <url> --token <ORG-...> | Enroll this device |
ltk status | Show enrollment, active policy and reporting status |
ltk policy sync | Fetch, verify (Ed25519) and apply the latest signed policy |
ltk report flush | Collect new metrics and send buffered batches now |
ltk report status | Show pending records, cursor and backoff state |
ltk update [--check] | Update the agent from your server's channel — verifies sha256 + Ed25519 before an atomic self-replace |
ltk sdlc sync | Fetch and apply signed org SDLC templates (CLAUDE.md/AGENTS.md standards) |
Self-update pulls from your internal server only (
GET /v1/agent/latest?channel=stable|beta, per CONTRACT §7): the download is verified against its published sha256 and an Ed25519 signature (the key pinned at enrollment) before an atomic replace that leaves the old binary intact on any failure — no root required. You can also roll out through the MDM/packaging channel (deploy/mdm/). Note: a dedicated open-source-licenses view (ltk licenses) is still a planned CLI addition (see).05-LEGAL-COMPLIANCE.md
5.3 Fleet rollout via MDM #
Silent-install scaffolding lives under deploy/mdm/:
- Intune —
deploy/mdm/intune/Install-LazyToken.ps1 - Jamf —
deploy/mdm/jamf/install-lazytoken.sh - GPO —
deploy/mdm/gpo/Deploy-LazyToken-GPO.md
Package the agent with deploy/packaging/ (WiX MSI, pkg, deb/rpm), pre-seed the --server and --token values, and push through your MDM. Manage devices from the dashboard Fleet screen or GET /v1/admin/devices (revoke with DELETE /v1/admin/devices/{id}).
6. Teams, users and RBAC #
Four roles: admin, lead, viewer, finance.
| Capability | admin | lead | finance | viewer |
|---|---|---|---|---|
| Read analytics dashboard | ✔ | ✔ (team) | ✔ | ✔ |
| Download ROI PDF | ✔ | ✔ | ✔ | — |
| Manage policies / assignments | ✔ | — | — | — |
| Manage teams / users / devices | ✔ | — | — | — |
| SSO / license / settings | ✔ | — | — | — |
Endpoints:
- Teams —
GET/POST /v1/admin/teams,PATCH/DELETE /v1/admin/teams/{id} - Users —
GET/POST /v1/admin/users,PATCH/DELETE /v1/admin/users/{id}(assign role + team) - Devices —
GET /v1/admin/devices,DELETE /v1/admin/devices/{id}(revoke)
Every write here lands in the audit log (GET /v1/admin/audit-log), which is append-only.
7. SSO (OIDC / SAML) #
Configure per protocol; both are supported. Admin-only.
GET /v1/admin/sso # list configured protocols (secrets masked)
PUT /v1/admin/sso/oidc # { enabled, config: { issuer, client_id, client_secret, ... } }
PUT /v1/admin/sso/saml # { enabled, config: { entryPoint, idpCert, ... } }
DELETE /v1/admin/sso/{protocol}
Login flows: GET /v1/auth/oidc → …/oidc/callback, and GET /v1/auth/saml → …/saml/acs. The OIDC client_secret is AES-256-GCM encrypted at rest using a key derived from SSO_ENCRYPTION_KEY (env only); secrets are never logged, echoed, or written to the audit log. SSO login state is DB-backed so it works across Helm replicas.
Caveat: the OIDC/SAML code paths are implemented and unit-tested, but each IdP (Azure AD, Okta, Google Workspace, ADFS) has its own claim/cert quirks. Validate the full login round-trip against your actual IdP in a staging org before enabling it for all users. MFA is delegated to your IdP.
8. Policies #
A policy is a signed JSON document the agent verifies before applying. Manage via:
GET/POST /v1/admin/policies
GET/PUT/DELETE /v1/admin/policies/{id}
POST/DELETE /v1/admin/policies/{id}/assignments # assign to team or device
Each edit bumps the version and re-signs with the org's private key; the server serves the exact signed bytes and supports If-None-Match/304. Policy body (see CONTRACT §3) controls:
- reporting — enabled, flush interval, whether to send
project_hash. - filters —
exclude_commands, filterlevel(strict/balanced),tee. - privacy —
anonymize_devices(the "Dev #N" mode). - updates — agent update
channel(stable/beta). - firewall (Context Firewall / DLP) —
enabled,sensitivity, per-rule toggles (aws-key,github-token,private-key, …), custom org regex rules, and command guardrails (e.g. blockcaton**/.env*).
Unassigned devices get the org default policy. Assignments let you target a team (all its devices) or a single device. Because policies are signed, a compromised server cannot push a malicious policy — the agent rejects an invalid signature and keeps the last good one.
9. Settings (retention, token prices, report delivery) #
GET /v1/admin/settings
PUT /v1/admin/settings
- retention_days — how long raw metrics are kept before the purge (default 90; the rollup keeps the daily aggregates).
- token_prices — USD per MTok, keyed by
ai_agentwith a"default"fallback (e.g.{ "default": 3, "copilot": 2 }). This drives every USD figure on the dashboard and in the ROI report — set it to the org's real contract pricing. - reporting — monthly ROI delivery config (
enabled,email_recipients,slack_webhook_url). When absent orenabled:false, the server sends nothing (air-gapped-safe default).
Doc gap noted:
PUT /v1/admin/settingsaccepts areportingobject (seeserver/src/routes/admin/org.ts), but theopenapi.yamlrequest schema for that endpoint currently lists onlyretention_daysandtoken_prices. The behavior is real; the spec is slightly behind.
10. Scheduled ROI reports #
The monthly ROI report summarizes savings and dollar value for a calendar month.
- On demand:
GET /v1/reports/monthly.pdf?month=YYYY-MM(admin/finance/lead) — Chromium-rendered PDF. Returns503if Chromium isn't installed (npx playwright install chromium). - Scheduled delivery: configure
reportingin settings (email recipients and/or a Slack webhook). The server runs the report job at boot and everyREPORTS_INTERVAL_HOURS, delivering the previous month's report and logging each send inreports_log. Email uses theSMTP_*env config; when SMTP is unset, email delivery is a logged no-op. - Manual job run:
npm run job:reports.
11. Reading the dashboard #
| Screen | Backing endpoint | What it shows |
|---|---|---|
| Executive | GET /v1/analytics/summary, …/trend | Tokens saved, USD value, active/total devices, adoption %, 30/90-day trend |
| Teams | GET /v1/analytics/by-team | Savings and adoption per team; drill-down; anonymization mode |
| Commands | GET /v1/analytics/by-command | Which tools save the most |
| Opportunities | GET /v1/analytics/opportunities | High-volume, low-savings passthrough tools to tune next |
| Security | GET /v1/analytics/security-events | Context Firewall events by type over time (type/tool/time only — never a value) |
| Fleet | GET /v1/admin/devices | Enrolled devices, silent/stale devices, agent versions |
| Policies / Settings | admin endpoints above | Policy editing, retention, prices, SSO, license |
USD figures come from the token_prices table (§9). summary.adoption_pct is active devices (seen in the last 7 days) against seats.
12. Operations quick reference #
| Task | How |
|---|---|
| Health check | GET /healthz |
| Run the daily rollup manually | npm run job:rollup |
| Run the monthly report manually | npm run job:reports |
| Rate limits | Ingest is 60 req/hour/device (INGEST_RATE_LIMIT_PER_HOUR) |
| Retention purge | Runs with the rollup; window = retention_days |
| Audit trail | GET /v1/admin/audit-log (append-only) |
| Backups | Back up PostgreSQL; the org's policy private key lives in the orgs table — protect DB backups accordingly |
13. Related documents #
- QUICK-START.md — 15-minute local setup.
- DEMO.md — synthetic-data sales demo.
- SECURITY-WHITEPAPER.md — trust model, allowlist, Context Firewall, air-gapped, security questionnaire.
- ONBOARDING-CHECKLIST.md — the implementation playbook.
CONTRACT.md,— the wire protocol and full API.server/openapi.yaml