HTTP endpoints
The server is a single Hono app: one process serves the SPA, the tRPC API, three WebSocket surfaces, and a handful of plain REST endpoints for uploads, auth, and integrations. Every surface (web, desktop, mobile, extension) is a thin client over this same API. See Architecture overview for how requests flow into the command model.
Authentication model
Section titled “Authentication model”Unclaimed instances (zero ops.users rows) resolve every request to a
legacy full-access principal, so nothing below is gated. Once an instance is
claimed (first successful login), anonymous requests get 401 on
every tRPC procedure except health.ping, plus explicit guards on /files,
/blobs, /mail/proxy-image, and /oauth/*. WebSocket upgrades on /ws,
/pty, and /meetings/audio are rejected before the handshake completes.
See Identity for the full model.
Core surface
Section titled “Core surface”| Route | Method | Access | Purpose |
|---|---|---|---|
/ |
GET | public | Serves the SPA (apps/web/dist). |
/health |
GET | public | Liveness probe: {ok: true}. |
/trpc/* |
ALL | authenticated | The tRPC 11 API. Every read/write model in the product (kb, mail, tasks, agents, and so on) rides through here. |
/ws |
WS upgrade | authenticated | The topic-bus WebSocket: outbox-derived live updates (pages, mail, run trees, notifications). See Outbox and dispatcher. |
/pty |
WS upgrade | admin-only | Terminal WebSocket: attaches to a PTY session on the server host. Rejected pre-handshake for non-admins. |
Files and blobs
Section titled “Files and blobs”POST /files multipart/form-data
Uploads a file: the body lands in the content-addressed blob store
(blobs/ab/cd/<sha256>) before a kb.files row commits. Returns {id, sha256, name, mime, size}. Authenticated.
GET /files/:id/content path param: id
Streams a file’s content by its kb.files row id. Readable by the file’s
owner or through any readable page that references it (kb.file_refs); an
unreadable row 404s exactly like a missing one, no existence probe.
GET /blobs/:sha256 path param: sha256
Streams a blob directly by content hash. Readable only if some readable
(or unowned) kb.files row carries that hash. Immutable: served with a
one-year cache-control: private, immutable header.
GET /provider-sync query: none
Status of connected provider mirrors (Google Drive, Dropbox, SharePoint) visible to the caller. Authenticated.
POST /provider-sync/:provider path param: provider (google-drive | dropbox | sharepoint), query: account?
Manually pulls one connector. Admin-only.
POST /files/:id/provider multipart/form-data
Writes a local file’s content back upstream to its connected provider (CAS
write-back, conflict-checked via If-Match/Dropbox update rev).
GET /mail/proxy-image query: url
Server-side image proxy for click-to-load remote images in mail bodies.
HTTPS-only, private hosts refused (SSRF guard), no credentials forwarded,
5s timeout, 10 MB cap, image/* content types only. Authenticated.
Identity and pairing
Section titled “Identity and pairing”| Route | Method | Access | Purpose |
|---|---|---|---|
/auth/google/start |
GET | public | Redirects to Google OAuth (openid email profile); accepts ?invite=<code>. 501 when Google login isn’t configured. |
/auth/google/callback |
GET | public | Google OAuth callback: sets the subspace_session HttpOnly cookie. |
/auth/dev-login |
POST | public, PROVIDERS=fake only |
{email} logs in (or claims the instance) without Google. The e2e/sandbox login seam; 403 outside PROVIDERS=fake. |
/auth/logout |
POST | authenticated | Destroys the session and clears the cookie. |
/auth/me |
GET | public | {user, mode, devLogin, googleLogin}. Open by design: the login page needs this pre-auth. |
/pair/claim |
POST | public | {code, name, kind?} claims a one-shot, 5-minute device pairing code minted from Settings → Devices and returns a bearer device token exactly once. See Device pairing. |
GET /oauth/gmail/start query: email?
Redirects into the Gmail OAuth consent flow for the signed-in user. 501
when no Gmail provider is configured (for example under PROVIDERS=fake).
Authenticated.
GET /oauth/gmail/callback query: code, state
Gmail OAuth callback: exchanges the code, stamps the connecting user as the
account owner, redirects to /. Authenticated.
GitOps
Section titled “GitOps”POST /gitops/webhook body: forge webhook payload
Public endpoint a forge push hook calls to trigger an immediate sync
instead of waiting for the poll interval. Verified constant-time as a
GitHub/Gitea X-Hub-Signature-256 HMAC or a GitLab X-Gitlab-Token
header against the configured webhook secret; no secret configured means
403 for everyone. The sync runs detached (202 {queued: true}) and
webhook bursts coalesce on an advisory lease. See
GitOps.
Meetings
Section titled “Meetings”WS /meetings/audio WebSocket upgrade
Streams live meeting audio into the speech-to-text pipeline. Rejected pre-handshake when unauthenticated. See Meetings.
Loopback-only surfaces
Section titled “Loopback-only surfaces”These routes are never exposed beyond the host process boundary; they exist for internal process-to-process calls (the worker role talking to the web role, or the workflow executor talking back to the server that spawned it).
| Route | Purpose |
|---|---|
/internal/broker/handshake |
Protocol version + readiness check between the web and worker roles. |
/internal/broker/available |
Reports whether a named capability is grantable, without acquiring it. |
/internal/broker/rpc |
Coarse capability RPCs (mail send, IM send) proxied from the worker to the web plane, which holds the credentials. |
/internal/broker/stdio-env |
Returns a stdio MCP server’s declared, resolved environment at spawn time only (the explicit scoped exception to “never ambient”). |
/internal/broker/anthropic/v1/*, /internal/broker/openai/v1/* |
LLM proxy: injects the provider API key host-side so it never reaches the worker’s environment. |
/internal/broker/mcp/:server, /internal/broker/mcp/:server/* |
HTTP MCP proxy with headers injected host-side. |
/internal/broker/egress |
General HTTP egress boundary for platform (mlops) API calls, credentials injected host-side. |
/.well-known/workflow/v1/{flow,step} |
The Workflow DevKit executor’s callback surface: graphile-worker POSTs durable step results back to this process here. See Agent engine. |