Runs, threads and realtime API
Create and cancel runs, stream a thread over SSE, answer questions and approvals, and reach the sandbox through the terminal, live, and desktop transports.
The core operational surface. A run is a durable command; a thread is a conversation of runs keyed by the root run id. See Runs, threads and commands. All routes here are org-session scoped unless noted.
Runs and threads
| Method | Path | Description | Notes |
|---|---|---|---|
| POST | /api/runs |
Create a run (root or reply). | Body: prompt (required), model, engine (one of the engine ids), parent_run_id, repo, repos[], branches ({"owner/name": branch}), memory_scope, skill ({id, version}), command ({name, args, provider, sessionId, catalogRevision}), attachments[] (at most 10 upload ids), resources[]. Header Idempotency-Key. origin is server-owned and rejected. 201 {id, status, queue} on create, 200 {id} on idempotent replay. |
| GET | /api/runs |
List runs, newest first, roots-only by default. | Query all=1 (every run), limit (1-100; 1-1000 in summary), view=summary, include_active=1. Returns {runs}. |
| GET | /api/runs/:id |
Fetch a run with its steps, or the whole thread. | Query thread=1 returns {thread} (oldest to newest); otherwise {...run, steps}. 404 run not found. |
| POST | /api/runs/:id/cancel |
Durable Stop: record run.cancel, fail queued work, signal the live actor. |
No body. 202 {status:"cancelling"} accepted, 200 when already settled or replayed. 404 run not found. |
| POST | /api/runs/:id/questions/:questionId/reply |
Answer a native provider question inside the running turn. | Body: answers. Rejects resources/attachments (400). 409 question_session_not_active, 409 questions_not_supported, 502 question_reply_failed. |
| POST | /api/runs/:id/approvals/:requestId/reply |
Resolve a native provider approval in the active turn. | Body: decision. 409 approval_session_not_active, 502 approval_reply_failed. |
| GET | /api/runs/:id/uploads |
The run’s inbound attachments (Slack files, browser uploads). | {uploads}. 404 run not found. |
| GET | /api/runs/:id/timings |
Per-run turn timing table (diagnostics). | 404 run not found. |
| DELETE | /api/runs/:id/sandbox |
Release the thread’s sandbox (explicit eval/test cleanup). | 404 run not found, 409 thread is active, 502 sandbox release failed. |
POST /api/runs failure codes worth knowing: 400 for invalid body, missing
prompt, a client-supplied origin, a bad engine/memory_scope,
reply_engine_mismatch, model_not_allowed, or invalid_command; 404 parent run not found; 401 authenticated user required for attachments; 403 model_provider_not_ready / resource_unauthorized; 409 idempotency_key_reused
/ upload_unavailable; 429 on the durable per-org queue ceiling; 503 RunAdmissionClosedError (retryable: true).
Realtime streams
Server-sent events. All are org-authorized before the stream opens; the thread id
is the root run id and is resolved server-side. Streams set Cache-Control: no-cache, no-transform and X-Accel-Buffering: no, with a 25s heartbeat.
| Method | Path | Description | Notes |
|---|---|---|---|
| GET | /api/runs/changes |
Tenant-scoped invalidation stream for ambient surfaces. | Carries ids only (“refresh this snapshot”), no payloads. |
| GET | /api/runs/:rootRunId/thread-events |
One stream aggregating every run in a conversation. | Frames: snapshot, run, step, delta, native, canonical, canonical-complete, done (settles one run, does not close). Overflow past the live-frame bound closes the socket so the client reconnects to a fresh snapshot. |
| GET | /api/runs/:id/events |
Per-run trace: replay existing steps, then live-push. | Query cursor (native seq; replays frames strictly greater). Frames: step, delta, native, done (closes). The rollback path, kept beside thread-events. 404 run not found. |
Chat
A lightweight, no-sandbox conversational surface (#122) with read-only retrieval.
Org-session scoped; personal-scope retrieval only applies when a real user
resolves. Inert without an OpenRouter credential (503).
| Method | Path | Description | Notes |
|---|---|---|---|
| GET | /api/chat/models |
The served model catalog and current default. | {models: [{value, ...}], ...}. |
| POST | /api/chat |
SSE chat completion with cited retrieval, no sandbox. | Body: messages (non-empty [{role, content}], must contain a user turn), model (optional, must be in catalog), memoryScope (default "org"). Emits event: context ({citations}) once, then event: delta tokens, then event: done. 400 model_not_allowed, 503 when no OpenRouter credential resolves (customer key beats house key). |
Commands
| Method | Path | Description | Notes |
|---|---|---|---|
| GET | /api/commands |
The cached slash-command catalog for “/” autocomplete before a sandbox exists. | Query engine (claude/codex/… read an ACP engine’s org-scoped catalog; default/opencode reads the default snapshot catalog). Returns {engine, commands, fetched_at}; commands is [] when uncached. |
Gateway approvals
The session-authenticated surface for approving gateway tool operations. Minting and per-request approve/deny are user-scoped; the run must be active and belong to the member. See Gateway tools and approvals.
| Method | Path | Description | Notes |
|---|---|---|---|
| POST | /api/gateway/approvals |
Mint an approval capability for a gateway tool operation on an active run. | Body: runId, toolName (must be an approval-requiring tool), arguments (must omit approvalCapability). 201 {capability, expires_at, tool_name, arguments_hash}. 404 run_not_found, 409 run_not_active, 403 run_user_mismatch. |
| GET | /api/gateway/approvals/requests |
List pending mid-run approval requests. | Query: runId and/or threadId (at least one required). {requests} - the parked capability is never returned here. |
| POST | /api/gateway/approvals/requests/:id/approve |
Approve a pending request. | {id, status}. 404 request_not_found/run_not_found, 403 run_user_mismatch. |
| POST | /api/gateway/approvals/requests/:id/deny |
Deny a pending request. | {id, status}. Same error mapping as approve. |
Sandbox transports
Same-origin bridges into the live sandbox. These are transports, not enumerable
REST APIs: the WebSocket bridges carry their own message protocol, and the two
.all() proxies forward arbitrary opencode/noVNC paths verbatim (injecting the
Daytona preview token the browser cannot set).
| Method | Path | Description | Notes |
|---|---|---|---|
| WS | /api/runs/:id/terminal |
Browser xterm to the sandbox PTY. | Open query cols/rows. Messages {type:"input", data} and {type:"resize", cols, rows}. One PTY per connection, killed on disconnect. Does not touch the event log. |
| ANY | /api/live-proxy/:threadId/* |
Same-origin bridge to the thread’s opencode server (:4096). |
404 thread not found (cross-org/unknown), 409 no live sandbox yet, 502 on upstream failure (retries once after re-resolving). /api/model and /api/provider catalog responses are trimmed to the allowlist. |
| WS | /api/desktop-proxy/:threadId/websockify |
Browser noVNC canvas to sandbox websockify (:6080). |
Binary RFB frames both ways; provisions the desktop and injects the preview token. |
| GET | /api/desktop-proxy/:threadId/ready |
Desktop readiness probe. | 204 ready, 409 no live sandbox, 502 on failure. |
| ANY | /api/desktop-proxy/:threadId/* |
HTTP proxy for the noVNC static app. | /vnc.html triggers a one-shot desktop provision; other assets pass through. |