127.0.0.1 on port 9300 by default. Change the port with --management-port.
This page documents the administrative routes. Phones, tablets, and the web app never receive
administrative access. They use the versioned /mobile/v1 namespace instead, documented in the
HQ Mobile API.
Overview
The HQ runs up to three HTTP listeners:
The mobile LAN listener forwards each
/mobile/v1 request to the management listener’s own
handlers in the same process. A request for any other path, including every route on this page,
never reaches them. The hosted relay works the same way: it forwards only /mobile/v1 traffic,
which the HQ replays against its management listener. Administrative routes are therefore
reachable only from the HQ machine itself.
Each route below is marked Loopback only or Also under /mobile/v1. A route that is also
under /mobile/v1 has the same handler at /mobile/v1 plus the path, authenticated with the mobile
bearer. Some mobile variants accept fewer fields; the differences are noted in each section.
Authentication
Send the management bearer in theAuthorization header on every request:
The two scopes never overlap. The management bearer is rejected under
/mobile/v1, and the
mobile bearer is rejected everywhere else. The HQ refuses to start when both tokens are set to the
same value. Relay mode requires both.
Three paths skip the bearer check:
A missing or wrong bearer returns HTTP
401:
Error shapes
Route groups return one of three error shapes. Each section below says which one it uses.
The management API sets no global body-size limit. The speech routes add their own limits.
Administrative routes send no CORS headers. CORS applies only to
/mobile/v1; see the
HQ Mobile API.
Route index
Conversation routes,
POST /mobile/v1/ws-ticket, and the chat WebSocket are covered on other
pages. See Conversations and chat.
Health and status
GET /health
Returns HQ status and the capabilities clients may use. No authentication is required. Also under/mobile/v1 as GET /mobile/v1/health.
conversation-sync-v1, chat-resume-v1, and conversation-control-v2 are always present.
speech-v1 appears only while a speech provider can both transcribe and speak. A failure while
checking the speech provider leaves it out rather than failing the request. Ignore capability
strings you don’t recognize.
GET /runtime/status
Returns a current operational snapshot. Loopback only.GET /health remains the lightweight liveness check. Runtime status reports process state; it
does not test provider availability or imply that queued work has finished. It contains no
credentials, conversation text, or pending-message contents.
POST /lifecycle/shutdown
Asks the HQ to shut down gracefully. Loopback only. Desktop calls this before it falls back to a signal. No body is required. The HQ replies first, then starts shutdown about 100 ms later so the response is not cut off:GET /identity
Returns the HQ’s stable ID and Ed25519 public key. Also under/mobile/v1, where native
clients call it with the mobile bearer.
GET /lan-tls
Returns the SHA-256 fingerprint of the HQ’s LAN HTTPS certificate. Loopback only. Desktop reads it to build the pairing QR code, which carries the fingerprint to the phone out of band. The route exists only when LAN TLS is configured; otherwise it returns404.
Agents
Create and manage agents. Agents are saved to~/.dash/gateway/agents.json. Every agent route
is also under /mobile/v1, where create and update accept fewer fields (see below).
Agent routes use the structured error shape. Responses never include providerApiKeys.
Agent object
status is registered, active, or disabled. config holds every configuration field you
set, except providerApiKeys.
Configuration fields
Any other key returns
400. Numeric caps must be positive integers, except
swarm.maxSteersPerWorker and subagents.maxDepth, which also accept 0. modelAliases maps a
bare alias (no /) to a model ID. Memory settings have their own routes; see Memory.
GET /agents
Lists every agent as an array of agent objects.POST /agents
Creates an agent and returns it with HTTP201. name, model, and systemPrompt are required.
Under /mobile/v1, only those three fields are accepted.
GET /agents/:id
Returns one agent object.PUT /agents/:id
Updates an agent’s configuration and returns the updated agent. Send only the fields to change; the body must not be empty.name cannot be changed. Under /mobile/v1, only model and
systemPrompt are accepted.
swarm or subagents restarts the agent’s warm runtimes so the next turn uses the new
settings. Other changes are read on the next turn.
DELETE /agents/:id
Deletes an agent and returns{"ok": true}. The HQ cancels the agent’s running turns and swarm
runs, removes it from messaging-channel routing, and deletes its browser sessions. Its
conversations are archived, not deleted: their messages stay readable through the
conversation routes.
POST /agents/:id/disable
Disables an agent without deleting it and returns{"ok": true}. The HQ stops admitting new turns
for it, cancels its running turns and swarm runs, and releases its runtimes.
POST /agents/:id/enable
Re-enables a disabled agent and returns{"ok": true}.
POST /agents/:agentId/conversation-title
Generates a short title for a conversation from its first message. Loopback only. The title is produced by one small completion on the agent’s own model. When projects exist, the same call also picks the active project the conversation best fits.project is null when no project fits. Errors use the plain shape: 400 for a missing text,
404 for an unknown agent, and 502 when the completion fails.
Memory
Read and edit what an agent remembers across conversations. See Memory. The list, read, and delete routes are also under/mobile/v1. Writing a memory and the settings
routes are loopback only.
Memory routes use the plain error shape, including under /mobile/v1:
GET /agents/:id/memory
Lists an agent’s memories without their bodies. It lists what is on disk even when memory is disabled for the agent.type is user, feedback, project, or reference. source records who wrote it: agent,
sweep, user, or import. size is the body length in characters.
GET /agents/:id/memory/:name
Returns one memory, including itscontent, with the same fields as the list minus size.
PUT /agents/:id/memory/:name
Creates or replaces a memory. The name comes from the path. Memories written here are recorded withsource: "user".
The name must be lowercase letters, digits, and hyphens, starting with a letter or digit, at most
64 characters.
action is created or updated.
DELETE /agents/:id/memory/:name
Deletes a memory and returns its name. This works while memory is disabled.GET /agents/:id/memory/config
Returns the agent’s memory settings. An agent that has never set them reads as enabled withsweep: "auto".
PATCH /agents/:id/memory/config
Changes memory settings and returns the merged result. Omitted fields keep their current values.
Changing
enabled restarts the agent’s warm runtimes so the next turn gains or loses the memory
tools. A sweep change applies on the next turn.
Skills
Manage the skills an agent can load. See Skills. OnlyGET /agents/:id/skills is
also under /mobile/v1, as a read-only view; every other skill route is loopback only.
Skill routes use the plain error shape:
GET /agents/:id/skills
Lists every skill available to the agent, from plugins and from the agent itself.source is managed, agent (learned by the agent), remote, or plugin. Under /mobile/v1,
each entry has only name, description, trigger, source, and content.
POST /agents/:id/skills
Creates a skill and returns HTTP201 with its name and file location.
GET /agents/:id/skills/:name
Returns one skill with the same fields as the list.PUT /agents/:id/skills/:name
Replaces a skill’s body and keeps its frontmatter. Send{"content": "..."}. Returns the skill’s
name and location.
DELETE /agents/:id/skills/:name
Removes a skill and returns{"name": "..."}. Plugin skills cannot be removed here.
POST /agents/:id/skills/install
Installs a skill from a git repository, URL, or local path. The skill is security-scanned before it is installed, and installation fails closed.verdict.verdict is safe or suspicious. A dangerous skill is rejected with 422.
GET /agents/:id/skills/config
Returns the agent’sskills configuration, or {} when none is set.
PATCH /agents/:id/skills/config
Merges fields into the agent’sskills configuration and returns the result.
GET /agents/:id/skills/:name/lessons
Returns the lesson book for a skill the agent learned. Returns404 when the skill has no lesson
book.
augments names other skills this book rides along with. retired holds lessons removed from
the active list, each with retiredAt and retiredReason (harmful or capacity).
DELETE /agents/:id/skills/:name/lessons/:lessonId
Retires one lesson and returns the updated lesson book. The lesson moves toretired; it is not
deleted, and a later review will not propose it again.
Sub-agents
Manage the helper agents an agent can start, and the sub-agents already running. Every route in this section is also under/mobile/v1.
Definitions. The five definition routes read and write the per-agent sub-agent types. On the
loopback API they use the plain error shape; under /mobile/v1 they use the structured shape. A
definition
:name must be lowercase letters, digits, and hyphens, starting with a letter or digit. Any other
name returns 400. An unknown agent returns 404.
GET /agents/:id/subagent-types
Returns the agent’s resolved roster: built-in types plus workspace, plugin, and per-agent definitions.location, disallowedTools, skills, maxTurns, isolation
(worktree), and shadowedBy. The system prompt is left out; read it from the definition route.
unknownAllowedTypes lists names in the agent’s subagents.allowedTypes that match no type,
which usually means a typo.
GET /agents/:id/subagent-definitions
Lists the definition files in the agent’s own writable directory. Workspace and plugin definitions appear only in the roster.GET /agents/:id/subagent-definitions/:name
Returns one definition’s raw Markdown.PUT /agents/:id/subagent-definitions/:name
Writes a definition and returns{"ok": true, "name": "reviewer"}. Send {"raw": "..."} with the
full Markdown file, frontmatter included.
The HQ validates the definition before writing it. The agent picks up the change on its next
model turn.
DELETE /agents/:id/subagent-definitions/:name
Deletes a definition file and returns{"ok": true, "name": "reviewer"}. Returns 404 when the
file doesn’t exist.
Running sub-agents. The conversation, stop, and resume routes use the structured error shape on
both mounts.
GET /conversations/:id/subagents
Lists the sub-agents started from a conversation. The list is read from saved state, so it still works after an HQ restart.status is running, waiting_input, done, failed, cancelled, interrupted, or
max_turns. report is scanned before it is returned. oneShot types, such as Explore and
Plan, cannot be resumed. An unknown conversation returns 404 not_found.
POST /subagents/:id/stop
Stops a sub-agent and every sub-agent it started. No body is required.404 not_found when :id isn’t a sub-agent, and 409 when the sub-agent has already
finished.
POST /subagents/:id/resume
Sends a sub-agent a follow-up message, exactly as its parent would with thesend_message tool.
mode is resumed when the message starts a new turn, or queued when it is delivered to a
running sub-agent. Returns 400 for an invalid body, 404 for an unknown sub-agent, and 409
when the sub-agent can’t take the message: a one-shot type, a spent steer cap, or no parent
conversation.
Swarm panel
Inspect and steer an agent’s swarm runs. A run is the set of sub-agents one parent turn started. Every route here is loopback only and uses the plain error shape; an unknown agent or run returns404.
GET /agents/:id/swarm/runs
Lists the agent’s recent runs, including runs from before the last HQ restart.startedAt and endedAt are epoch milliseconds.
GET /agents/:id/swarm/runs/:runId
Returns one run with the same fields plus aworkers array of per-worker snapshots.
POST /agents/:id/swarm/runs/:runId/workers/:workerId/cancel
Cancels one worker. Returns{"ok": true}, or 409 with {"ok": false, "reason": "..."} when the
worker can’t be cancelled. This also works for a background worker whose spawning turn has ended.
POST /agents/:id/swarm/runs/:runId/workers/:workerId/send
Sends one worker a message. Send{"message": "..."}; an empty message returns 400. Returns
{"ok": true}, or 409 with {"ok": false, "reason": "..."} when the worker can’t accept it.
POST /agents/:id/conversations/:conversationId/swarm/cancel
Cancels every unfinished worker in the conversation’s live swarm turn. Workers keep running after the chat stream ends, so this is how the stop button reaches them.cancelled is false when there was no live turn. Calling it again is safe.
Browser
Manage the browser agents use and the saved logins in their browser profiles. See Browser. Every route here is loopback only. Browser routes use the plain error shape. An invalid profile name returns400, a profile in use
by a running browser returns 409, and an unknown agent returns 404.
GET /browser/status
Returns which browser the HQ will launch, the managed-download progress, and the number of live browser sessions.POST /browser/install
Starts downloading the managed browser and returns HTTP202 immediately with the current
progress. Poll GET /browser/status to follow it. Calling it again while a download is running
returns the same 202 and does not start a second download. A failed download shows up as
install.state: "failed".
GET /agents/:id/browser/profiles
Lists the agent’s saved browser profiles.DELETE /agents/:id/browser/profiles/:name
Deletes a saved profile, including its cookies and logins, and returns{"name": "work"}.
Returns 409 while the profile is in use.
Channels
Connect and manage messaging apps such as Telegram and WhatsApp. See Messaging Apps. Channels are saved to~/.dash/gateway/channels.json. Every
route here is loopback only and uses the plain error shape.
Channel object
adapter is telegram or whatsapp. A routing condition is {"type": "default"},
{"type": "sender", "ids": [...]}, or {"type": "group", "ids": [...]}.
GET /channels
Lists every channel as an array of channel objects.POST /channels
Creates a channel, starts its adapter, and returns HTTP201 with {"ok": true}.
Store the channel’s credential first with
POST /credentials: a Telegram bot token under
channel:<name>:token, or WhatsApp auth JSON under channel:<name>:whatsapp-auth. Without it,
creation fails with 400.
GET /channels/:name
Returns one channel object. URL-encode the name.PUT /channels/:name
Updates a channel and returns the updated channel object. Send any ofrouting,
globalDenyList, and allowedUsers; omitted fields are unchanged. Each must be an array.
DELETE /channels/:name
Stops the channel’s adapter, removes the channel, and returns{"ok": true}.
Credentials
Store provider API keys and messaging tokens in the HQ’s encrypted store (~/.dash/gateway/credentials.enc). See Secrets. Every route here is
loopback only and uses the plain error shape.
Two key formats have special meaning:
GET /credentials
Lists stored credential keys. Values are never returned.POST /credentials
Sets one credential and returns HTTP201 with {"ok": true}. Both key and value are
required and must be nonempty.
DELETE /credentials/:key
Deletes a credential and returns{"ok": true}. URL-encode the key.
Phones and the web app manage provider API keys, and sign in with a Claude or ChatGPT
subscription, through the Mobile API’s AI providers routes.
Models
List the models agents can use. See AI Providers.GET /models is also under
/mobile/v1 as a read-only view; POST /models/refresh is loopback only.
The HQ fetches each configured provider’s model list, filters it through its bundled provider
catalogs, and caches the result for six hours. A provider that fails is retried after five
minutes, and its last good models are kept meanwhile. Setting or deleting a provider API key
clears the cache. With no provider keys at all, the response lists every catalog’s built-in
models with source: "bootstrap".
GET /models
On the loopback API,
?debug=true adds bootstrap (the catalogs’ built-in models), patterns,
providersConfigured, and providersAvailable. Under /mobile/v1, any query parameter returns
400 with the structured validation_failed error.
POST /models/refresh
Fetches the model list again, ignoring the cache, and returns the same shape asGET /models.
Speech
Dictation and read-aloud. Every speech route is also under/mobile/v1, with an identical
handler. Hands-free voice mode runs over the chat WebSocket instead; see
HQ Chat WebSocket.
Speech errors use the structured shape and pass the provider-level code through:
GET /speech/config
Returns the current speech configuration and each provider’s availability.reason: no_credential or no_provider_offers_realtime.
stt.language is present only when a language is pinned, and tts.speed only when set.
PATCH /speech/config
Merges a partial configuration, saves it, and returns the same shape asGET /speech/config.
Omitted keys keep their current values. An unknown key anywhere in the body returns 400.
Content-Length above 64 KiB returns 413 too_large before the body is read.
GET /speech/models
Lists the models the configured provider offers for one kind.POST /speech/transcriptions
Transcribes one recorded clip. Audio is sent inline as base64 JSON, not multipart.durationSeconds is present only when the provider reports it. A declared Content-Length above
12 MiB returns 413 too_large before the body is read.
POST /speech/speech
Synthesizes speech from text. Send{"text": "..."}, 1 to 4,000 characters.
audio/mpeg, except for a PCM-only model
(currently Google’s Gemini TTS), which returns a complete audio/wav file. An error raised before
the first audio chunk is still JSON, so accept audio/mpeg, audio/wav, and application/json.
A declared Content-Length above 64 KiB returns 413 too_large.
MCP servers
Connect Model Context Protocol servers whose tools agents can use. Every route here is loopback only and uses the plain error shape. A server’s tools are named<server>__<tool>. Add a server to an agent by listing its name in the agent’s mcpServers.
MCP server object
transport is one of:
status is connected, disconnected, reconnecting, error, or needs_reauth.
GET /runtime/mcp/servers
Lists every saved MCP server as an array of MCP server objects.POST /runtime/mcp/servers
Connects a new MCP server, saves it, and returns HTTP201.
GET /runtime/mcp/servers/:name
Returns one MCP server object.DELETE /runtime/mcp/servers/:name
Disconnects and removes a server, removes it from every agent’smcpServers, and returns
{"ok": true}.
POST /runtime/mcp/servers/:name/reconnect
Disconnects and reconnects a saved server.POST /runtime/mcp/servers/:name/reauthorize
Restarts sign-in for a server whose status isneeds_reauth. Returns
{"ok": true, "status": "..."}, 404 for an unknown server, or 500 when the server is in any
other state.
GET /runtime/mcp/allowlist
Returns the URL patterns that remote MCP servers must match, as an array of strings. An empty list allows every URL.stdio servers are not checked.
PUT /runtime/mcp/allowlist
Replaces the allowlist. Send a JSON array of strings;* matches any run of characters.
Plugins
Install and manage plugins. See Plugins. Every route here is loopback only and uses the plain error shape. Changes are saved first and then applied by reloading every plugin.GET /plugins
Lists every plugin, including disabled and failed ones, sorted by name.status is loaded, disabled, or error. activated lists the component kinds in use; noop
lists kinds present but not active, such as code components of an untrusted plugin. A built-in
plugin has builtin: true. A failed plugin has failure with phase (discovery, manifest, or
route), error, and failedAt.
POST /plugins/install
Fetches, scans, and installs a plugin, then reloads. The plugin is enabled but not trusted: its code components stay inactive until you trust it withPUT /plugins/:name.
201:
200 with {"ok": true, "installed": {...}, "note": "...", "error": "..."}, and the next reload
picks it up.
PUT /plugins/:name
Enables, disables, trusts, or untrusts a plugin, then reloads and returns its updated record.400 for a non-boolean field and 404 for an unknown plugin. If the reload fails, or the
plugin is missing afterward, it returns 409 with error, plugin, and note: the setting was
saved, but the running plugins are unchanged.
DELETE /plugins/:name
Removes a plugin’s saved settings and, for a plugin installed through this API, deletes its directory. Then reloads.path is present only when a directory was deleted. A built-in plugin returns 400; disable it
instead. An unknown plugin returns 404. If the reload fails after removal, the response is 409
with ok: true, removed: true, error, and note: the removal stuck.
POST /plugins/reload
Rediscovers plugins without changing any settings.500 with {"error": "..."}.
GET /runtime/plugins
Returns a compact view for model and credential pickers: providers contributed by plugins, and every plugin that isn’t disabled.Projects, issues and inbox
Track work in projects and issues. See Projects. Every route here is loopback only and uses the plain error shape. Wherever a path takes a project or issue:id,
you can pass its ID or its key, such as WEB or WEB-12. Body fields and query filters, such
as project_id, take IDs only.
Writes through this API are recorded as made by the local human user. Live updates are pushed over
the /projects/ws WebSocket; see HQ Chat WebSocket.
Project object
status is active, paused, completed, or cancelled.
Issue object
status is backlog, todo, in_progress, review, done, or cancelled. sub_status is
waiting_on_human, agent_working, blocked, or null. An issue with no project gets a TASK-
key.
GET /projects
Lists projects as an array. Filter with?status=; an invalid status returns 400.
POST /projects
Creates a project and returns it with HTTP201.
A rejected project, such as a duplicate key, returns
400.
GET /projects/:id
Returns a project plusissue_counts_by_status, a count for every issue status.
PATCH /projects/:id
Updates any ofname, description, status, and archived_at (a timestamp or null), and
returns the project. Other keys are ignored.
GET /projects/:id/issues
Lists a project’s issues as an array.GET /issues
Lists issues as an array. Every query parameter is an optional filter:POST /issues
Creates an issue and returns it with HTTP201.
GET /issues/:id
Returns an issue pluscomments, events, linked_sessions, and subtasks.
PATCH /issues/:id
Updates any oftitle, description, status, sub_status, assignee_user_id, project_id,
and completed_at, and returns the issue. Other keys are ignored. An invalid status or
sub_status returns 400.
DELETE /issues/:id
Deletes an issue and returns{"ok": true}.
POST /issues/:id/comments
Adds a comment and returns it with HTTP201. Send {"body": "..."}; an empty body returns 400.
PATCH /issues/:id/comments/:commentId
Edits a comment’s text and returns the comment. Send{"body": "..."}. An unknown comment returns
404.
DELETE /issues/:id/comments/:commentId
Soft-deletes a comment and returns{"ok": true, "issue_id": "..."}.
GET /issues/:id/events
Returns the issue’s history as an array. Each event hasid, issue_id, type, actor_type
(human, agent, or system), actor_id, data, and created_at.
GET /issues/:id/sessions
Lists the chat sessions linked to an issue.POST /issues/:id/sessions
Links a chat session to an issue and returns the link with HTTP201.
GET /inbox
Lists the issues waiting for the local user.issue and project are full objects, abbreviated here; project is null for a standalone
issue. reason is waiting_on_human or new_activity.
POST /inbox/:issue_id/mark-read
Marks an inbox item read and returns{"ok": true}.
Events
GET /events
Streams HQ changes as server-sent events. Also under/mobile/v1, which streams the same events. Clients use it to know when to refetch
cached data.
type, and its data is the JSON event:
: keepalive comment is sent every 30 seconds.
Ignore event types you don’t recognize.
GET /agents/:agentId/conversations/:conversationId/events
Replays a conversation’s durable chat events after a sequence number, so a client can catch up after a dropped WebSocket without resending the prompt. Also under/mobile/v1. Errors use
the structured shape.
{"entries": []}. Any query parameter other than a single
sinceSeq returns 400 validation_failed. An unknown agent, or a conversation that belongs to a
different agent, returns 404 not_found. See HQ Chat WebSocket for the
event payloads and how to resume a turn.
Conversations and chat
Conversations, messages, and chat are documented on their own pages:- HQ Mobile API covers conversation lists, messages, titles, deletion, and
POST /mobile/v1/ws-ticket. The conversation routes are also available unprefixed on this loopback API with the management bearer. - HQ Chat WebSocket covers
/ws/chat: sending messages, resumable turns, answers, cancellation, voice mode, and browser live view.
The loopback management API on port
9300 and the channel server on port 9200 use separate
management and chat credentials. Native clients never receive the management bearer: the pinned
mobile listener and the relay accept only the mobile bearer, on /mobile/v1 and /ws/chat.