Run the API server
bacio api exposes every CLI mutation and read over HTTP, backed by the same SQLite database, JSON shapes, validators, and audit log. The CLI conventions all apply — discover schemas, compose JSON, dry-run, then commit. Only the transport differs.
Start the server
bacio api # bind 127.0.0.1:5320, no auth
bacio api --addr 127.0.0.1:7777 # custom port, still no auth
bacio api --token T # require Authorization: Bearer T
BACIO_API_TOKEN=T bacio api # token via envThe default 127.0.0.1:5320 binding plus no auth is the trust boundary — only loopback callers can hit it. Bind to a public address only with --token set.
Pointing other bacio calls at it
bacio itself can drive a remote server instead of the local DB:
export BACIO_REMOTE=http://127.0.0.1:5320
export BACIO_API_TOKEN=… # if the server enforces auth
bacio issue list # now hits the API
bacio issue add "New bug" --feature auth # mutating verbs work tooEvery read and mutating verb behaves identically over remote — same flags, same JSON output, same --dry-run, same --user. Audit rows are written by the server.
Verbs that touch the local filesystem or terminal error clearly in remote mode and stay local-direct: bacio init, bacio install-skill, bacio doc add --from-path / --content-file, bacio doc export, bacio tui, bacio schema *, bacio status.
Discovery endpoints
The schemas are the same as the CLI's:
curl http://127.0.0.1:5320/schema/list # every operation name + summary
curl http://127.0.0.1:5320/schema/issue.add # full JSON Schema with examples[0]
curl http://127.0.0.1:5320/schema # everything in one objectRoutes follow REST under /repos/{prefix}/... — list/create on the collection, show/patch/delete on the item, plus sub-resources for state (/state, /assignee), batch ops (/tags, /pull-requests), graph edges (/relations, /links), bulk reads (/brief, /plan), and claim/peek (/next). Use GET /schema/list to enumerate every operation.
Issue keys in URLs and bodies must be canonical (MINI-42) — the bare-number shortcut isn't accepted over HTTP.
A curl smoke test
# Health check (no auth required)
curl http://127.0.0.1:5320/healthz
# List issues in repo MINI
curl http://127.0.0.1:5320/repos/MINI/issues
# Add an issue, attributing the action to "claude-code"
curl -X POST http://127.0.0.1:5320/repos/MINI/issues \
-H 'Content-Type: application/json' \
-H 'X-Actor: claude-code' \
-d '{"title": "Login 500 on Safari"}'
# Dry-run the same call
curl -X POST 'http://127.0.0.1:5320/repos/MINI/issues?dry_run=true' \
-H 'Content-Type: application/json' \
-H 'X-Actor: claude-code' \
-d '{"title": "Login 500 on Safari"}'Headers and query params
| Header / param | What it does |
|---|---|
Authorization: Bearer <token> | Required when --token is set. Constant-time compare. |
X-Actor: <agent-name> | Stamps the audit log. Absent → falls back to literal "api" (not OS user). Required on POST /repos/{prefix}/features/{slug}/next (claiming work needs a real assignee). |
?dry_run=true / X-Dry-Run: 1 | Validate and project the result without writing. Response carries X-Dry-Run: applied. |
?with_description=true | Inflate description on *.list responses. |
?with_content=true | Inflate content on doc.list responses. |
?no_feature_docs=1, ?no_comments=1, ?no_doc_content=1 | Brief opt-outs on GET /issues/{key}/brief. |
?limit, ?offset, ?op, ?kind, ?actor, ?since, ?from, ?to, ?oldest_first | History filters on GET /history. |
Errors
{ "error": "title is required", "code": "invalid_input", "details": {"field": "title"} }| Status | Code | When |
|---|---|---|
| 400 | invalid_input | Malformed JSON, unknown field, validator failure, missing required X-Actor on claim. |
| 401 | unauthorized | Token configured and bearer is missing or wrong. |
| 404 | not_found | Path resolves no such entity. |
| 409 | conflict | Duplicate slug / prefix / PR URL / document filename. |
| 413 | payload_too_large | Body exceeds 4 MiB. |
| 500 | internal | Server-side panic (caught by recovery middleware). |
API-only quirks
POST /reposis the equivalent ofbacio init, but the server can't see the caller's CWD — pass{"name":"...", "path":"..."}(plus optionalprefix) explicitly.GET /repos/{prefix}/documents/{filename}/downloadis the only non-JSON endpoint. Streams the body astext/markdownwithContent-Disposition: attachment. No audit row, no dry-run.- No CLI equivalent → not exposed:
bacio install-skill(writes a file in the caller's repo),bacio tui(terminal-bound),bacio doc add --from-path(read the file yourself and inline the content).
When to use the API
- You're building a web UI or IDE plugin on top of bacio.
- Long-running agents that don't want to fork a CLI per call.
- One machine is the source of truth, others are clients — set
BACIO_REMOTEon the clients. (For multi-machine peer sync, prefer git-backed sync.)
See also
bacio api— the CLI reference.- JSON payloads — the same contract the HTTP API exposes.
- How agents drive bacio — the rules behind the contract.
