Skip to content

Daemon API ​

The daemon serves everything a surface other than an agent needs: a menu bar, a script, a dashboard you build yourself. It answers from the same code as the MCP tools and the CLI (ADR 0010), so all three give the same figures.

The full contract is the generated OpenAPI 3.1 document, docs/api/openapi.json. This page is the overview.

Calling it ​

The daemon listens on 127.0.0.1:7878. Every /api/* route needs the bearer token the daemon writes to ~/.tokenops/daemon.url (mode 0600):

bash
TOKEN=$(python3 -c "import json,os;print(json.load(open(os.path.expanduser('~/.tokenops/daemon.url')))['dashboard_token'])")
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:7878/api/glance

/healthz, /readyz and /version need no token, so a supervisor can probe them.

The daemon answers only requests addressed to this machine: a loopback Host (127.0.0.0/8, ::1, localhost), the listen address, or a name it was told about (tls.hostnames, the advertised tokenops.local, allowed_hosts). Browser requests from another site — an Origin that is not local, or Sec-Fetch-Site: cross-site — are refused with 403. CLI tools and SDKs send neither header and are unaffected; the probes answer whatever the Host.

What it never serves ​

Derived figures only: windows, spend, grades, counts, tips. Never prompt text, file contents or transcripts. Where a question is built on the operator's own words, the API answers without them:

  • /api/story returns each task's shape (turns, files, frictions, timing) with its title withheld, and says titles_withheld: true.
  • /api/coach/prompts returns counts and recommendations, with every quoted instruction left out.

The MCP tools keep the words, because the agent reading them is the operator's own.

Reading ​

RouteAnswers
GET /api/glanceSession budgets, plan headroom and one ranked insight; boundaries lists the boundaries that hold now, as advice; plan_use gives each provider's plan utilization and coverage over the last 7 days
GET /api/findingsWhat the coach and the session analysis observed, ranked, with what to do
GET /api/plans/headroomEach plan: usage, the vendor's windows, spend against the limit, risk
GET /api/plans/session-budgetEach plan window: share used, pace, what to do
GET /api/plans/advicePlan right-sizing over days (30 by default, up to 90): each plan's windows replayed against its other tiers, metered overflow and the API at list price. Advice only
GET /api/statusReadiness, blockers, warnings, next actions
GET /api/modeOperating mode and what each subsystem may do
GET /api/coachThe coach's dials and each power's autonomy
GET /api/configThe active configuration, secrets redacted
GET /api/data-sourcesEvents per source and each reader's health
GET /api/vendor-usageWhich usage sources are on and producing
GET /api/sourcesEach reader's ingestion health
POST /api/sources/refreshAsk the usage readers to poll now, at most every 30 seconds: 202 with how many were asked, or 429 with Retry-After
GET /api/dxAgent DX, graded, with the single change worth making
GET /api/storyRecent work, task by task, titles withheld
GET /api/coach/promptsPrompt scoring, quotes withheld
GET /api/spend/summarySpend and tokens over a window
GET /api/spend/seriesSpend and tokens over time
GET /api/spend/forecastDaily spend projected forward
GET /api/spend/cache_statsPrompt cache hits and savings
GET /api/spend/topTop consumers, ranked on the API equivalent
GET /api/spend/commitsWhat each commit cost: the agent work that led to it, priced; subjects withheld
GET /api/spend/unitsCost per commit, merged pull request, recorded and achieved outcome and change class against the 4 weeks before (?since=; the last 7 days when omitted), with plan-window shares on a flat-rate plan and the work no unit took
GET /api/spend/wasteAvoidable usage by waste class, each token in one class, in API-equivalent dollars and plan-window share (?since=; the last 7 days when omitted), what was realized beside it, and the guardrail to switch on next
GET /api/spend/burn-rateUsage over the last hours
GET /api/spend/tagsSpend per project, client or cost centre, tagged on the daemon by your allocation rules (?by=project|client|cost_center&since=&until=; project over the last 30 days when omitted), with the untagged work kept apart
GET /api/spend/exportSpend and usage as FOCUS 1.2 CSV for FinOps tools (?since=&until=; the month so far when omitted)
GET /api/scorecardThe wedge KPI scorecard
GET /api/pricingThe rate card TokenOps costs with
GET /api/workflowsRecorded workflows
GET /api/workflows/{id}One workflow's trace and its waste
GET /api/optimizationsOptimization recommendations
GET /api/routing/proposalsModel upgrades waiting on you
GET /api/decisions/{id}Why a decision was made
GET /api/boundariesEach action boundary, how often it matched, where it is enforced, and what waits on you
GET /api/rules/analyzeRule files analysed
GET /api/rules/compressRule files compressed
GET /api/rules/conflictsConflicts between rules
GET /api/rules/injectRules to inject for a task
GET /api/auditThe audit log, newest first
GET /api/domain-eventsDomain-event counts

A plan that is not set up answers {"error": "plans_unconfigured", "hint": …}, the same as the MCP tools, so a surface can show the hint instead of an empty screen.

Changing ​

RouteDoes
POST /api/modeSets the operating mode
POST /api/budgetsCreates, updates or deletes a budget
POST /api/routing/rulesCreates, updates or deletes a routing rule
POST /api/plansBinds a provider to a plan, or clears it
POST /api/preferred-modelsSets or clears a provider's preferred model
POST /api/routing/decisionsAnswers a routing proposal
POST /api/boundaries/decisionsDenies what a boundary asked about ({"id", "decision": "deny", "session"}); an approval only comes from tokenops approve at a terminal or Claude Code's prompt
POST /api/outcomesRecords your judgement of an execution
POST /api/coachApplies a coach preset, or changes its dials

The write routes are guarded more tightly than the reads:

  • They exist only behind the token.
  • They take only an application/json body. A browser cannot send that to another origin without a preflight the daemon never answers, so a web page cannot change your setup.
  • An unknown field is refused, not ignored, so a typo cannot pass as accepted.
  • Every accepted change is written to the audit log as config_change with actor api (GET /api/audit?actor=api).
  • A change to config answers first, then restarts a supervised daemon so it takes effect. Unsupervised, the answer says how to restart it.
bash
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"provider":"anthropic","plan":"claude-max-20x"}' http://127.0.0.1:7878/api/plans

Connecting a vendor's login (tokenops vendor-usage setup) is not on the API. It goes through your browser and the OS's consent prompt, which belongs in a terminal or an agent conversation, not a background daemon.

Apache 2.0 licensed.