Files
Codeman/docs/wiki/HTTP-API.md
T
Codeman maintainer bd286bf502 docs(wiki): catch the manual up to 1.29.0 and add the three run modes it never had
The wiki was written for seven run modes and never received Grok Build, DeepSeek
Harness or OMP. They now appear everywhere the others do: the modes table and
per-CLI notes, install commands, environment prefixes, the Quick Start table, the
requirements rows, the vocabulary, and every "seven modes" count.

The 1.27 to 1.29.0 changes land on the pages that own them: attaching a case to an
existing container, multi-case adoption and the copy-a-case picker (Docker Cases);
file reads over ssh in remote cases and what stays unavailable (Remote SSH Sessions,
Working With Files, Security); single-page app routing, frame recovery, localhost
links as tabs and the egress guard (Web Tabs); DeepSeek as the one non-Claude mode
with real stop/blocked signals and Approvals items, Codex's own work detection,
last-response, the model-endpoint routes and refreshed counts (HTTP API, Driving
From An Agent, Hooks, Notifications, Keeping Agents Running, Core Concepts);
Shift+drag, right-click copy, Auto Copy, the Ctrl+Z guard, font weight, the vertical
rail and its activity sort (Keyboard Shortcuts, Input And Voice, The Dashboard,
Settings Reference); the 600px phone cutoff, Codex shift arrows and iPhone Duo
(Mobile Guide); the Docker Compose route and its update rule (Installation, Running
As A Service); four new symptom entries and a "which CLIs" question (Troubleshooting,
FAQ).

Custom model endpoints are deliberately left to #430, which adds that page and edits
Agent CLIs, Settings Reference and the sidebar; these edits stay out of the regions
#430, #428 and #376 touch, and all three still merge cleanly on top.

Both READMEs: the web-tab menu entry is labelled "Add URL" in the UI, not
"Add dashboard".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-15 19:05:59 +02:00

8.8 KiB

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:

errorCode HTTP Meaning
INVALID_INPUT 400 Malformed request or failed validation.
UNAUTHORIZED 401 Authentication required or failed.
NOT_FOUND 404 No such resource.
SESSION_BUSY 409 The session is busy.
CONFLICT 409 Conflicts with current state.
ALREADY_EXISTS 409 Resource already exists.
OPERATION_FAILED 422 Well formed, could not be completed.
RATE_LIMITED 429 Too many requests.
INTERNAL_ERROR 500 Unexpected 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 missing Origin 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 235 handlers across 26 route modules. By domain:

Domain Handlers Covers
System 56 Status, settings, digest, updates, tunnel.
Sessions 34 Create, input, terminal, wait, last response, kill.
Cases 34 Create, link, clone, remote and docker cases.
Files 17 Preview, edit, raw, attachments, path picker.
Orchestrator 10 Plans and phases.
Ralph 9 Loop control and configuration.
Cron 9 Jobs and run history.
Admin 8 Multi-user administration.
Plan 8 Plan orchestration.
Respawn 7 Respawn configuration and presets.
Webviews 6 Saved dashboards, plus the proxy.
Mux 5 tmux operations.
Custom model endpoints 5 Saved OpenAI-compatible endpoints, and applying one to a session.
Push 4 Web push subscriptions.
Read My Mind 4 Intent profiles and prediction.
Scheduled 4 The legacy scheduled-run concept.
Approvals 4 The inbox, answering, acknowledging.
Tab layout 2 Named tab groups per owner.
Teams, me, search, hooks, clipboard, telemetry, voice, ws 1-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:

Call Blocks until
GET /api/v1/sessions/:id/wait One of a set of lifecycle signals fires.
GET /api/v1/sessions/:id/wait-output A literal string appears in the session's output.
POST /api/v1/sessions/:id/input + wait The 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 and deepseek sessions emit stop and blocked: Claude's come from Claude Code hooks, DeepSeek's from the harness reporting its state to Codeman. Shell and the other external CLI sessions accept idle, working, and exit.

SSE

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

The heartbeat is a named sse: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.

const es = new EventSource('/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

# with ID set to a session id:
curl -s "$API/api/sessions/$ID/last-response" | jq -r '.data.text'   # last answer, from the transcript (claude, codex, deepseek)
curl -s "$API/api/model-endpoints" | jq                                # saved custom OpenAI-compatible endpoints
curl -s -X POST "$API/api/sessions/$ID/custom-model" -H 'Content-Type: application/json' \
  -d '{"endpointId":"local-llama","modelId":"qwen3-27b"}' | jq        # restart the CLI on that endpoint; {"clear":true} undoes it

Limits

Limit Default
Max sessions 50
Max agent windows 500
Max SSE clients 100
Terminal buffer 32 MB per session
Text payload 1 MB
Wait timeout ceiling 600 s, and the response tells you what was applied

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