Download
Docs/Drive the deck/Routes

Routes

Every Deck API route — what it takes, what it does, and which ones need the bearer token.

Raw markdown

Deck API routes

No auth:

RouteReturns
GET /v1/health{ok, app, docs, home, panes, permissions:{screenRecording, accessibility, fullDisk}}
GET /agents.mdthis file, from the running deck
GET /cli/mandeckthe CLI (bash), ready to save and chmod +x
GET /cli/mandeck-mcp.mjsthe stdio MCP server (Node, zero deps)
GET / or /phonethe phone PWA (HTML)

Everything else requires Authorization: Bearer <cli-token>:

RouteBody / queryDoes
GET /v1/sessionsevery pane: id ("2.3"), pane (stable uuid), agent, account, cwd, project, title, kind, focused, needsYou, live
GET /v1/sessions/:id?lines=40 (max 200)that row + screen: ANSI-stripped tail of the terminal
GET /v1/sessions/:id/messages?limit=60 (max 200)structured transcript {messages:[{kind,text,at,tool?,status?}], transcript, screen, title?}; transcript:false means no reader for that agent — use screen
POST /v1/sessions/:id/message{text, submit?}types text; submit:true (default) presses a real Return. submit:false types without submitting — required before slash-command menus
POST /v1/sessions/:id/key{key}ctrl-c|interrupt, escape|esc, ctrl-d|eof, up, down, enter|return, or a digit 0-9. Digits answer TUI pickers (they must be pressed, not typed)
POST /v1/sessions/:id/focusbrings the pane to the human's screen
DELETE /v1/sessions/:id?force=1closes the pane. Refuses (403) a pane that was not spawned through this API — the human opened it — unless force=1; the reply carries the pane uuid. CLI close <id> --force, MCP deck_close {force:true}. After a deck restart every pane counts as human-opened
POST /v1/spawn{agent, cwd, prompt?, workspace?, account?, check_after?, spawner?, force?}new pane running that agent CLI. cwd is required (no default $HOME). Refuses a second live pane for the same agent+cwd unless force: true (409 {error:"already live", session, pane, job?}). workspace names a tab (created once, reused). A shell pane gets MANDECK_PANE/MANDECK_JOB exported into it but does not run prompt — it is recorded on the job only. Returns {ok, session, pane, job}. A spawn with a prompt or a named spawner opens a follow-up contract (see below) — check_after minutes (default 10), spawner = your own pane id. The job is a different object from that contract
GET /v1/jobstasked-spawn jobs, newest first
GET /v1/jobs/:idone job: {id, state, session, pane, agent, cwd, prompt, live, needsYou, createdAt, finishedAt?, report:{session, messages, screen}}. :id is the job uuid, or a pane/session address (latest job on that pane). Reading a job does not settle the follow-up contract
POST /v1/jobs/:id{state: "done"|"failed"}explicit finish. The only API write that marks a job terminal
GET /v1/agentsinstalled agent slugs (claude-code, codex, gemini, … and shell for a bare shell) — only offer what this Mac can run
GET /v1/events?since=<seq>append-only log of needs-you / spawned / needs-review / done / failed; poll with the last seq instead of diffing /v1/sessions. done/failed events carry job
GET /v1/workspacestabs: {index, title, active, panes}
GET /v1/history?limit=40&q= (max 200)past sessions by title/cwd/agent
POST /v1/resume{id}reopens a past session (ids from /v1/history) in a new pane, via the tool's own resume flow
GET /v1/projectsrecent project folders
GET /v1/accountsprovider accounts this deck can spend
POST /v1/accounts{label}ensure an account exists (idempotent)

Addressing. :id accepts the "2.3" address (workspace 2, pane 3), the pane's uuid, or a bare lifetime pane number. Addresses are positions and move when panes close; hold the pane uuid for anything longer than one command.

Waiting on a human? A pane with needsYou: true is blocked on input. Read its screen to see the question, answer with message or a digit via key.