Skip to content
github-actions[bot] edited this page Aug 23, 2026 · 2 revisions

HTTP API

Codeman's HTTP and SSE API is a stable contract. Everything the dashboard does goes through it, so anything the dashboard can do, a script can do.

This page is the orientation. The complete specification, including every wait semantic and the SSE catalogue, is docs/api-reference.md.

What is stable

Covered by semantic versioning: endpoint paths under /api/v1, the response envelope, errorCode values, and SSE event names.

Not covered, and free to change in a patch release: on-disk state files, internal modules, and anything marked experimental. The full statement is in Versioning.

/api/v1/* is a versioned alias of /api/*. Prefer the versioned form in anything you intend to keep.

The envelope

{ "success": true, "data": { } }
{ "success": false, "error": "human readable", "errorCode": "NOT_FOUND" }

A few legacy GET handlers return bare bodies rather than the envelope, so a robust client reads body.data ?? body.

Branch on errorCode, which is stable. The HTTP status is reliable too:

errorCodeHTTPMeaning
INVALID_INPUT400Malformed request or failed validation.
UNAUTHORIZED401Authentication required or failed.
NOT_FOUND404No such resource.
SESSION_BUSY409The session is busy.
CONFLICT409Conflicts with current state.
ALREADY_EXISTS409Resource already exists.
OPERATION_FAILED422Well formed, could not be completed.
RATE_LIMITED429Too many requests.
INTERNAL_ERROR500Unexpected server error.

New error codes are non-breaking. Removing or renaming one is a major change.

A 401 is the bare string Unauthorized, not the envelope. Piping it into jq throws a parse error rather than showing the failure, so check the status first.

Authentication

With no password set, and the default loopback bind, there is none. With CODEMAN_PASSWORD set, use HTTP Basic or the session cookie:

curl -s -u admin:"$CODEMAN_PASSWORD""$API/api/sessions"

A missingOrigin header is allowed, so curl and CLI tools work unchanged. A present-but-foreign origin is rejected by the CSRF guard. On an HTTPS install with the self-signed certificate, add -k.

Endpoint map

Roughly 200 handlers across 24 route modules. By domain:

DomainHandlersCovers
System45Status, settings, search, digest, updates.
Sessions34Create, input, terminal, wait, kill.
Cases29Create, link, clone, remote and docker cases.
Files16Preview, edit, raw, attachments, path picker.
Orchestrator10Plans and phases.
Ralph9Loop control and configuration.
Cron9Jobs and run history.
Admin8Multi-user administration.
Plan8Plan orchestration.
Respawn7Respawn configuration and presets.
Webviews6Saved dashboards, plus the proxy.
Mux5tmux operations.
Push4Web push subscriptions.
Read My Mind4Intent profiles and prediction.
Scheduled4The legacy scheduled-run concept.
Approvals3The inbox and answering.
Teams, me, search, hooks, clipboard, telemetry, voice, ws1-2 each

Each route module documents its own endpoints in its file header.

Long-polling instead of polling

Three calls block until something happens, so an agent driving Codeman from a shell can wait rather than spin:

CallBlocks until
GET /api/v1/sessions/:id/waitOne of a set of lifecycle signals fires.
GET /api/v1/sessions/:id/wait-outputA literal string appears in the session's output.
POST /api/v1/sessions/:id/input + waitThe input is delivered and then a signal fires.

Three semantics that break callers who assume otherwise:

  1. A timeout is 200, not an error. It answers with wait.timedOut: true. Loop over short waits; a single long call gets cut by tunnels and proxies.
  2. Send-and-wait is not a POST followed by a wait. It registers the waiter before writing, which closes the window where a separate wait sees the session still idle from the previous turn and answers instantly about the wrong turn.
  3. Signals are edge triggered with no history. One that fires with no waiter registered is unobservable afterwards. Fan-outs must register their waits as they dispatch.

wait-output matches a literal substring, never a regex. That is deliberate: no regex means no catastrophic backtracking on attacker-influenced output.

Only claude sessions emit stop and blocked, because those come from Claude Code hooks. Shell and external CLI sessions accept idle, working, and exit.

SSE

GET /api/events is the live event stream. 156 event names, kept in sync between server and client with a test that fails on drift.

The heartbeat is a namedsse:heartbeat event rather than an SSE comment, because comments are invisible to EventSource by specification and a client could not observe them. That is what lets the browser detect a stream that has silently stopped delivering.

constes=newEventSource('/api/events');es.addEventListener('session:created',(e)=>console.log(JSON.parse(e.data)));

Quick examples

API="${CODEMAN_API_URL:-http://localhost:3000}"
curl -s "$API/api/status"| jq # whole-system snapshot
curl -s "$API/api/sessions"| jq '.data[].name'# live sessions
curl -s "$API/api/sessions/unified"| jq # live + historical, deduped
curl -s "$API/api/subagents"| jq # background agents
curl -s "$API/api/search?q=deploy"| jq # cross-session search

Limits

LimitDefault
Max sessions50
Max agent windows500
Max SSE clients100
Terminal buffer32 MB per session
Text payload1 MB
Wait timeout ceiling600 s, and the response tells you what was applied

Most are environment-overridable. See src/config/.

Read next

Clone this wiki locally