Reference: dashboard routes
Every route below sits behind the dashboard's one identity gate unless noted. Which credential it checks is the dashboard.auth strategy in config.yaml: access — a browser signed in through Cloudflare Access or, for machine callers, a service token (the default when ACCESS_TEAM_DOMAIN and ACCESS_AUD are set); token — an Authorization: Bearer header holding the secret in DASHBOARD_TOKEN (or the env var dashboard.token.env names), which resolves to the one actor dashboard.token.actor names; or none — no credential, served only to loopback callers of a localhost deployment and refused everywhere else (the default without Access). None of them set caching headers that would let a proxy or browser cache a response beyond the request that made it (no-store throughout) — every load is live.
The replacement sidebar offers Home, Delivery, Costs and Metrics; capabilities hide unavailable reports. The same navigation opens as a drawer on phones. Settings is a global destination with Channels, MCP servers, Identities, Runtime and Installation sections; MCP servers and Runtime follow their capabilities. Docs remains available. The account menu selects Mine (default), read-only Person or privileged All people for work pages; this selection changes no sender or grants.
Every page route below also answers its data alone as JSON to a request whose Accept header is application/json — the same status and the same content the page paints from. The dashboard navigates between its pages that way, without a document load; an agent may read a page the same way.
| Route | Shows | Notes |
|---|---|---|
GET /runs | Active runs, newest first | Default view excludes finished runs; each row's link carries that run's capability token — the index itself is gated specifically because of this |
GET /runs?all=1 | Active and finished runs | Only meaningful with runHistory configured — otherwise there's nothing finished to show |
GET /runs?mine=1 | Compatibility filter for the actual requester, within the authorized global work view | The replacement has no per-page Show mine switch. Use the global account menu; an email match never makes the account a Slack requester |
GET /runs/<id> | One run: request → steps (tool calls, results) → answer | Live via SSE while the run is active; served as a static page, no token needed, once it's in history |
GET /runs/<id>/events | Raw SSE event stream for that run | What the run page itself consumes; resumable via Last-Event-ID |
GET /runs/<id>/friction | Why a finished run was slow, if it was | Read-only diagnosis, no side effects |
GET /runs/unit/<pipeline>:<unit> | One ship unit's story: its one thread's runs in round order, each opening to its timeline, with a search over the whole thread | What runs unit answers, as a page; a unit you may not see is the same 404 an unknown run gives. The pipeline's own run page lists its units, and a conductor's run page lists the runs it spawned |
POST /runs/<id>/stop?mode=soft|hard | — | Stops a live run; soft lets it wrap up and answer, hard aborts in-flight |
GET /residents | Every onboarded repo, its lifecycle state, its disk gauge (used/total) and how many runs are on it; each row folds open to those runs — stopwatch, run link, the worktree each holds (ref, commit, OS user, deps, size), the idle worktrees, and the room left on the disk | The dashboard twin of repo list; the fold lists the runs the viewer may read, live |
GET /residents?stream=1 | The residents index feed (SSE): run rows as the registry publishes them, and the listing again whenever a run's worktree is bound or released | What the index consumes to stay current; no timer re-reads the resident Worker |
GET /residents/<owner>/<name> | One repo's resident: mirror status, warm checkout, active thread worktrees, and its disk — used/total, free, the reserve it keeps back, headroom in "more trees", and every component (mirror, deps, checkout, each thread tree, leftover caches) | The same numbers the resident's attach gate decides on — see onboard a repo → Disk |
GET /costs | Daily spend across every configured group | Priced from a snapshot of Cloudflare's and (optionally) Anthropic's billing data, taken daily (costs.snapshot.everyHours) or on request with costs snapshot; the page names the snapshot, its age and when the next is due |
GET /costs/<group> | Spend for one group | ?view=users|threads|channels|agents|models opens that dimension's tab — the same dollars laid against who started the runs, the thread or channel they ran in, the agent, or the model; ?stream=1 is the page's status feed (SSE: the snapshot's stamp, a take in flight, the next due time — the page repaints its figures when a new snapshot lands) |
GET /costs/<group>.json | Same data, machine-readable | For scripting/alerting, not for embedding a live dashboard elsewhere; carries snapshot.takenAt; 503 with Retry-After before the first snapshot |
GET /costs/<group>/<view>.json | One dimension's report, machine-readable | <view> is users, threads, channels, agents or models: the rows the tab shows (key, runs, turns, llmUsd, cloudUsd, totalUsd, unpricedTokens, byModel), the coverage and the reconciliation; the same as costs by <dimension> on the command surfaces; 503 with Retry-After before the first snapshot |
GET /metrics | Usage for the authorized review report | ?from=YYYY-MM-DD&to=YYYY-MM-DD: inclusive UTC finish days, at most 90; the selected Mine/Person/All people scope filters before aggregation |
GET /metrics/health | System health from the same recorded review report | Shares the exact range; ratios retain their denominators, and unavailable diagnostics are not inferred |
GET /metrics/week | Week over week from the same recorded review report | The range beside the same range whole weeks earlier, cut at the same time while today is filling; an earlier range outside the 90 days kept is said, not compared |
GET /metrics.json | The same scoped review report as JSON | Both tabs retain this exact range in their download link; metrics trend remains a separate command/API contract |
GET /metrics/week.json | The Week over week tab's report and comparison as JSON | The tab's download link keeps its exact range |
GET /delivery | Delivery indicators for the first configured repository — issue-to-merge time, first-pass CI, review rounds, the findings and the share resolved with no human edit, per week and per unit | From the repository's snapshot of GitHub's facts, refreshed on an interval and dated in the footer, plus the run history you may see; a read that stopped at its cap says over the tiles that they cover the newest pull requests only and marks the incomplete weeks; ?weeks=n or ?since=YYYY-MM-DD; ?fresh=1 reads GitHub now |
GET /delivery/<owner>/<name> | The same for one configured repository | The command twin, delivery report --repo, takes any repository |
GET /delivery/<owner>/<name>.json | Same data, machine-readable | |
GET /plane | Compatibility address for Home | The replacement route resolves to /home; it is not a second dashboard |
GET /settings | Global Settings: MCP servers when configured, else Channels | Registry reads and writes use the actual account and its grants; me remains the Access account's own tier. Missing/mismatched seed shows unavailable and Reload without editable controls |
GET /settings/connect | Sign in to approve a local CLI or MCP client and manage your connected devices | The link carries a bearer digest, never the bearer; approval requires the personal MCP grant, and revocation removes that digest from the credential store |
GET /settings/mcps | Every MCP server your runs can reach, by scope, with an add form; ?channel=<id> lists that channel's scope beside the org's | Add and Connect hand back the one-time link below; a credential never passes through the page |
GET /settings/channels | The channels that carry a scope, with the setting names each one has | The dashboard twin of config overrides |
GET /settings/channels/<channel id> | One channel's scope as a form: agent, models, effort, the boundary, instructions | Save is config set channel --channel <id>; Clear is config clear; the instructions box is config instructions |
GET /threads | Compatibility address for Home thread discovery | The replacement resolves to /home/threads. Discovery shows its loaded coverage and adds no second composer |
GET /threads/<id> | One thread: your turns and the runs they started, each with its route, its work folded and its reply; a live run draws from its own stream | <id> is a thread of your lane, or another channel's thread by its whole key — read-only there; one you may see nothing of is the same 404 an unknown run gives |
POST /threads/<id>/send | — | { text } into the pipeline as you: 202 { runId, viewPath } when a run started, 200 { reply } when it answered inline (a hand-back, help, a steer); same-origin JSON only; another channel's thread is refused |
GET /settings/installation | The running config.yaml's behaviour knobs with the value in force, and the capabilities that are on | Read-only by construction: a projection by allow-list, no env var name or URL ever on the page |
GET /mcp/connect/<nonce> | The one-time MCP credential-paste form | Bound to whoever mints it or first opens it; single use, expires in 10 minutes |
GET / | — | Not gated: a 302 to /threads, and nothing else — the Access application lists path prefixes, and / would cover the bearer routes |
GET /healthz | {ok, inFlight, draining, catchUp} | Not gated — this is the process health probe, meant to be hit by the deploy tooling and the container platform |
Command routes (/api/<group>.<verb>)
Every registered command has an HTTP twin behind the same dashboard gate, plus an MCP tool of the same name — one definition, every surface (explanation). A write is POST-only; a read takes either verb (GET with a kebab-case query string, POST with a camelCase JSON body). The Scope column is what an operator identity or service token must hold.
| Route | Methods | Action | What it does |
|---|---|---|---|
/api/identity.challenge | POST | identity:exec | Complete challenge through fresh two-sided account verification. |
/api/identity.list | GET, POST | identity:read | Read your verified identity operation. |
/api/identity.link | POST | identity:exec | Complete link through fresh two-sided account verification. |
/api/identity.unlink | POST | identity:exec | Complete unlink through fresh two-sided account verification. |
/api/identity.recover | POST | identity:exec | Complete recover through fresh two-sided account verification. |
/api/help.show | GET, POST | help:read | How to talk to this bot — the agents, forcing one, changing a route in the thread — for a person asking about the bot itself, never for a task or a thing to show. |
/api/help.commands | GET, POST | help:read | Every chat command by group, the grammar, and the per-request directives. |
/api/status.show | GET, POST | status:read | Which build this process runs and, when known, the base config loaded at startup. |
/api/config.show | GET, POST | config:read | The effective agent/model/effort for you in this channel, the defaults, both scopes, and what is restricted; without a channel (a browser, a token, the CLI), your settings outside any channel. |
/api/config.overrides | GET, POST | config:read | Which channels carry a scope (a config.yaml block or a runtime override) and which settings each one names — never a value; config show --channel <id> reads one. |
/api/config.channels | GET, POST | config:read | The channels you may pick settings or MCP servers for, by name: the channels the bot is in that you may read, plus any that already carry a scope; listed: false says the bot could not list its channels and only the scoped ones are here. |
/api/config.set | POST | config:write | Set the agent, model, effort, verbosity, harness, boundary or default repository (--repo owner/name) for a channel (gated), or agent settings for yourself; per-agent forms take --models.<agent>, --efforts.<agent> and --harness.<agent>. Set the intake gate's mode for a thread (gated like the channel), a person's GitHub binding (config set user --user <id> --github <login>, identity admins — never your own: it is not yours to type), or the pull-request watch (config set org|repo --pulls.watch on|off with its caps, repo taking --repo <owner/name>). |
/api/config.clear | POST | config:write | Drops every runtime override of a channel (gated), of yourself (your GitHub binding stays — it is an identity admin's write), or of a thread (gated like the channel); config clear user --user <id> removes one person's GitHub binding (identity admins). Static config.yaml values show through again. |
/api/config.instructions | POST | config:write | Custom instructions for a channel (gated) or for yourself — advisory prompt content that never changes agent, model, or permissions. |
/api/credentials.inspect | POST | credentials:exec | Inspect a live run's credential boundary; reports only counts and booleans, never credential contents. Does not test publication or revocation. |
/api/runs.list | GET, POST | runs:read | List runs (live and persisted, newest first) — metadata only, never message text. |
/api/runs.claims | GET, POST | runs:read | List persisted unknown native claim observations. These are not current holds or commit receipts; missing observations do not prove absence. |
/api/runs.get | GET, POST | runs:read | One run's record and cost in dollars per model (or unpriced); --include messages adds events with free text wrapped as untrusted content; --include audience adds a structural refusal diagnostic when recorded. |
/api/runs.events | GET, POST | runs:read | A page of one run's events after --after-seq (server-capped); free text wrapped as untrusted content. |
/api/runs.friction | GET, POST | runs:read | One run's friction diagnosis (live: computed now; persisted: as stored). |
/api/runs.check | GET, POST | runs:read | Inspect an original recorded check without running it again; saved output is wrapped as untrusted content. |
/api/runs.settlecheck | POST | runs:write | Retire one original check after its run finishes, only when saved proof verifies that the original process ended. |
/api/runs.stop | POST | stop:write | End the current turn, clean up the runtime, and keep the run as stopped by user. Records who requested the stop. Needs stop permission for the run. |
/api/runs.kill | POST | stop:write | Kill the runtime without a graceful turn shutdown and delete the run from live and saved run views. Destructive. Needs stop permission for the run. |
/api/runs.unit | GET, POST | runs:read | A ship unit's runs in round order — its thread's, live and finished, each with its round — from one read. |
/api/runs.children | GET, POST | runs:read | The runs one run spawned — a conductor's children, live and finished — oldest started first. |
/api/runs.findings | GET, POST | runs:read | A pull request's findings ledger — every review finding by id with its severity, where it was raised, what the coding run recorded against it and whether the next review agreed — read from the run records alone. |
/api/runs.search | GET, POST | runs:read | Search one session's log — a thread's conversation on one agent, every run of it — for words: the matching turns in relevance order, each with its run; snippets wrapped as untrusted content. |
/api/steer.run | POST | steer:write | Fold words into a live run at its next step boundary, by run id. |
/api/review.abridge | POST | review:write | Abridge a finished PR review's reading diff with meat.dev on the bot host (one Opus-class call) and store it on the run; idempotent — a stored one is answered, not recomputed. |
/api/review.status | GET, POST | runs:read | Read the progress of an abridged review diff without starting or recomputing it. |
/api/friction.report | GET, POST | friction:read | Ranked recurring friction patterns across recent runs — read-only, GitHub never consulted. |
/api/friction.propose | POST | friction:write | Clusters recent friction, deduplicates against open issues, and files the top proposals as labeled issues. |
/api/repo.list | GET, POST | repo:read | Every registered repo, including cold entries; resident state, ref, sha, last refresh, and disk gauge. |
/api/repo.workload | GET, POST | repo:read | Read one resident's workload count, owner records and missing evidence. Does not change or release work. |
/api/repo.onboard | POST | repo:write | Register a repo, optionally without a resident (--no-resident); resident provisioning is billable and admin-gated. |
/api/repo.offboard | POST | repo:write | Remove a repo registration and tear down its resident when present (admin-gated; --dry-run plans only). |
/api/repo.reconfigure | POST | repo:write | Change a registered repo's branch, commands or warm resident count (admin-gated; additional warm capacity is billable). |
/api/repo.rebuild | POST | repo:write | Rebuild the container and snapshot to repair provisioning; keep bindings and operation records. --full also clears all resident state and backups for a clean start or stale hold. Both discard container files and keep repo settings. --dry-run plans only (admin-gated). |
/api/repo.test | POST | repo:exec | Executes the repo's onboarded test command with zero model turns (needs coding-agent access; the ref must be a plausible branch). |
/api/repo.build | POST | repo:exec | Executes the repo's onboarded build command with zero model turns (needs coding-agent access; the ref must be a plausible branch). |
/api/memory.list | GET, POST | memory:read | Your own memory records and the shared org / repo / channel records, with ids — what influences your runs. |
/api/memory.forget | POST | memory:write | Soft-delete one memory record so it no longer influences any run (yours freely; shared org/repo/channel records need repo-management rights). |
/api/memory.sweep | POST | memory:write | Retire the stored status records the write gate rejects today (soft delete, per scope; yours freely, shared org/repo/channel scopes need repo-management rights); --dry-run lists the marked ids and changes nothing. |
/api/mcp.list | GET, POST | mcp:read | External MCP servers your runs in this channel can use — org-wide, this channel's, and your own — with state and agents; never a credential. --all (admins): every tier. |
/api/mcp.add | POST | mcp:write | Register an external MCP server for yourself, this channel, or the org — auth is detected from the server; sign-in or a token happens on a one-time link, never in chat. |
/api/mcp.connect | POST | mcp:write | A fresh one-time link to sign in to an OAuth server or enter (or replace) a bearer server's token — only you can complete it; it expires in 10 minutes. |
/api/mcp.show | GET, POST | mcp:read | One MCP server's entry plus a live probe of the tools it offers (names, read-only flags); never a credential. |
/api/mcp.remove | POST | mcp:write | Remove an MCP server you added and its stored credential (yours freely; channel ones need channel-config rights, org-wide ones admin rights). |
/api/mcp.promote | POST | mcp:write | Promote a person's MCP server into the org tier (admins): the same name, URL and auth, added by you; a bearer/oauth server gets a fresh org connect link for you to complete — the person's credential is never copied. |
/api/schedule.list | GET, POST | schedule:read | Every scheduled job (cron, UTC), which Worker fires it, its next firing, and what its last firing did. |
/api/deploy.plan | GET, POST | deploy:read | The production deploy plan: checks, Worker order, preflight handling — computed, nothing executed. With --affected, also which Workers this tree actually needs deployed and why. |
/api/delivery.report | GET, POST | delivery:read | Delivery indicators per week and per unit — issue-to-merge time, first-pass CI, review rounds, findings and the share resolved with no human edit — from the repository's snapshot of GitHub's facts (--fresh reads GitHub now) and the run history; nothing written. |
/api/costs.by | GET, POST | costs:read | What the runs cost by user, thread, channel, agent or model over the range — LLM from their tokens through the price table, cloud allocated by run wall-clock — the costs page's tabs as text or JSON, from the snapshot; nothing written. |
/api/costs.snapshot | POST | costs:write | Take the costs snapshot now: read both billing sources and the run history once over the page's widest range, store the result, and serve it to every reader of the costs page from then on. |
/api/metrics.trend | GET, POST | metrics:read | The run trend from the metrics dataset: runs, failure rate, p50/p95 wall and dollars per day and per agent over the range, weighted for sampling as text or JSON; review dashboard tabs use metrics reviews; nothing written. |
/api/metrics.reviews | GET, POST | metrics:read | The review dashboard's report from the metrics dataset: review runs by requester and surface, time spent, reviews per pull request, verdicts and findings, and the system tiles (run success, warm starts, setup losses), weighted for sampling; with --compare, the same range whole weeks earlier; nothing written. |
/api/metrics.feedback | GET, POST | metrics:read | Find human feedback on native PR reviews or retrieve an exact retained reaction snapshot by run ID. Returns PR/review links, reviewed head, snapshot status and stable reaction/actor IDs for investigation. Stored reads only; no review, model run or GitHub write is started. |
/api/providers.check | GET, POST | providers:read | Read the provider's own endpoints for each aggregator model the configuration names and report where the resolved model card disagrees — supported parameters, context length, modalities — with the override that would pin each. |
/api/pulls.rebase | POST | pulls:write | Rebase the pipeline's open pull requests (or one named) onto their bases: git alone first — the repository's own merge drivers, rerere, an unchanged patch carries its approval — then one bounded fix round for a conflict git leaves; one line per pull request. |
/api/pulls.merge | POST | merge:write | Squash-merge one approved pull request at exactly its reviewed head, under your own name: refused when no review approves the head, when a review there requests changes, when a check at it is red, still running or none has reported yet, and for the release pull request, which is answered with its card — that merge stays a person's click. |
/api/pulls.enqueue | POST | merge:write | Put one approved pull request at exactly its reviewed head in the base branch's merge queue, recorded under your own name: refused when no review approves the head, when a review there requests changes, when a check at it is red, or when the head moves before enqueue, and for the release pull request, which is answered with its card — that merge stays a person's click; the queue runs the still-pending checks itself. |
/api/plane.show | GET, POST | runs:read | What is happening: every live and recently ended run, every tracked pull request and every ship unit, each with its owner and its health — the plane's table, as text or JSON; nothing written. |
/api/plane.stop | POST | runs:write | Stop a pipeline and end its live children in one move: it starts no more units, its run is sealed, and every live child in its unit threads is aborted — recorded on the pipeline's run and the unit threads. |
Screenshots
Residents index — every onboarded repo, its state, last activity; each row folds open to the runs on it and their worktrees:

Resident detail — one repo's mirror, warm checkout, and per-thread worktrees:

What's not on the dashboard yet
Personal settings (config set me, mcp add for yourself) use the signed Access account's own scope and current grants. Email matches and explicit identity links do not borrow Slack Settings ownership; links change future conversation projection only. A service token has no human me scope. The org scope for agent, model and effort (config set org) is not offered here; Installation shows the defaults in force. The Vue frontend renders from the protected JSON seed and reads a fresh seed for in-app navigation.