Routes
Every Deck API route — what it takes, what it does, and which ones need the bearer token.
Deck API routes
No auth:
| Route | Returns |
|---|---|
GET /v1/health | {ok, app, docs, home, panes, permissions:{screenRecording, accessibility, fullDisk}} |
GET /agents.md | this file, from the running deck |
GET /cli/mandeck | the CLI (bash), ready to save and chmod +x |
GET /cli/mandeck-mcp.mjs | the stdio MCP server (Node, zero deps) |
GET / or /phone | the phone PWA (HTML) |
Everything else requires Authorization: Bearer <cli-token>:
| Route | Body / query | Does |
|---|---|---|
GET /v1/sessions | — | every 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/focus | — | brings the pane to the human's screen |
DELETE /v1/sessions/:id | ?force=1 | closes 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/jobs | — | tasked-spawn jobs, newest first |
GET /v1/jobs/:id | — | one 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/agents | — | installed 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/workspaces | — | tabs: {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/projects | — | recent project folders |
GET /v1/accounts | — | provider 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.