session-viz backend v0 PLANE A — fleet telemetry (person-blind, closed schema, k-gated) GET /healthz GET /v1/reference/latest public, Ed25519-signed GET /v1/pubkey POST /v1/contrib svt_ token (contrib or collab), or legacy CONTRIB_TOKEN GET /admin bearer ADMIN_TOKEN (?token= also works and should not be used) PLANE B — collaboration (identity-bearing; cannot join plane A) GET /v1/live SSE stream with Accept: text/event-stream, else the 50 most recently reporting sessions, with `total` beside them GET /v1/live/stream the stream, under a name of its own POST /v1/live report finished turns session, collab svt_ token, or COLLAB_TOKEN answers {accepted, rejected, dropped, limit}; dropped turns were never stored and have to be sent again DEL /v1/live ?session= forget what was reported POST /v1/qpact/report a rendered /qpact page, keyed on (tenant, session) so a re-run corrects rather than duplicates. 8 MiB of document, 12 MiB of request; over either is a 413 with a sentence, never a dropped connection. Kept 90 days, then deleted GET /v1/qpact/reports this workspace's reports, newest first, with `total` GET /v1/qpact/report/:id one report's metadata; the document is only at /r/:id/doc DEL /v1/qpact/report/:id gone immediately; there is no tombstone GET /r, /r/:id, /r/:id/doc the console pages. A report carries prompt text, so there is NO link that opens one without a workspace credential POST /v1/mcp stateless MCP OAuth token actor comes from its creator; legacy COLLAB_TOKEN callers also send X-Actor GET /v1/vaults ?scope= POST /v1/vaults register/refresh a vault index GET /v1/vaults/resolve ?name=[[Link]]&from=vault_id GET /v1/vaults/dangling ?vault=vault_id GET /v1/tasks ?mine=1&state=&scope= POST /v1/tasks create POST /v1/tasks/:id/offered|accepted|done|draft GET /v1/events ?since=seq GET /v1/shares shares visible to your workspace GET /v1/share ?id= POST /v1/share publish; needs a tenant-scoped credential POST /v1/share/revoke {id} GET /v1/shares/live every live link that still opens, plus a tail of expired ones; `expired_not_listed` counts the lapsed rows left out POST /v1/share/live {sessionId, ttlMs} -> an unguessable /live/ URL POST /v1/share/live/revoke {id} takes effect on the next request the link makes BY LINK — no credential, the secret in the path is the credential GET /live/ one live session, redacted; expires, revocable GET /v1/live/shared/ the JSON that page polls OAUTH — how a browser or an MCP client actually signs in GET /.well-known/oauth-protected-resource[/v1/mcp] discovery GET /.well-known/oauth-authorization-server discovery GET|POST /authorize consent, then redirect with a code POST /v1/oauth/register dynamic client registration POST /v1/oauth/token code + verifier -> access token; authorization_code only GET /v1/token/introspect what a credential is and what scope it carries GET /v1/auth/providers which upstream identity providers are configured GET /v1/auth/oauth/:id/start|callback AUTH — passwordless, multi-tenant (mandantenfähig) POST /v1/auth/otp/start {email} POST /v1/auth/otp/verify {email, code} -> session token POST /v1/auth/passkey/challenge {kind:register|login, email} — email is login-only; register takes the session's and 401s without one POST /v1/auth/passkey/register {challengeId, credential} — the address comes from the session, so no caller can enrol onto someone else's account POST /v1/auth/passkey/login {challengeId, credential} GET /v1/auth/me Bearer session (cookie not accepted here) POST /v1/auth/logout GET /v1/me your account, tenant, role and onboarding state POST /v1/me/invite/accept {token} POST /v1/me/passkey/remove {id} TEAM — L1 tenant administration (admin role required) GET /v1/team members of your tenant POST /v1/team/invite {email, role} POST /v1/team/role {accountId, role} POST /v1/team/status {accountId, status} GET /v1/team/tokens svt_ tokens minted for this tenant POST /v1/team/token mint one; scope contrib or collab POST /v1/team/token/revoke {id} OPS — L0 operator, metadata only, every action audited GET /ops console (operator session) GET /v1/instructions?session= what is queued for one session (author or admin) POST /v1/instructions {sessionId, body} queue one (author or admin) POST /v1/instructions/claim {sessionId} collect and mark delivered; the session itself POST /v1/instructions/withdraw {id, sessionId} only while uncollected GET /v1/machines runners that have registered, and what may be run POST /v1/machines {name, repos} a runner saying it is listening GET /v1/runs the run ledger, newest first POST /v1/runs {machine, command, repo} queue one; commands.silent only POST /v1/runs/claim {machine} collect one job, marked started POST /v1/runs/finish {id, machine, outcome, detail, exitCode} GET /ops/funnel anonymous funnel, by day (operator session) GET /v1/ops/overview GET /v1/ops/audit POST /v1/ops/tenant/status {tenant, status, note} POST /v1/ops/tenant/delete {tenant, confirm, alsoFindings} POST /v1/ops/lookup {email}; POST so no address lands in a log GET /v1/ops/orphans rows written before plane B carried a tenant POST /v1/ops/orphans/purge {confirm:"orphans"} GET /v1/ops/mail delivery health + last 100 sends POST /v1/ops/mail/test send yourself a test Credentials: an svt_ token is per-tenant, so what it sends is attributed, and collab is accepted wherever contrib is because a plugin config holds one token. CONTRIB_TOKEN and COLLAB_TOKEN are the shared, identity-free legacy path, kept for single-tenant installs. They are named here because a self-hoster who reads only this page would otherwise stand up a service with no browser sign-in at all and conclude none exists.