Skip to content

HTTP API reference

The daemon listens on http://127.0.0.1:8080 by default. JSON errors generally use { "error": "message" }. The router in axocoatl-server/src/lib.rs and handler Serde types remain authoritative; there is not yet a generated OpenAPI document.

The docs content check compares every literal router path with this page and the WebSocket reference.

Workbench and control-router routes other than /health, /health/ready, and /health/live pass through configured authentication. The dedicated Preview virtual Host is intercepted before that router and is constrained separately by its exact Session/port Host and browser-origin boundary. Send one of:

x-api-key: <key>
Authorization: Bearer <token>

An empty server.cors_origins allows same-origin workbench requests only. Browser writes and WebSockets with an opaque Origin: null are always rejected; other cross-origin browser writes require an explicitly allowed origin. In the default unauthenticated loopback mode, an unknown Host is rejected independently to prevent DNS rebinding. Origin-less CLI clients remain supported. Cross-origin preflight for the control router advertises HEAD, GET, POST, PUT, PATCH, and DELETE. Control-router rate limiting is disabled by default and applies per client IP when enabled; Preview traffic stays outside that middleware.

Preview applications use a separate virtual origin shaped like <session>-p<port>.localhost:<listener-port>. That Host is intercepted before the workbench router, so no /api, /ws, app-shell, or static-asset path can fall through to the control origin. Each Session and logical port has its own browser storage/cookie origin. Modern browsers can block ordinary app cookies while that origin is embedded; the workbench’s Open full preview action opens the same virtual URL top-level with noopener, where host-only cookies work without becoming shared sibling cookies. A browser GET or HEAD for the exact / workbench document on a loopback IP literal redirects to localhost on the same listener and preserves the query. API, health, and asset URLs do not redirect.

MethodPathPurpose
GET/Embedded Session-first workbench; the only interactive browser destination
GET/ui/{*file}Embedded UI modules and styles
GET/vendor/{*file}Vendored Monaco, xterm, highlighting, fonts, and icons
GET/lattice/{file}Lattice web-component assets
GET/brand/{file}Canonical brand assets
GET/.axocoatl/preview-picker.js on a Preview HostPicker bridge for the dedicated Preview origin; communication is limited to source-and-origin-validated postMessage events
GET/axo-tap.jsCompatibility picker asset for the response-sandboxed legacy proxy
GET/healthOverall status and Agent count
GET/health/ready200 when at least one Agent is spawned, otherwise 503
GET/health/liveLiveness probe
MethodPathRequest / behavior
GET/api/llm-healthOllama-only reachability and missing-model summary
GET/api/llm/models?agent=<id>Live Ollama list; curated hosted lists; current Agent model is always included
GET/api/agentsConfig projection for every Agent; live status is separate
GET/api/agents/{agent_id}/statusCurrent actor status
POST/api/agents/{agent_id}/execute{input, system_override?, model_override?, stateless?}; waits for output and returns input/output/reasoning/total tokens plus token_usage_known
POST/api/agents/{agent_id}/restartRestart from current in-memory config and latest checkpoint
PATCH/api/agents/{agent_id}Temporary in-memory name/model/prompt/dependency/budget patch; defaults to restart
GET/api/tokens/reportPer-Agent and total runtime token counts

An execute override implies stateless execution. Agent PATCH does not update YAML. /api/agents does not make one status request per Agent; consumers that need liveness must call the status route. Stateless execution performs one provider inference, advertises no tools, and does not enter a tool loop. An execution error carries the same token fields. A false known flag marks the numeric value as a known subtotal rather than an exact total.

MCP, configured Skills, and event projections

Section titled “MCP, configured Skills, and event projections”
MethodPathRequest / behavior
GET/api/mcp/catalogBundled catalog JSON
POST/api/mcp/install{slug, values?, name?}; connect in the live registry only
GET/api/mcp/serversConnected server name, transport, tool count
POST/api/mcp/servers/{name}Reconnect cached live transport
DELETE/api/mcp/servers/{name}Remove from live registry
GET/api/mcp/toolsDiscovered tool name, server, and description
GET/api/mcp/permissionsDurable saved permission decisions
DELETE/api/mcp/permissions?server=…&agent_id=…&tool=…Revoke matching decision; Agent/tool are optional
GET/api/skillsConfigured YAML Skills
POST/api/skills/{id}/firePublish every event in the Skill’s emits list
GET/api/events/recentIn-memory recent typed-event log

Catalog connection changes are not persisted to YAML and do not rebuild already-created Agent tool executors. Approval decisions use the WebSocket command described in the next reference.

Legacy workflow, schedule, and proactive projections

Section titled “Legacy workflow, schedule, and proactive projections”
MethodPathRequest / behavior
GET/api/workflowsManual canonical Automations projected as workflows
POST/api/workflows/{workflow_id}/execute{input}; execute compatibility workflow and wait; handled step failures and structural failures return a measured error
GET/api/schedulesSchedule-triggered Automation projection and observations
PATCH/api/schedules/{id}{enabled?, every?, input?} on the canonical record
POST/api/schedules/{id}/runRun the schedule immediately and wait; success and failure retain completeness-aware usage
GET/api/proactiveEvent-, Skill-, and legacy proactive schedule projection

These are compatibility views. Full Automation records use the canonical API below.

MethodPathRequest / behavior
GET/api/workspacesEvery persisted named Workspace, including one with no Sessions
POST/api/workspacesRegister {path, name?}; canonical path is unique and an omitted name preserves an existing custom label
GET/api/workspaces/{id}One Workspace
PATCH/api/workspaces/{id}Rename with {name}; the filesystem path does not change
GET/api/workspaces/{id}/sessionsEvery Session owned by the Workspace, including closed records
POST/api/workspaces/{id}/sessionsCreate from {name, mode, enabled_skills?, exposed_ports?, image?, setup_command?, setup_approved?, setup_reviewed?} under the Workspace’s fixed path
GET/api/sessionsEvery persisted Session, including closed records
POST/api/sessionsCompatibility create from {name, working_dir, mode, enabled_skills?, exposed_ports?, image?, setup_command?, setup_approved?, setup_reviewed?}; finds or creates the matching Workspace
PATCH/api/sessions/{id}Rename with {name}
DELETE/api/sessions/{id}?force=falseSoft-close; force=true deletes the Session record
PUT/api/sessions/{id}/environmentReview/change {image, setup_command, setup_approved, setup_reviewed:true} and synchronously prepare that exact generation
POST/api/sessions/{id}/environment/rebuildRemove the old runtime and reproduce the persisted reviewed environment plan
POST/api/sessions/{id}/environment/confirm-runtime-cleanupHigh-friction E2B recovery acknowledgment. Send exactly one target: {runtime_id, confirmed:true} after manually deleting that retained runtime, or {creation_token, confirmed_all_matching_sandboxes_deleted:true} after manually deleting every sandbox whose metadata contains axocoatl_creation_token=<exact token>
POST/api/sessions/{id}/reopenExplicitly reactivate a closed Session before any live runtime can be resumed or reconstructed
POST/api/sessions/{id}/execute{input} synchronous compatibility execution with completeness-aware success/error usage; other execute override fields are ignored here
GET/api/sessions/{id}/messagesOlder checkpoint-backed transcript projection
GET/api/sessions/{id}/turnsCanonical visible durable turns
GET/api/sessions/{id}/active-turnExact live run/turn ownership for Stop, or null
GET/api/sessions/{id}/turns/{turn_id}One canonical turn owned by this Session
GET/api/session-turns/search?q=…&session_id=…Literal case-insensitive search; omit Session ID for all
GET/api/sessions/{id}/export?format=markdownMarkdown by default; accepts markdown, md, or json
POST/api/sessions/{id}/rewind{keep_through_turn_id?}; legacy {keep} is raw message count

Workspace paths and Session working_dir values are canonical absolute paths. Every Session response carries its durable workspace_id; working_dir remains for execution and compatibility clients.

environment.state is unprepared, awaiting_approval, preparing, ready, or failed. A setup command is executable only when the durable Session record binds setup_approved:true to that exact text. A reviewed API request can grant that approval; the operator’s visible devcontainer default described below can grant it only while the Session remains unreviewed. Clearing the command with setup_reviewed:true is an explicit no-setup decision. Expected runtime/setup failures return the updated Session in failed state so clients can render retained evidence; malformed consent is 400, and an active turn or Attempt ownership conflict is 409. File, task, Git, Preview, turn, and Ways execution fail closed until the environment is Ready.

Live-checkout routes return 404 for a missing Session and 409 for every existing non-Ready environment. This applies to Session file tree/read/write, tasks and terminal attach, Git, Preview, HTTP execute, and Ways routes that start work or inspect a live checkout. Durable History and attachments, environment review/rebuild, completed Attempt results and cost, and the exact Keep/Discard recovery actions remain available so a failed Session can still be understood or resolved.

GET /api/fs/project?path=… includes runtime capability metadata. Under E2B, supports_session_image is false and template names the daemon-configured runtime; create or reconfigure rejects an explicit/devcontainer OCI image before provisioning. auto_approve_devcontainer_setup exposes the operator default for the exact devcontainer command. A browser/API request with setup_reviewed:true remains authoritative, including an unchecked veto; lockfile-detected commands are never covered by that policy.

Session mode is tagged JSON such as {"kind":"single_agent","agent_id":"coder"} or {"kind":"lattice"}. The directory must already exist when registering a new Workspace. The server has no configured folder allowlist.

Rewind is blocked during an active turn or unresolved Attempt set and currently requires an autonomous single-Agent Session. It changes the logical conversation head; it does not restore files or reverse effects.

MethodPathRequest / behavior
GET/api/sessions/{id}/attachmentsActive composer relations
POST/api/sessions/{id}/attachments?scope=this_turnMultipart file; scope is this_turn or session
GET/api/sessions/{id}/attachments/{reference_id}Metadata for one owned reference
PATCH/api/sessions/{id}/attachments/{reference_id}{scope} before a one-turn relation is consumed
DELETE/api/sessions/{id}/attachments/{reference_id}Remove future selection while retaining historical pins when used
GET/api/sessions/{id}/attachments/{reference_id}/contentDownload/preview bytes through owning relation

Declared images are capped at 10 MiB; other documents at 25 MiB. Extracted/OCR text is bounded. Attempts do not receive these references.

Session files, tasks, Terminal, and Preview

Section titled “Session files, tasks, Terminal, and Preview”
MethodPathRequest / behavior
GET/api/sessions/{id}/tree?path=…Lazy one-level directory listing within Session root
GET/api/sessions/{id}/file?path=…Read one file, capped at 512 KiB
POST/api/sessions/{id}/file?path=…Atomically overwrite existing resolved path with {content}
GET/api/sessions/{id}/tasksBackground task and terminal state
POST/api/sessions/{id}/tasks{command, interactive?, rows?, cols?}; uses or reconstructs the already-Ready sandbox and never prepares an unreviewed environment implicitly
GET (WS)/api/sessions/{id}/terminals/{tid}/wsRaw PTY output as binary; input binary/text; resize JSON
Any HTTP method/api/sessions/{id}/proxy/{port}Legacy response-sandboxed Preview proxy root
Any HTTP method/api/sessions/{id}/proxy/{port}/{*tail}Legacy response-sandboxed Preview proxy path

File resolution rejects paths outside the canonical Session root. Tree, read, and write execute through the Ready sandbox: local Podman operates on the read-write Workspace bind, while E2B operates on its remote clone. There is no host-filesystem fallback for a failed remote runtime or a container-local dependency volume. The write endpoint does not create missing parent directories. Terminal resize is {"kind":"resize","rows":30,"cols":100}.

The workbench opens the virtual Preview Host, not the legacy path. The Host boundary validates the exact Session and logical port, resolves that pair to the sandbox’s dynamically assigned loopback transport, and forwards normal app HTTP methods, query strings, request bodies, cookies, module/root assets, and WebSocket upgrades. HTML receives the source-and-origin-pinned picker bridge; non-HTML bodies stream without whole-response buffering. The upstream app sees the validated virtual authority while the TCP connection remains loopback. Sibling Sessions can therefore use the same logical port without sharing a host port, browser origin, host-only cookies, or control-plane access. Rejection of a parent-domain cookie such as Domain=localhost is browser policy; the current Chromium journey verifies it, while the proxy forwards application Set-Cookie headers unchanged.

MethodPathRequest / behavior
GET/api/sessions/{id}/git/statusBranch and staged/unstaged file state, including Last turn flags
GET/api/sessions/{id}/git/diff?path=…One path’s working diff
GET/api/sessions/{id}/git/branchesCurrent and available branches
GET/api/sessions/{id}/git/hunks?path=…&staged=falseParsed staged or unstaged hunks
POST/api/sessions/{id}/git/stage{paths}; empty list means all
POST/api/sessions/{id}/git/unstage{paths}; empty list means all
POST/api/sessions/{id}/git/hunk{path, index, stage}; stage defaults true
POST/api/sessions/{id}/git/hunk/discard{path, index}; destructive unstaged hunk discard
POST/api/sessions/{id}/git/discard{path?}; omitted path discards all unstaged changes
POST/api/sessions/{id}/git/checkout{"ref":"branch-or-ref"}
POST/api/sessions/{id}/git/commit{message?, stage_all?}; default commits existing index
PUT/api/sessions/{id}/check{check_command}; null/empty clears it

Discard and checkout can destroy or obscure working changes. API clients must provide their own confirmation UX.

One Session can own one unresolved set. Except for results, set-scoped calls use attempt_set_id; a stale ID returns 409 rather than touching a newer set.

MethodPathRequest / behavior
POST/api/sessions/{id}/variants{input, task?, n?, lanes?}; starts isolated local-Podman Attempts
GET/api/sessions/{id}/variants/status?attempt_set_id=…Changed-file status per Attempt
GET/api/sessions/{id}/variants/resultsDurable current set, outputs, verdicts, usage, and Judgment
GET/api/sessions/{id}/variants/trajectories?attempt_set_id=…&baseline=0Aligned Route comparison
GET/api/sessions/{id}/variants/diff?attempt_set_id=…&index=0&path=…One file before/after for one Attempt
POST/api/sessions/{id}/variants/verify{attempt_set_id, check}; run Checks after all Ways are terminal
POST/api/sessions/{id}/variants/judge{attempt_set_id, agent_id}; rank passing non-empty survivors with that configured Agent
GET/api/variants/probe?provider=…&models=a,bPreflight comma-separated models for structured tool calls; each result includes control_usage
POST/api/sessions/{id}/variants/plan{task, agent_id}; return plan, rendered instruction, and control_usage
GET/api/sessions/{id}/variants/cost?attempt_set_id=…&baseline=…&baseline_provider=…Actual and counterfactual price report with known flags
POST/api/sessions/{id}/variants/adopt{attempt_set_id, index}; resumable Keep without commit
POST/api/sessions/{id}/variants/discard{attempt_set_id}; cancel/join and remove without keeping

Attempts require an autonomous single-Agent Session, Git, and local Podman. A keepable candidate must be completed, non-empty, and passing. Keep phases are durable and retrying the same index resumes; discard is rejected after apply begins.

Remote missing prices remain unknown. total_usd is complete only when its known flag says so. Plan, model-check, and Judge calls are outside Attempt execution totals and use control_usage. Plan/Judge error responses also include that field so a failed or invalid Agent response does not hide a paid call; token_usage_known: false marks a known subtotal.

MethodPathRequest / behavior
GET/api/automationsList canonical records
POST/api/automationsCreate from a complete Automation JSON body
GET/api/automations/{id}Read one record
PUT/PATCH/api/automations/{id}Full replacement/upsert; path ID must equal body ID; PATCH is not partial
DELETE/api/automations/{id}Delete the record
POST/api/automations/{id}/run{input?, inputs?}; background start, returns {ok,id} where ID is the Automation ID
POST/api/automations/{id}/move{folder}; null moves to root
GET/api/automation-foldersList durable folders
POST/api/automation-folders{path, name?}
PATCH/api/automation-folders{old_path, new_path, new_name?}
DELETE/api/automation-folders?path=…&keep_contents=trueMove contents up by default or recursively delete
GET/api/toolsTool IDs available to Automation editor/runtime

Automation JSON contains id, name, optional description, nodes, edges, tagged trigger, enabled, and optional folder. Node kinds are agent, tool, conditional, map, subgraph, text_input, and interrupt. Triggers are manual, schedule, on_event, and on_skill.

MethodPathRequest / behavior
GET/api/automations/{id}/runsRun history for one Automation
GET/api/automations/{id}/runs/{run_id}One run with node state and diagnostic detail
POST/api/automations/{id}/runs/{run_id}/fork{input?}; fresh whole-graph rerun from start, not continuation
GET/api/interruptsEvery pending operator Interrupt
POST/api/automations/{id}/runs/{run_id}/nodes/{node_id}/resume{value?}; continue from top-level durable pause
POST/api/automations/{id}/runs/{run_id}/nodes/{node_id}/cancelWake with empty value and continue; does not cancel whole run

Observe run creation and lifecycle through run history and WebSocket events. The generic /run response does not return the new run ID.

These routes keep directoryless Chat integrations working. They do not provide a separate browser destination.

MethodPathRequest / behavior
GET/api/chat?q=…List or search compatibility Chats
POST/api/chat{agent_id, name?}
GET/api/chat/{id}Read one Chat
PATCH/api/chat/{id}{name?, starred?, system_override?, model_override?}; null clears overrides
DELETE/api/chat/{id}Delete Chat record
POST/api/chat/{id}/fork{truncate_at, replacement_content?, replacement_role?}
GET/api/chat/{id}/exportExport compatibility transcript
POST/api/chat/{id}/attachMultipart file upload and attach
PUT/api/chat/{id}/attachAttach existing FileStore entry with {file_id}
GET/api/chat/{id}/attach/{file_id}Read referenced bytes
PATCH/api/chat/{id}/attach/{file_id}{pinned}
DELETE/api/chat/{id}/attach/{file_id}Remove relation, not underlying FileStore entry

Streaming Chat turns use the retained WebSocket chat-turn command.

MethodPathRequest / behavior
GET/api/filesList/search shared entries using supported query fields
POST/api/filesMultipart file upload without attaching to a Chat
GET/api/files/{id}Read metadata
PATCH/api/files/{id}{name?, tags?}
DELETE/api/files/{id}Delete unless retained by historical Session context; removes Chat references
GET/api/files/{id}/bytesDownload stored bytes

The FileStore API is not the Session Files editor and does not expose the Workspace tree.

MethodPathRequest / behavior
GET/api/fs/list?path=…&hidden=falseList child directories for the Session folder picker
GET/api/fs/project?path=…Probe devcontainer metadata and ancestor AXOCOATL.md files
GET/.well-known/agent.jsonA2A discovery card
POST/a2a/tasksInbound A2A task dispatch to local receiver_id

The folder picker defaults to the daemon user’s home when path is omitted. It is a read endpoint but can enumerate any daemon-readable directory; protect the API accordingly. A2A is inbound only.

The unified live endpoint is GET /ws. Terminal PTYs use their separate Session route. See WebSocket reference →