HTTP API
You will be able to drive commission over HTTP — every route, its request and response shape, how authentication works, and which status code each kind of failure produces — from a client that is not a shell.
The API is a thin wrapper over the same core functions the CLI calls. There is no second implementation, so a route cannot behave differently from its command.
Running it
commission serve --port 4400commission serving http://localhost:4400 (db: /Users/you/.commission/commission.db)
humans: http://localhost:4400/
agents: http://localhost:4400/v1/... (no token set — local mode)Two surfaces on one port: / is the read-only web view for people, /v1 is
the API. Add --sync 5m to run the background reconciliation loop; it is
pull-only, and outbound plans are logged rather than pushed — see
interop.
Authentication
Set COMMISSION_TOKEN in the server’s environment and mutations require it:
COMMISSION_TOKEN=s3cret commission serve --port 4400no COMMISSION_TOKEN | COMMISSION_TOKEN set | |
|---|---|---|
GET | allowed | allowed |
POST | allowed | requires Authorization: Bearer <token> |
A missing or wrong token on a mutation returns 401:
{ "error": "unauthorized", "message": "mutations require Authorization: Bearer <COMMISSION_TOKEN>" }Reads are never gated. That is deliberate: it is what lets you expose the board to a team read-only while writes stay agent-only. If you need reads protected too, put it behind something that does authentication properly — commission’s token is a single shared secret, not an identity system.
The token is not an actor. POST /v1/tasks/:id/approve resolves the actor
from the server process (COMMISSION_ACTOR), never from the request, and refuses
anyone who is not a human. A caller cannot name itself into a person, so the
endpoint grants no power the CLI does not already grant to whoever runs commission.
Version handshake
GET /v1/version{ "commission": "0.1.0", "schema": 11, "config": 1 }Three numbers a client can check before it talks: the binary’s semver, the
database’s schema version, and the .commission.json format this binary speaks. What
each one promises is in compatibility.md.
Routes
Reads
| method | path | returns |
|---|---|---|
GET | /v1/version | { commission, schema, config } |
GET | /v1/projects | every project row |
GET | /v1/projects/:slug/board | the board |
GET | /v1/projects/:slug/ready | the ready frontier |
GET | /v1/projects/:slug/inbox | the inbox — ?actor=, ?all=true, ?limit= |
GET | /v1/next?project=<slug> | preview: the selection plus its bundle — never claims |
GET | /v1/tasks/:id | the context bundle |
GET | /v1/prime | the agent operating manual, as text/plain — ?project= |
GET | /v1/search?q=<query> | full-text matches — ?project= scopes it |
GET | /v1/events | the event feed — ?project=, ?task=, ?since=, ?since_id=, ?limit= |
GET /v1/events returns an array with payload already parsed. Page with
since_id, not since: ids are monotonic, timestamps are a race.
Writes
| method | path | body | returns |
|---|---|---|---|
POST | /v1/next | { project, leaseMs? } | atomically selected and claimed task + bundle |
POST | /v1/tasks | a task definition | 201 with the created task |
POST | /v1/tasks/:id/transition | { to, force?, expectedVersion? } | { task, from } |
POST | /v1/tasks/:id/approve | { kind?, note?, role? } | 201 with the approval record |
POST | /v1/tasks/:id/claim | — | the claim row |
POST | /v1/tasks/:id/release | — | the actor that held it, or null |
POST | /v1/tasks/:id/notes | { text } | 201 { ok: true } |
POST | /v1/tasks/:id/evidence | { items: [{ kind, value, note? }] } | 201 { ok: true } |
POST | /v1/apply | an apply document | 201 with what was created and updated |
The atomic select-and-claim contract
GET /v1/next and POST /v1/next look similar and are not:
GETpreviews. It selects a task, builds its bundle, and changes nothing. Two clients calling it get the same answer.POSTselects and claims, in one operation. The claim is decided by a single conditional statement in SQLite, so two clients calling it concurrently receive different tasks. There is no window in which both can win, and no advisory lock to hold.
This is the orchestration primitive. commission next --claim and the commission_next
MCP tool are the same call; a fleet of workers needs no coordinator beyond it.
curl -s -X POST localhost:4400/v1/next \
-H 'content-type: application/json' \
-d '{"project":"myapp"}'The response is { task, claimed, skipped, selectedBy, routing, selectionReason, bundle }. task is null when the frontier is empty. skipped says which
tasks were passed over and why — usually already claimed by someone else.
selectedBy names the project’s selection policy and routing explains who may
hold it and what would change that. leaseMs: 0 claims without expiry; omit it
for the default eight hours.
Status codes
| code | when |
|---|---|
200 | ok |
201 | created — tasks, notes, evidence, approve, apply |
401 | mutation without a valid bearer token, when COMMISSION_TOKEN is set |
404 | the project, task, or record does not exist |
409 | optimistic-concurrency conflict — expectedVersion no longer matches |
422 | refusal: understood and declined |
500 | unexpected error |
Bodies for 404, 409, and 422 are commission’s refusal shape — error,
message, missing, hint — documented once in
exit codes and the refusal shape. A 422 is an instruction:
{
"error": "refusal",
"message": "cannot complete myapp-1: 1 unchecked criteria",
"missing": ["criterion 0: tested"],
"hint": "tick each with 'commission check myapp-1 0' — only once it is actually true; or --force to override (logged)"
}A missing required query parameter is also a 422 — GET /v1/next without
?project= and GET /v1/search without ?q= both refuse rather than guessing.
What the API deliberately does not cover
Project administration, configuration, import, and the workspace commands are CLI-only. They touch the local filesystem, the git checkout, and vendor CLIs the server has no business reaching on a caller’s behalf. If you need them remotely, run commission where those things live.
Related
- JSON output — the shapes these routes return
- Exit codes and the refusal shape
- CLI reference —
commission serveand its flags - compatibility.md — what may change, and when
Part of Reference.
This page is docs/reference/http.md in the Commission
repository, rendered in place — the site keeps no copy of it. The repository is private, so there is no edit link to follow.