Point 1 of the v1.0 lock-in: commit to a stable HTTP API (the cleanest, fullest form).
Core (centralized):
- Every JSON /api response now uses ONE envelope via a Fastify preSerialization hook (src/web/server.ts): success -> { success:true, data:<payload> }; error -> { success:false, error, errorCode } with a conventional HTTP status. Non-JSON routes (file-raw, tail-file SSE, download, screenshots, /q redirect, WS) are skipped.
- Error-code -> HTTP status is a single source of truth (httpStatusForErrorCode in src/types/api.ts): 400/401/404/409/422/429/500. Expanded ApiErrorCode (added UNAUTHORIZED, CONFLICT, RATE_LIMITED). Errors are no longer HTTP 200.
- Versioned alias: /api/v1/* rewrites to /api/* (rewriteApiV1Url), so external clients pin to a stable surface while the bundled UI keeps using /api/*.
- Handlers stripped of manual 'success:true' (50 across 14 route files) so they return bare payloads the hook wraps uniformly; fixed the mux DELETE {success:<bool>} envelope collision (-> {killed}).
Frontend (48 call sites across 10 files):
- _apiJson() auto-unwraps { success:true, data } -> data (null on error), so most bare-shape readers are transparent. Raw-fetch sites relocate payload reads under .data; success/res.ok/error checks unchanged.
Docs: new docs/api-reference.md (envelope, status table, error codes, /api/v1, SSE); versioning-policy.md flipped — the HTTP/SSE API is now part of the stable, SemVer-covered surface.
Verification: full unit/route suite green (2680 passed) incl. ~166 updated assertions across 24 test files; typecheck/lint/format/frontend-syntax clean; a headless-chromium smoke loaded the migrated UI and drove the panels with 0 console/page errors; /api/status and /api/v1/status confirmed returning the uniform envelope live.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.7 KiB
HTTP API Reference
Codeman's HTTP API is a stable contract as of 1.0 — see
versioning-policy.md for the SemVer guarantee. This page
defines the response envelope, status codes, error codes, versioning, and the SSE
event channel.
Versioning
- The stable, public surface is served under
/api/v1/.... Pin external clients to this prefix. - The unversioned
/api/...paths are a permanent alias of the current version (what the bundled web UI uses). They are kept working, but new external integrations should use/api/v1. - Breaking changes to the contract ship under a new prefix (
/api/v2);/api/v1keeps its semantics. Additive changes (new endpoints, new optional fields, new error codes) are non-breaking and may appear in a minor release. - The implementation rewrites
/api/v1/*→/api/*at the server level (rewriteApiV1Urlinsrc/web/server.ts).
Response envelope
Every JSON response uses one uniform envelope, applied centrally by a
preSerialization hook (src/web/server.ts) — handlers return bare data and the
hook wraps it:
Success — HTTP 2xx:
{ "success": true, "data": <payload> }
data is the endpoint's payload (object, array, or value). Endpoints with no
payload return { "success": true, "data": {} }.
Error — HTTP 4xx/5xx:
{ "success": false, "error": "human-readable message", "errorCode": "NOT_FOUND" }
ApiResponse<T> in src/types/api.ts is the canonical type.
Non-JSON endpoints are exempt from the envelope:
GET /api/sessions/:id/file-raw,GET /api/sessions/:id/tail-file(SSE),GET /api/download,GET /api/screenshots/:name,GET /q/:code(QR redirect), and theGET /ws/sessions/:id/terminalWebSocket upgrade.
Error codes → HTTP status
The single source of truth is ErrorStatus / httpStatusForErrorCode() in
src/types/api.ts. Clients should branch on errorCode (stable) and may rely on
the HTTP status.
errorCode |
HTTP | Meaning |
|---|---|---|
INVALID_INPUT |
400 | Malformed request / failed validation |
UNAUTHORIZED |
401 | Authentication required or failed |
NOT_FOUND |
404 | Resource does not exist |
SESSION_BUSY |
409 | Session is busy |
CONFLICT |
409 | Conflicts with current state (e.g. already running) |
ALREADY_EXISTS |
409 | Resource already exists |
OPERATION_FAILED |
422 | Well-formed but could not be completed |
RATE_LIMITED |
429 | Too many requests |
INTERNAL_ERROR |
500 | Unexpected server error |
Adding a new error code is non-breaking; removing or renaming one is a major change.
Authentication
Optional HTTP Basic (CODEMAN_USERNAME/CODEMAN_PASSWORD) → opaque
codeman_session cookie. When enabled, unauthenticated requests get
401 UNAUTHORIZED; rate-limited requests get 429 RATE_LIMITED. See
security-architecture.md.
SSE event channel
GET /api/events is a Server-Sent Events stream (text/event-stream); each
message is event: <name> + data: <json>. The event-name registry
(src/web/sse-events.ts, mirrored in src/web/public/constants.js) is part of
the stable contract — event names are not renamed without a major bump. An
optional ?sessions=<id,...> filter suppresses only the high-volume terminal
stream; lifecycle/metadata events are delivered to all clients regardless.
Consuming from JavaScript
The bundled frontend reads responses through _apiJson()
(src/web/public/api-client.js), which unwraps {success:true,data} → data and
returns null on a non-2xx / {success:false} response. External clients should
do the same: check the HTTP status (or body.success), then read body.data.