Skip to content

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

bash
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 env

The 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:

bash
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 too

Every 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:

bash
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 object

Routes 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

bash
# 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 / paramWhat 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: 1Validate and project the result without writing. Response carries X-Dry-Run: applied.
?with_description=trueInflate description on *.list responses.
?with_content=trueInflate content on doc.list responses.
?no_feature_docs=1, ?no_comments=1, ?no_doc_content=1Brief opt-outs on GET /issues/{key}/brief.
?limit, ?offset, ?op, ?kind, ?actor, ?since, ?from, ?to, ?oldest_firstHistory filters on GET /history.

Errors

json
{ "error": "title is required", "code": "invalid_input", "details": {"field": "title"} }
StatusCodeWhen
400invalid_inputMalformed JSON, unknown field, validator failure, missing required X-Actor on claim.
401unauthorizedToken configured and bearer is missing or wrong.
404not_foundPath resolves no such entity.
409conflictDuplicate slug / prefix / PR URL / document filename.
413payload_too_largeBody exceeds 4 MiB.
500internalServer-side panic (caught by recovery middleware).

API-only quirks

  • POST /repos is the equivalent of bacio init, but the server can't see the caller's CWD — pass {"name":"...", "path":"..."} (plus optional prefix) explicitly.
  • GET /repos/{prefix}/documents/{filename}/download is the only non-JSON endpoint. Streams the body as text/markdown with Content-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_REMOTE on the clients. (For multi-machine peer sync, prefer git-backed sync.)

See also

bacio · v0.1 · MIT