# 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`.
