HTTP API
The daemon owns all task state. The dashboard, the natamux command and the agents’ hooks all use this API.
Base URL and auth
Section titled “Base URL and auth”-
Base URL:
http://127.0.0.1:4317by default (NATAMUX_PORTchanges the port). When the daemon serves the dashboard (the desktop app, or Docker), the API is also under/apion the dashboard’s address. -
Send the token in a header on every request:
Terminal window curl -H "Authorization: Bearer $NATAMUX_TOKEN" http://127.0.0.1:4317/tasks -
GET /healthneeds no token and answers{"status":"ok"}. -
A wrong or missing token gets 401.
The agent token
Section titled “The agent token”Task terminals get a second token, the agent token, in NATAMUX_TOKEN. The daemon makes a new one at each start and never prints it or writes it to disk. Agents and the natamux command in a task terminal use it, so the history shows what an agent did.
With the agent token:
POST /tasks/:id/eventsrecordssource: agent, whateversourcethe request names.- Stage actions record
source: agent. - These answer 403
forbiddenand change nothing:POST /tasks/:id/eventswithreview.approved,stage.approved,stage.passed,stage.skippedorstate.overriddenPOST /tasks/:id/stagewithapprove,skiporpassPUT /tasks/:id/ci,PUT /tasks/:id/skip,PUT /tasks/:id/workflowPUT /repos/:id/settings
To do these from a script, use the daemon’s own token, not the one in a task terminal.
Main endpoints
Section titled “Main endpoints”| Method and path | Meaning |
|---|---|
GET /tasks?state=<state> |
List tasks |
POST /tasks |
Create a task (title is required) |
GET /tasks/:id |
One task |
DELETE /tasks/:id |
Delete the task, its notes and its history. A workspace is removed too. Answers 409 when that workspace has changes that are not committed, and the task stays |
PATCH /tasks/:id |
Change title, description, priority, labels or dependsOn |
GET /tasks/:id/events |
The event log of a task |
POST /tasks/:id/events |
Send an event { source, type, payload? }. Answers { task, accepted } |
POST /tasks/:id/stage |
A stage action { action, stage?, terminal?, to?, reason? }. Actions: approve, reopen, skip, pass, fail, report, start |
PUT /tasks/:id/ci |
Whether a stage waits for CI before it passes { stage, on }. Answers 400 when the stage does not offer it |
PUT /tasks/:id/skip |
Whether a stage not reached yet is skipped when the task reaches it { stage, on }. Answers 400 when marking the current stage or an earlier one or the context stage, and for any change on a task that is over |
PUT /tasks/:id/workflow |
Whether a stage runs as a workflow the next time it starts { stage, on }. Answers 400 when the stage does not offer it or the agent is not Claude Code |
POST /tasks/:id/workspace |
Create the worktree and branch. Body { base? } |
DELETE /tasks/:id/workspace?deleteBranch=true |
Remove the worktree |
POST /tasks/:id/agent |
Start an agent { agent, prompt?, base? } |
GET /tasks/:id/changes |
Files changed in the workspace. The answer is kept for 5 seconds; ?fresh=1 reads git now |
GET /tasks/:id/graph |
The history of the task branch next to the base branch. Kept and ?fresh=1 as for changes |
POST /tasks/:id/commit-message |
A commit message for the changes, by the task’s agent: { message, source, agent, reason? }. source is draft when the agent failed and the message is a simple one; reason says why. Answers 409 when nothing changed |
POST /workspaces |
Make a task with a worktree and optionally start an agent |
POST /hooks/:agent |
Where an agent’s hook posts. Always answers 200 {} for a known agent |
GET /repos |
Projects Natamux manages, and the daemon’s worktree folder: { repos, worktreesDir }. A project with its own worktree folder has worktreesDir |
POST /repos |
Add a project { path, name?, worktreesDir? }. worktreesDir is an absolute folder for the project’s worktrees; it is made when missing. Answers 400 when it is not absolute or cannot be made |
DELETE /repos/:id |
Forget a project. Any project can be removed. Answers 409 while it has workspaces |
GET /repos/:id/settings |
The settings of a project: settings, error, path, text (the text of .natamux.json, or null without a file) and version (none without a file) |
PUT /repos/:id/settings |
Save the settings to .natamux.json. Send the version you read in the header If-Match: <version>. Answers { settings, version }. Answers 409 conflict when the file changed since that version. Keys of the file that Natamux does not know are kept |
GET /repos/:id/skills |
The skills of every agent for the project: { skills, notices }. A skill is { name, description, origin, agents, path }, and origin is project, plugin or user. A notice says, for example, that a plugin is on but not installed |
The task is named in a hook by the X-Natamux-Task header. The X-Natamux-Terminal header names the terminal.
More endpoints
Section titled “More endpoints”Files, git, GitHub, terminals and templates.
| Method and path | Meaning |
|---|---|
GET /tasks/:id/files |
The stage files of a task: { files } |
GET /tasks/:id/files/:name |
One stage file, such as spec.md: { name, text }. Answers 404 when it does not exist |
PUT /tasks/:id/files/:name |
Replace a stage file { text }, for example to fix the spec before you approve it |
GET /tasks/:id/attachments |
The files attached to a task: { attachments } |
GET /tasks/:id/attachments/:name |
One attachment. Images come with their type, any other file downloads |
GET /tasks/:id/ended-shells |
The last screens of the shells of a done or cancelled task: { shells } |
GET /tasks/:id/changes/file?path=<file> |
The diff of one changed file |
GET /tasks/:id/changes/image?path=<file>&side=original|modified |
One side of a changed image |
GET /tasks/:id/tree?path=<folder> |
One folder of the worktree: { entries }. No path is the root. .git is never listed |
GET /tasks/:id/file?path=<file> |
One file of the worktree: { path, text, binary, tooLarge, version } |
PUT /tasks/:id/file?path=<file> |
Save a file that exists { text }, with the version you read in If-Match. Answers 409 when the file changed since |
POST /tasks/:id/agent/message |
Paste a comment into the agent’s terminal { text }. Answers 400 when no agent runs there |
POST /tasks/:id/commit |
Commit all changes { message } |
POST /tasks/:id/push |
Push the task branch |
POST /tasks/:id/pull-request |
Open the pull request: { task } |
POST /tasks/:id/pull-request/refresh |
Ask GitHub now for the state of the pull request, instead of waiting for the next check |
POST /tasks/:id/issue |
Open a GitHub issue for the task now |
PUT /tasks/:id/issue |
Link an issue that exists { provider, key, url } |
POST /tasks/:id/fan-out |
Run more tries of a task { count, start? }. count is 1 to 5 |
GET /tasks/:id/terminals |
The open shells of a task: { terminals, agents, lanes, programs, restoring } |
DELETE /tasks/:id/terminals/:terminal |
Close one shell. :terminal is its number |
POST /tasks/:id/terminal/ticket |
A one-time ticket for the terminal WebSocket |
GET /templates |
The saved task templates: { templates } |
POST /templates |
Save a template |
POST /templates/:id/tasks |
Make a task from a template |
GET /github/issues?repo=<id> |
The open issues of a project: { issues } |
POST /github/issues/import |
Import issues as draft tasks { numbers, repo? }. Up to 100 numbers |
GET /git/branches?repo=<id> |
The branches of a project, for Branch from |
GET /activity |
What the agent of each task does now, from its last hook |
GET /diffstats |
The size of the changes of every workspace |
Live updates and terminals
Section titled “Live updates and terminals”A browser cannot set headers on a WebSocket, so:
GET /events(WebSocket) streams live task changes, includingtask.deletedwhen a task is deleted. Pass the token as?token=<token>. Each message is JSON with akind. Besides the task messages it sends:repos.changedwhen a project is added or removed, so a dashboard can readGET /reposagain.activity.changedwith{ taskId, activity }when what an agent does changes.activityis the task’s whole activity by terminal, asGET /activitygives it;{}means none.shells.changedwith{ taskId }when a terminal of the task starts or ends, when the program in front of a terminal changes (checked when its output goes quiet), when an agent that comes back after a restart starts or is up, or when a hook changes the state of an agent that works on no stage, unless the lastGET /tasks/<id>/terminalsfound no agent in that terminal.workspace.dirtywith{ taskId }when files in the task’s workspace may have changed: after an agent hook for a tool that ran or for the end of a turn, when its terminals go quiet, after a commit, or after a save in the editor. Each comes 750 ms after a change. Up to 3 in a row come that soon; while changes go on after that, at most one per task every 10 s, and one more after the last change.resyncto one socket that missed messages because it read too slowly. The dashboard loads everything again.
- Terminals open with a one-time ticket. Call
POST /tasks/:id/terminal/ticketto get{ ticket }, then openGET /tasks/:id/terminal?ticket=...(WebSocket) within 30 seconds.
The full table
Section titled “The full table”A request with a bad field answers 400 with an error that names the field and the reason, such as which field is too long.
The daemon README lists every endpoint with its body and errors: packages/daemon/README.md.