Response shape
JSON routes return JSON with an appropriate status code. Errors are usually:request failed response. Stack fields are redacted from JSON payloads.
Core routes
/api/desk returns the manifest-backed workspace snapshot.
/api/system returns the cached system metrics snapshot.
/api/pulse is the UI’s regular heartbeat. It includes system metrics, running
session ids, attention state, unread event count, LSP status, and channel
runtime state.
/api/up { dryRun? } starts every missing manifest session and, unless it is
a dry run, boots every explorer terminal whose shell is missing and whose
folder still exists. It answers { exitCode, error?, actions, explorerTerminals? };
explorerTerminals is { booted, skipped, errors } (ids; skipped are the
terminals whose folder is gone, errors are boot <id>: <reason> and make the
call fail with exitCode 1) and is absent on a dry run.
/api/explorer-terminals manages the shells opened from the editor’s file
tree. Every route answers { terminals: [...] }, each entry
{ sessionId, cwd, name, command?, createdAt, state } with state running
or missing. POST /api/explorer-terminals { cwd, name?, command? } needs an
existing absolute directory (~ expands). name (1-64 characters) is the
display name and the source of the term-... id; it defaults to the folder
name. command (up to 4096 characters) runs in that directory instead of the
login shell and is re-run on boot; blank means the login shell. The entry is
committed under the manifest lock, the shell is provisioned through the
terminal daemon, and the manifest is rolled back if provisioning fails; the
response also carries created.
/boot { sessionId } re-provisions a known entry; /delete { sessionId }
retires the shell first and keeps the entry if the daemon refuses.
/delete-under { cwd } takes an absolute folder (~ expands; it need not
exist any more) and retires every terminal whose folder is that folder or lies
below it, all at once, then removes the entries whose retire succeeded in one
manifest write. It answers 200 with { terminals, removed, errors }: removed
lists the ids it forgot, errors lists retire <id>: <reason> for the entries
it kept; a relative cwd is a 400 invalid-input.
/edit { sessionId, name, cwd, command? } rewrites the entry under the same
validation as create (the sessionId never changes; a blank or absent
command clears it; the name must be unique in the target folder among the
other terminals, else 409). When the shell is running and the folder or the
command changed, the edit retires it with reason operator-reboot and
provisions it again with the new values; a rename alone never touches the
daemon. If that respawn fails the route answers 500 but keeps the edited entry,
so the list shows it missing for a later boot. /restart { sessionId }
retires a known entry with reason operator-reboot and provisions it again;
it refuses with 400 when the folder no longer exists and answers 500 with the
daemon’s error when the retire or the provision is refused. A malformed body is
invalid-input (400) and an unknown id is not-found (404).
Session and layout mutation routes
Desk uses API routes for UI mutations such as:- add, edit, delete, move, reorder, and restart sessions
- add, edit, delete, move, and reorder groups
- add, edit, delete, move, and reorder projects
- update group layout and persisted split sizes
Attention and agent events
/api/attention-read to mark events as read. Send { "clear": true } to the same route to clear the attention-event list.
Agent hooks and plugins post typed events to /api/agent-event. Desk uses those events for attention, resume capture, and channel-engine release signals.
Terminal routes
sessionId, row count, and offset. It proxies
the terminal daemon’s bounded emulator history and returns color-preserving
lines for the frozen scrollback viewer. Live resize, repaint, input, and
subscription traffic use the binary WebSocket rather than REST routes.
Terminal WebSockets
/ws/terminal is the only browser terminal path. One binary connection per
browser tab carries multiple terminal surfaces. Hidden surfaces retain their
channel under VISIBILITY false; the daemon rejects their input, ignores their
resize authority, and sends no live output deltas. Same-channel reveal normally
receives one bounded contiguous OUTPUT catch-up from its retained cursor. A
cursor gap or terminal revision change receives a current-screen SNAPSHOT
instead. Only actual surface removal or transport loss unsubscribes. Frames are
channel- and generation-fenced through the supervised terminal daemon to the
session’s Moor holder.
Filesystem routes
Filesystem routes live under/api/fs.
They include:
- home/root discovery
- notes root and notes state
- path validation
- directory listing
- file read and raw media read
- download: one file as an attachment, or several files / a folder streamed as a
.zip(GET /api/fs/download;GET /api/fs/download-planis the validating pre-flight the explorer calls first, answering the archive name, byte total, entry count and how many unreadable entries were skipped). Archives are uncompressed (STORE) with per-entry data descriptors and keep empty folders; readers that scan strictly front-to-back without the central directory (Java’sZipInputStream,funzip) cannot open them, everything that reads the central directory can. Selections beyond 65,535 members or 4 GiB are refused up front (no ZIP64). - filename and content search
- write, create, rename, copy, and delete operations
- LSP-aware preview/apply flows for create, rename, and delete
Git routes
Git routes live under/api/git.
They cover:
- repository discovery
- status and status maps
- log/history
- line diffs
- worktree, index, commit, range, and branch diffs
- stage and unstage
- discard
- commit and amend
- fetch, pull, push, and publish
- branch checkout, create, and delete
- worktree removal
- commit revert
- GitHub repository and PR context
- open-on-GitHub URL resolution
git and gh commands and reports failures through JSON errors.
GitHub Projects routes
GitHub Projects routes live under/api/projects.
They cover:
- auth/scope checks
- project listing
- board data loading
- item detail loading
- owner listing
- field value updates
- item position changes
- adding items by URL
- draft item creation, edit, and conversion
- archive, unarchive, and delete
- status update create/delete
- issue and pull request edits/comments/state changes
- project create/edit/link-repo operations
gh api graphql, gh issue, and gh pr.
Channels routes
Channels routes live under/api/channels.
They cover:
- channel state
- engine diagnostics and actions
- channel listing and message windows
- threads
- reactions
- saved views
- paused sessions
- search
- featured messages
- uploads and file serving
- activity events
- export
- message create, edit, and delete
- member add and remove
- share-to-channel
- queue control
DESK_API is reachable, and falls back to direct markdown append for some post flows when the server is unavailable.
Usage routes
/api/usage/snapshot lists every configured profile and the ambient:claude
and ambient:codex entries. Each entry has profileId, kind, provider,
label, support (supported, unsupported with a reason, or unknown
before the first check), an opaque quotaScope when the provider confirmed the
account identity, and lastCheck. account and usage are independent
observations: value, observedAt, staleAfter, attemptedAt, result
(ok or a code such as rate-limited) and freshness (never, fresh or
stale). An account value is available, unavailable with a reason
(not-logged-in, expired, rejected, access-denied) or unknown. A usage
value lists limits (id, scope, usedPercent, windowSeconds,
resetsAt, allowed), credits in their provider unit, plan and partial.
Unknown durations, reset times and balances are null, never zero. Two
profiles with the same quotaScope share one quota. The snapshot also carries
revision, configurationRevision, generatedAt and the collector state.
/api/usage/history returns 10-minute UTC slices of one profile, oldest first,
for the last 90 days by default. from and to are RFC 3339 times; limit is
1–1000 (default 200); nextCursor continues the page. Each slice has its
interval, instance (a new one after the id is reused), series (unknown or
the quota scope), check counts, authorizationFailures and the same
observations as the snapshot, with freshness judged at the end of the interval.
Errors keep their codes: invalid-input (400), unknown-profile (404),
payload-too-large (413), usage-incompatible or unsupported-version (502),
and uninitialized, service-unavailable or a storage code (503). A failed
read is never reported as an empty snapshot.
LSP routes and sockets
Plugins
Plugins can add middleware, routes, and WebSocket upgrade guards for local embedders and downstream builds. See Security and plugin model before adding runtime extensions.Next steps
- Read Security and plugin model before adding runtime plugins.
- Read Architecture to understand which runtime component owns each API surface.
- Use Operations for operator-facing health and control routes.