HTTP API Reference
All Cacophony services expose their APIs under /api/v1/. Default content type is application/json; SSE endpoints use text/event-stream.
Authentication
Prefer first-party caco CLI commands for local inspection because they read the node token without placing it in shell history or process arguments.
caco status --json true
caco node show --json true
If you must use raw HTTP on a trusted single-user machine, keep the token file private and avoid pasting bearer values into shared logs. Shell-expanded curl bearer-header examples can expose the token through process inspection while the command runs.
Discovery
| Method | Endpoint | Description |
| GET | /api/v1/node | Node identity, version, config hash, listeners, and startup-progress status |
| GET | /api/v1/nodes | All configured nodes with last-seen, hash, liveness |
| GET | /api/v1/nodes/<node> | Single node detail |
| GET | /api/v1/projects | All configured projects |
| GET | /api/v1/projects/<project> | Single project detail |
Cluster stats
| Method | Endpoint | Description |
| GET | /api/v1/stats | Canonical unified-stats aggregate consumed by all four CLI/web/Android/TUI frontends — one endpoint, not four parallel collectors. Query params: project (or all, default all), node (narrows the per-node rows; the fleet rollup stays whole), window (e.g. 24h/7d/2w or a bare hour count, default 24h), bucket (hour/day). |
The /api/v1/stats response is { generated_at, scope{project?, node?, window_hours, bucket}, compute, per_node[], throughput, tokens }. compute is the cluster ClusterComputeRollup (nodes_counted, cpu_cores, mem_total_bytes, mem_used_bytes, disk_total_bytes, disk_used_bytes, nodes_with_disk) and each per_node[] row carries node, platform, load_avg_1, cpu_cores, and the same mem/disk byte fields, sourced from local live telemetry plus peer snapshots. tokens is the fleet token-usage rollup by project ({ by_project, grand_total }) summed across the local node and peers. throughput is the bead-lifecycle throughput summary by project: totals aggregates closed_last_24h/closed_last_7d and throughput_beads_per_hour, and each by_project[] entry adds throughput_window_hours, average_time_to_close_secs, and a series object — the dense closed-bead time-series for charts (window_hours, bucket, buckets, total_closed), driven by the request window/bucket. The whole field is null only when no beads data is reachable (for example when the beads primary is unavailable). Read-only; available on both the local and cluster listeners.
Feed
| Method | Endpoint | Description |
| GET | /api/v1/feed | Feed event history |
| GET | /api/v1/feed/stream | Feed SSE stream |
| GET | /api/v1/ui/stream | UI aggregate SSE stream for browser dashboard, companion app, and TUI (agents, beads, feed, choices, services) |
| GET | /api/v1/ui/snapshot | Full UI state snapshot for browser dashboard, companion app, and TUI hydration; includes crons[] with configured daemon cron entries used by mobile clients to enumerate cron names, speech history entries whose completed transcriptions include transcript text plus text_length, and a bounded topology projection with static/dynamic nodes, direct/relay edges, active beads-primary metadata, intended-primary views, and truncation markers for large clusters. ?exclude=<section,...> omits top-level data sections a client renders without (for example flow_definitions,flow_runs for a single-agent view); filtered bodies carry their own ETag and are cached per snapshot generation |
| GET | /api/v1/operator-inbox | Operator inbox messages (direct, broadcast, speech) |
Browser caco-web treats HTTP 5xx snapshot/bootstrap failures and SSE/network failures that outlast the bounded 15s transient-error grace after the last healthy SSE open or UI event as Backend unavailable, even if the dashboard web service and local /health endpoint are still healthy during daemon restart convergence.
- Snapshot timeouts: The
GET /api/v1/ui/snapshot proxy hop is bounded at 8 seconds and the browser-side snapshot fetch budget is 12 seconds; timeout returns a 200 OK JSON sentinel with ok: false, error: "daemon_proxy_timeout", backend_unavailable: true, upstream_status: 504, and X-Caco-Upstream-Status: 504. caco-web maps that timeout sentinel to Snapshot delayed / snapshot_degraded while it retries; if it arrives before any usable bulk snapshot data, the hero/freshness copy says the snapshot proxy timed out and avoids healthy-looking zero-count cluster metrics.
- Upstream errors: Upstream snapshot server errors such as 500/502/504 are also translated into console-clean
200 OK JSON sentinels with ok:false, error:"daemon_proxy_upstream_error", backend_unavailable:true, upstream_status:<original status>, and X-Caco-Upstream-Status:<original status>; those map to Backend unavailable rather than Snapshot delayed.
- Quiet reconnects: Any background mesh/full-state refresh completion stays in daemon logs/feed diagnostics instead of a toast.
Short-lived EventSource errors during the SSE grace keep the connection pill
Connected while the browser reconnects.
If the upstream SSE body is interrupted after headers have been sent, the proxy emits a handled SSE event: error payload with error:"daemon_proxy_stream_error", backend_unavailable:true, and reconnecting copy instead of surfacing Chromium net::ERR_INCOMPLETE_CHUNKED_ENCODING as the primary operator signal.
- Recovery: After a real disconnect or unavailable/proxy-timeout snapshot state, SSE reconnect queues a full snapshot refresh and preserves the queued reconnect reason when another snapshot request is already in flight, so state missed during the gap is repaired without a manual Refresh.
Sustained SSE failures keep last-known dashboard rows visible as cached/reconnecting when usable data exists; only pages with no usable state fall back to disconnected/backend-unavailable copy.
Daemon automation
| Method | Endpoint | Description |
| GET | /api/v1/cron | List configured daemon cron entries. Response data.crons[] includes name, schedule, action_type, command, and nodes. |
| POST | /api/v1/cron/run | Dispatch a named cron immediately on the current node; body is {"name":"..."}. The caller should supply a stable x-caco-request-id: exact-ID retries replay one terminal receipt instead of executing twice, while a different request is refused during an in-flight run of the same cron. Success is reported only after observed command completion. Failures include the execution receipt; an outer deadline returns 503 cron_run_backpressured with request ID, phase, operation_started, dispatched:false, and Retry-After. The request deadline defaults to 330 seconds and can be overridden with CACO_CRON_RUN_REQUEST_TIMEOUT_SECS. |
| GET | /api/v1/cron/logs?name=<name>&tail=<n> | Tail one cron log file. Response data contains name, completed runs[], backward-compatible raw lines[], and log_path. Each run includes run_id, start/finish timestamps, duration, exit code, success/status, combined stdout/stderr output, and output_truncated. Combined output is response-bounded and preserves emission order because cron stderr is merged into stdout before capture. Android requests tail=80 for each distinct cron name from /api/v1/ui/snapshot. |
Managed-agent Heartbeats
| Method | Endpoint | Description |
| GET | /api/v1/heartbeat | List local managed-agent heartbeat rows, or query agent/agent_id to resolve one local or remote owner and return its authoritative row over the daemon mesh. node remains a legacy local assertion for selector-free operations. |
| POST | /api/v1/heartbeat/on | Persist a runtime override enabling heartbeat delivery. Body {"agent":"..."} or {"agent_id":"..."} transparently routes to a remote owning daemon; an empty selector retains local-node bulk behavior. |
| POST | /api/v1/heartbeat/off | Persist a runtime override disabling heartbeat delivery when runtime opt-out is allowed, with the same selected-agent mesh routing and selector-free local bulk semantics. |
Heartbeat APIs are authenticated mesh-routed controls for a selected agent: the ingress daemon resolves the authoritative owner and forwards over mTLS without requiring callers to know a node. Selector-free calls remain local-node bulk operations, and explicit CLI routing (caco @node heartbeat ... or caco @all heartbeat ...) remains available for deliberate node/fleet scopes. Status is readable by authenticated agent scopes; mutation requires operator/node or cluster-controller authority. JSON rows distinguish owner node, config defaults, runtime overrides, effective enabled state and reason, interval, last/next timestamps, delivery health, and transition provenance.
Feed Event Schema
{
"id": "evt-01ARZ3NDEKTSV4RRFFQ69G5FAV",
"ts": "2026-03-16T12:00:00Z",
"origin_node": "localhost",
"project": "cacophony",
"type": "agent_status",
"sender": "localhost:cacophony:sunny-owl",
"target": null,
"payload": {
"status": "running tests"
}
}
Standard Event Types
Daemon Events
daemon_started
daemon_stopped
daemon_state
Config Events
config_hash
config_mismatch
Agent Events
agent_requested
agent_started
agent_status
agent_message
agent_broadcast
agent_completed
agent_failed
agent_stopped
Bead Events
bead_created
bead_updated
bead_claimed
bead_closed
Agents
| Method | Endpoint | Description |
| POST | /api/v1/projects/<p>/agents | Spawn agent |
| GET | /api/v1/projects/<p>/agents | List latest-known project agent inventory, including peer snapshot rows when available; ?local=true restricts to daemon-local live rows and responses include inventory/source metadata |
| GET | /api/v1/projects/<p>/agents/<id> | Project-validated agent detail |
| GET | /api/v1/agents/<id> | Compatibility detail alias for /api/v1/agents/<id>/status, including remote-owner forwarding |
| GET | /api/v1/agents/<id>/launch?node=<owner> | Shared launch progress: stage is one of accepted, project_checkout, workspace, configuring, launching, ready or failed, with an operator-facing label, per-stage history and a structured error on failure. Async creates return it as launch plus a launch_url; the optional node hint forwards cross-node launches before the agent row replicates. Every stage is also published as a launch_progress UI-stream event. 404 launch_unknown when nothing is tracked. |
| POST | /api/v1/agents/<id>/stop | Stop agent (global; forwards to the owning node) |
| POST | /api/v1/agents/<id>/pause | Pause agent (global; forwards to the owning node) |
| POST | /api/v1/agents/<id>/restart | Restart agent (global) |
| POST | /api/v1/agents/<id>/resume | Resume a failed/stopped/paused agent with a lifecycle-specific budget; client transport failures before a semantic response are surfaced as unexpected agent_resume_transport_error when the daemon node probe remains reachable. |
| POST | /api/v1/agents/<id>/refresh | Re-materialize the owning agent runtime config workspace in place. The response reports agent_dir, checkout_path, and updated_paths[]; session history, checkout data, and lifecycle state are preserved. |
| POST | /api/v1/agents/<id>/nudge | Nudge agent; lifecycle-scoped but intentionally shorter-budget than stop/restart/resume so controller health probes fail quickly with retryable diagnostics when tmux or peer forwarding is stuck |
| GET | /api/v1/agents/summary | Aggregate fleet health from the background agent read model. Potentially-stuck rows include bead context from the agent record or, when missing, from assigned in-progress bead lookup; JSON fields include bead_id_source, bead_status, stale_closed_bead_assignment, stale_closed_bead_id, bead_is_blocked, and bead_open_dependency_ids. Assignment, planned-outage, peer-health, and message-delivery-health collectors are independently bounded; base counts remain HTTP 200 while degraded, degraded_sections, collection_elapsed_ms, and section_timeout_ms identify omitted enrichment. |
| GET | /api/v1/agents/<id>/status | Canonical global agent status/detail endpoint with remote-owner forwarding. |
| GET | /api/v1/agents/<id>/logs / /log / /diff | Read-only agent diagnostics: structured logs, raw tmux scrollback, and checkout diff. Diff supports summary_only=true and max_diff_bytes=<N> for lighter polling. |
| GET | /api/v1/agents/<id>/files/tree / /files/read | Read-only agent filesystem browser for managed agent directories. |
| GET | /api/v1/agents/<id>/artefacts / /artefacts/<kind>/<index> | Session summary and related agent artefact listing/reads. |
| GET | /api/v1/agents/<id>/pty | Current structured interactive PTY WebSocket. |
| POST | /api/v1/agents/<id>/{stop,restart,refresh,fork,pause,resume,discard,recreate,nudge} | Global lifecycle controls that resolve and forward to the owning node. |
The project-scoped agent endpoints validate project membership and are the preferred create/message surface. The global /api/v1/agents/... family is the cross-node read/lifecycle surface used by CLI, TUI, web, and companion clients. Do not call a non-existent /api/v1/agents/<id>/reintegrate; reintegration remains a lifecycle/CLI surface unless a future daemon endpoint is explicitly added.
Remote agent inspection and lifecycle forwards should not leak peer-side 502 Bad Gateway responses as recurring local daemon 502s. For common operator endpoints such as agent detail/status, restart, and diff, the local daemon classifies a forwarded unavailable target as remote_agent_unavailable with degraded/unavailable local status, remote node, endpoint, forwarded status, remote error code/message, and retryable metadata when available. Remote status forwarding is also bounded as an authority/health probe, so a settling or half-reachable peer returns structured retryable failure quickly instead of consuming an entire controller sweep.
Spawn Request Body
{
"node": "localhost",
"type": "codex",
"profile": "worker",
"goal": "Implement ready work",
"id": null,
"env": {},
"hooks": {
"on_start": [],
"before_tool": [],
"after_tool": [],
"on_complete": []
},
"reintegration": {
"mode": "direct"
}
}
Session summaries
Summary APIs return the union of state-branch .cacophony/agent/.../summary records and legacy default-branch summaries, de-duplicating by project, agent, and index with the state-branch copy preferred. For state-branch reads, the daemon prefers a warmed read-only checkout under $CACOPHONY_DIR/state-branches/<project>/ and falls back to the canonical project checkout's remote-tracking state ref when that cache is unavailable.
| Method | Endpoint | Description |
| GET | /api/v1/summaries?project=&agent_id=&bead_id=&limit=&offset= | List recorded summaries, newest first. project may be omitted, a single project, or a comma-separated project list. Responses include items, total, limit, and offset for paging; large unscoped state branches use a bounded recent-commit fast path before falling back to exhaustive enumeration, while agent_id-filtered state-branch walks stay scoped to that agent's summary subtree. List responses may also include first_detail, the parsed detail for the first returned row, so TUI, web, and Android summary viewers can populate the initial detail pane without a second request while still falling back to the detail endpoint when absent. Summary endpoints use a dedicated daemon timeout (default 60s, override CACO_SUMMARIES_REQUEST_TIMEOUT_SECS, minimum 5s) so cold state-branch scans do not fail under the ordinary control-plane budget. CLI summary readers distinguish a whole-daemon transport outage from a summaries-only failure; if /api/v1/node is healthy but the summaries route fails, JSON reports retryable summaries_unavailable instead of telling operators to run caco up. |
| GET | /api/v1/summaries/<agent_id>/<index>?project= | Show one parsed summary with Goal / Beads / Before / After / Diff / Artefacts / Takeaway sections. |
| GET | /api/v1/summaries/<agent_id>/<index>/raw/<asset_path>?project= | Serve a recorded raw artefact such as a screenshot, terminal cast, or data.json. Paths are constrained to the selected summary directory. |
Beads
| Method | Endpoint | Description |
| POST | /api/v1/projects/<p>/beads | Create bead |
| GET | /api/v1/projects/<p>/beads | List beads (with query params) |
| GET | /api/v1/projects/<p>/beads/<id> | Bead detail |
| PATCH | /api/v1/projects/<p>/beads/<id> | Update bead |
| DELETE | /api/v1/projects/<p>/beads/<id> | Delete bead |
| POST | /api/v1/projects/<p>/beads/refine | Apply post-creation refinement instructions to selected existing beads |
| POST | /api/v1/projects/<p>/beads/dispatch | Atomically reserve one bead and spawn or queue its exact worker. JSON fallback and SSE use the shared typed phase contract; terminal outcomes are done, queued, or failed. The queued receipt carries bead, reserved agent, node, queue-entry id, and position. |
| POST | /api/v1/projects/<p>/beads/claim | Claim next ready bead |
| POST | /api/v1/projects/<p>/beads/<id>/close | Close bead |
| GET | /api/v1/projects/<p>/beads/<id>/attachments | List bead attachments |
| POST | /api/v1/projects/<p>/beads/<id>/attachments | Append bead attachments |
| GET | /api/v1/projects/<p>/beads/<id>/attachments/<attachment_id> | Read one bead-authorized attachment |
| GET | /api/v1/projects/<p>/beads/stats | Lifecycle metrics (avg time-to-close, throughput, in-progress) |
| GET | /api/v1/projects/<p>/beads/throughput-series?window_hours=<h>&bucket=<hour|day> | Closed-bead throughput time-series buckets — the data behind the stats throughput view. The unified /api/v1/stats throughput field carries the matching by-project closed-throughput summary. |
| GET | /api/v1/beads/all | Global bead list with owning project per row |
| GET | /api/v1/beads/spoken-names | Lightweight cross-project TTS projection with only project, id, and spoken_name rows plus count/failure metadata |
| GET | /api/v1/beads | Canonical beads routing and status summary, including the primary view that caco status and caco bd status use for authority identity |
Messaging
| Method | Endpoint | Description |
| POST | /api/v1/projects/<p>/messages/send | Send direct message; accepts only after bounded local durable storage and returns retryable msg_send_backpressured when the message store is unavailable before acceptance; replication, injection, audit/event-log append, and lifecycle refresh continue after acceptance; the CLI performs one mutating POST attempt and classifies transport failures as msg_send_backpressured with endpoint/node-probe/retry metadata when /api/v1/node remains healthy; if the bounded node probe is unavailable too, the same retryable classification is returned with daemon_probe.ok:false instead of retrying a potentially accepted send |
| POST | /api/v1/projects/<p>/nodes/<node>/messages/send | Fan out ordinary direct-message rows to injectable agents on one node; request global:true includes all projects on that node, and responses include per-recipient accepted/failed metadata |
| POST | /api/v1/projects/<p>/messages/broadcast | Broadcast message |
| POST | /api/v1/projects/<p>/messages/speak | Speak via TTS. A busy primary message store returns bounded HTTP 503 message_store_busy with Retry-After, lock-wait/timeout diagnostics, and operation_started:false. |
| GET | /api/v1/projects/<p>/messages/inbox | Read inbox |
| GET | /api/v1/projects/<p>/messages/chat | Project chat history (?limit=, ?include_body=, ?body_preview=, ?before=). before is an RFC3339 cursor returning only strictly older messages, so memory-bounded clients page backwards instead of refetching a larger full-history response; a short page signals end of history. |
| GET | /api/v1/messages/chat | Global chat history (all projects); same limit/include_body/body_preview/before parameters |
| POST | /api/v1/messages/broadcast | Broadcast message (global). Direct send and speak are project-scoped only; there is no global /api/v1/messages/send or /speak. |
Actions
| Method | Endpoint | Description |
| GET | /api/v1/actions | List available project actions |
| POST | /api/v1/actions/{name}/run | Execute a project action |
Release Queue
| Method | Endpoint | Description |
| POST | /api/v1/projects/<p>/releases | Trigger a release job |
| GET | /api/v1/projects/<p>/releases | List release jobs (?status=, ?channel=, ?limit=). Terminal status includes completed, skipped, failed, canceled, and error. Provider-backed rows expose provider_conclusion; a provider-confirmed no-op additionally exposes no_op_reason and uses state: "skipped" rather than a failure state. Owner-dispatched Android Play promotion rows use deterministic android-promotion-<source-run-id> identities and expose exact source_run_id, source_head_sha, and downstream dispatched_run_id correlation through the same durable/replicated release surface. |
| GET | /api/v1/projects/<p>/releases/{id} | Release job detail |
| GET | /api/v1/projects/<p>/releases/{id}/logs | Release job build/push output |
| POST | /api/v1/projects/<p>/releases/{id}/cancel | Cancel a release job |
| POST | /api/v1/projects/<p>/releases/sync | Trigger GitHub Actions sync |
| GET | /api/v1/projects/<p>/releases/config | Resolved release channel configuration |
Notifications
| Method | Endpoint | Description |
| POST | /api/v1/notifications | Create a replicated operator notification from caco notify send or a first-party subsystem. |
| GET | /api/v1/notifications?project=&level=&source=&acknowledged=&limit= | List recent notifications, newest first, with optional project, severity, source, acknowledgement-state, and limit filters. |
| GET | /api/v1/notifications/{notif_id} | Fetch one notification by ID. |
| POST | /api/v1/notifications/{notif_id}/ack | Acknowledge one notification. Web and TUI clear/read actions call this endpoint so dismissed rows stay dismissed after the next snapshot refresh. |
Suggestions
| Method | Endpoint | Description |
| POST | /api/v1/suggest | Generate a suggestion set from a prompt (node-local; proposes only, never runs). |
| GET | /api/v1/suggest/list | List suggestion sets. |
| GET | /api/v1/suggest/{uuid} | Fetch one suggestion set, including option run-state. |
| POST | /api/v1/suggest/{uuid}/{option_uuid}/run | Run one option, node-local, under the high-risk DENY backstop plus run-once enforcement (403 suggest_run_denied_high_risk / 409 suggest_run_already_run). |
File cache
| Method | Endpoint | Description |
| GET / POST | /api/v1/file-cache | List cached project files, or add a file/content blob. |
| GET | /api/v1/file-cache/{file_id}/content | Retrieve file bytes. Internal cluster-mTLS content URLs are sanitized to null in user-facing records. |
| GET | /api/v1/file-cache/{file_id}/preview | Bounded preview of a cached file. |
| GET | /api/v1/file-cache/{file_id}/associations | Associated bead/agent/build/test IDs for a file. |
Links
| Method | Endpoint | Description |
| GET / POST | /api/v1/links | List URL/link artifacts, or record one, with project/bead/tag/host filters. Internal cluster-API URLs are refused at the writer and sanitized in user-facing fields. |
Audio
| Method | Endpoint | Description |
| POST | /api/v1/audio/speech | Text-to-speech |
| POST | /api/v1/audio/transcription | Speech-to-text through provider, local, or configured command-template STT backends; command-template model labels advertised by capabilities are valid model values. Provider warmup/backpressure/connectivity failures return structured transcription_unavailable diagnostics such as transcription_provider_unavailable with retry metadata instead of opaque gateway errors. |
TTS Request
{
"model": "gpt-4o-mini-tts",
"voice": "sage",
"text": "Build completed successfully",
"format": "wav"
}
Supported TTS models: gpt-4o-mini-tts, azure/speech/azure-tts, gemini-3.1-flash-tts-preview.
Gemini TTS uses the generateContent API with response_modalities: ["AUDIO"] and requires a Google API key.
- Timeouts: Provider-backed speech synthesis uses a longer bounded daemon timeout than ordinary control-plane endpoints (default
90s, override CACO_AUDIO_SPEECH_REQUEST_TIMEOUT_SECS) so transient provider latency does not fail as a generic 30-second request timeout; the headless TTS daemon uses a matching client budget just below that endpoint timeout (default 85s, override CACO_TTS_AUDIO_TIMEOUT_SECS).
- Expected skips: If the local daemon API is unavailable during an expected restart window, or a quick local daemon-port probe shows the listener is unavailable during a transport failure, the headless TTS daemon classifies speech and acknowledgement connection failures as bounded daemon-unavailable skips rather than provider synthesis failures.
Intentional mute policy is also a non-error path: a speech response with
audio_muted is treated as skipped playback, traced as audio-muted, and not reported as a daemon exception.
- Caller identity: For feed-originated speech, the headless TTS daemon forwards the originating sender as
x-caco-caller before calling this API, so mute-policy evaluation, traces, tts status --explain, and audit output identify the real source agent when known; node-token is only an auth fallback and should not be the displayed agent for agent-authored speech.
- Spoken names: Bead spoken-name refresh is best-effort: failed refreshes back off quietly, use the lightweight
GET /api/v1/beads/spoken-names?limit=5000 ID/name projection with a short timeout instead of the full UI snapshot path or broad all-beads payload during daemon startup/backpressure, fall back to /api/v1/beads/all?limit=5000 during mixed-version rollouts, periodically retry after the cooldown when the feed is connected but quiet, including while SSE keepalive chunks are arriving without bead-bearing events, include source endpoint, transport class, error-chain detail, stale-cache versus no-cache failure classification, and a bounded /api/v1/node local-daemon probe in lookup-health diagnostics, and resume normally after a successful refresh.
TTS failures use classified provider error envelopes instead of a generic timeout. Error codes include upstream_provider_5xx, upstream_provider_4xx, upstream_provider_timeout, upstream_provider_unreachable, upstream_provider_transport_error, and upstream_response_read_failed. Where available, details includes upstream_error_class, provider host/endpoint, and HTTP status so callers can distinguish provider outage, bad request, timeout, and transport failures.
Config
| Method | Endpoint | Description |
| GET | /api/v1/config/hash | Local config hash |
| GET | /api/v1/config/peers | Peer config hashes |
| GET | /api/v1/config/diff | Config comparison |
Choices & Operator Inbox
| Method | Endpoint | Description |
| GET | /api/v1/choices | Current active choices (excludes test-agent choices). Read responses include choice_id, choiceId, and legacy id aliases for the same interaction. A busy persisted store returns bounded HTTP 503 choices_store_backpressured with state_known_clear:false, never a fabricated empty set. |
| GET | /api/v1/choices/list | Choice history with optional status/project filters. Read responses include choice_id, choiceId, and legacy id aliases. |
| GET | /api/v1/choices/{choice_id} | Single choice lookup by interaction ID, including terminal choices. Read responses include choice_id, choiceId, and legacy id aliases. |
| POST | /api/v1/choices/present | Present a new choice to the operator. Store-lock contention returns bounded retryable choices_present_backpressured with operation_started:false before any insert. |
| POST | /api/v1/choices/resolve | Resolve an active choice (first-writer-wins) |
Active unresolved choices are treated as urgent operator-decision blockers across first-party clients. TUI, caco-web, Android, macOS, and speech/readout surfaces derive their pending-choice counts from these choices/inbox APIs, route the operator to a resolution surface, and clear the urgent state once the shared choice status becomes terminal. Replicated choice_presented and choice_resolved feed events also materialize or update the durable local operator-inbox choice row so remote choice reads and operator surfaces do not wait for a slower full-state inbox pass before showing or clearing urgent decisions.
Test Agent Isolation
The /api/v1/choices endpoint automatically filters out choices from test-type agents, preventing test data from leaking into operator-facing surfaces.
Restart
| Method | Endpoint | Description |
| POST | /api/v1/restart | Trigger daemon restart (fire-and-forget) |
The restart endpoint responds immediately with accepted: true before spawning the restart subprocess. Since the restart tears down the current daemon process, the actual outcome cannot be reported synchronously. The caller should poll daemon health to determine success. Available on both local and cluster listeners.
Logs
| Method | Endpoint | Description |
| GET | /api/v1/logs | Log history; mobile clients should request and render bounded tails |
| GET | /api/v1/logs/stream | Log SSE stream |
| POST | /api/v1/logs/error | Submit a structured error report (handle_log_error). Companion apps post runtime errors as first-class platform errors using their existing node/operator token, tagged via component (ios-app / android-app) plus platform: / surface: / kind: labels; see the Logs error-report contract. |
Sidecar Endpoints
Each long-running service exposes a sidecar with uniform endpoints:
| Endpoint | Purpose |
GET /ping | Liveness check |
GET /health | Health with check details |
GET /logs | Log metadata and tailing |
Cluster-Only Endpoints
These endpoints are available only on the cluster (HTTPS/mTLS) listener:
| Method | Endpoint | Description |
| POST | /api/v1/feed/events | Accept feed events from peer |