condensate

Reference

Runtime HTTP API

Every runtime HTTP route, grouped by task, with its error codes.

On this page

The runtime serves HTTP on 127.0.0.1:4741 by default. Set CONDENSATE_AGENTS_PORT to change the port. The listener binds to loopback only. Remote callers reach it through Tailscale Serve or another local front.

This page lists the runtime's HTTP routes, grouped by what they do, and the error codes a program can branch on. The SDK wraps most of them and decodes their responses, so prefer it when you can. The request bodies and response schemas are defined in packages/agents-host/src/http.ts and in the SDK. HTTP route index lists every route on the runtime's listener, generated from the routers.

The optional engine field on a spawn selects the agent that runs the thread: Claude Code (claude), Codex (codex), or Condensate's own agent (pi). Without it, the host picks the engine from the model.

Errors

Refusals return JSON with an error message. The refusals a program needs to branch on also carry a code, listed below. When a code is present, branch on it, not on the message text. Simple input errors, such as a bad limit or cursor, return a 400 with only error.

StatusCodeMeaning
400spawn-cwd-requiredA build thread has no cwd. Pass a worktree.
400spawn-cwd-fencedThe cwd sits inside a checkout the host fences off.
400account-not-in-rosterThe spec names an account outside the workspace's account list, or, with no workspace, an account a workspace claims. The body lists the workspace's accounts.
400model-not-allowedThe model is outside the workspace's allowed list. The body lists the allowed models.
409not-a-headA role change names a thread that is not a head.
429session-capThe host is at its live session cap. Retry after Retry-After.
429thread-list-rate-limitedThe client has spent its budget for first-page listings. Retry after Retry-After.
503drainingThe host is draining for a restart. Retry after Retry-After.
503spawns-pausedNew spawns are paused because free disk space is low. Retry after Retry-After.

A path no route claims returns a 404 with { "error", "build" }. build is the commit the host runs, the same one GET /health reports, so a route missing from an older build is easy to spot.

Read threads

MethodRoutePurpose
GET/threadsA page of thread summaries. Query: limit (default 50; values above 200 are clamped to 200), after (the nextAfter cursor), sort (created, the default, or recent), archived (live, the default, archived or all), role, type, workspace, parent (one thread's children), attached (threads held by an attached seat), and seats (open, the default, or all).
GET/threads/:idThe thread row, its stage, the cost so far and the archive flag.
GET/threads/:id/tailA page of the durable event log. after=<seq> reads forward. before=<seq> reads older events, newest first. limit defaults to 200 and caps at 1000. wait=<ms> holds the request open for new events, up to 30 seconds.
GET/threads/:id/followServer-sent durable events. Resumes from after, or from the Last-Event-ID header.
GET/threads/:id/streamServer-sent live deltas. Nothing is replayed, and a slow reader loses its oldest frames.
GET/threads/:id/aguiThe thread as an AG-UI event stream, from after=<seq>.
GET/threads/:id/agui-pageOne reverse page of the log as AG-UI frames, for chat clients that open at the end and page back.
GET/threads/:id/turnWhether a turn is running.
GET/threads/:id/headThe thread's type and metadata, including its seat lease.
GET/threads/:id/steeringEvery steering turn in the thread, oldest first.
GET/threads/:id/attachmentsThe thread's attachments.
GET/threads/:id/memoryThe memory recorded for the thread.
GET/threads/:id/tool-results/:seqThe full body of one tool result.
GET/threads/:id/operations, /operations/:operationDelegations, subscription edges and long actions, or one of them. POST on the same paths starts an operation or reports its outcome. A second outcome for a finished operation returns 409 operation-settled.
GET/threads/:id/subscriptionsThe thread's subscriptions.
GET/threads/:id/compactCompaction status.

Use after for forward reads and before for older history, and keep the two cursors apart. A forward page returns events, nextAfter and hasMore. A reverse page returns prevBefore and hasMore.

Poll the tail or follow route for changes in one thread. To watch the list, follow GET /workspaces/:name/changes and re-read only the threads that changed. The first page of GET /threads is rate-limited per client, keyed on the caller's address and its x-condensate-client header (or User-Agent), so repeated full-list polling gets a 429.

curl -fsS 'http://127.0.0.1:4741/threads?limit=20'
curl -fsS 'http://127.0.0.1:4741/threads/THREAD_ID/tail?limit=20'

Replace THREAD_ID with an ID from a listing response.

Start work

MethodRoutePurpose
POST/spawnCreate a thread from a spawn spec. Returns { threadId }. Refused while the host is draining or spawns are paused.
POST/threads/:id/messagesAppend a message and return 202 with its seq and ts, before any model runs. Set hold: true to keep the message held for editing with PATCH or DELETE /threads/:id/messages/:seq. Use from to name a program that sent text no person typed.
POST/threads/:id/sendSend a message and wait for the reply. With async: true it behaves like messages and returns 202.
POST/threads/:id/forkBranch the conversation. The child continues the parent as it stood at atSeq.
POST/threads/:id/loop, /loop/continue, /loop/stopStart, continue or stop a loop on the thread. GET /threads/:id/loop reports its state.
GET, POST/threads/:id/seatsList the thread's seats, or join a new one. DELETE /threads/:id/seats/:name removes a seat.
POST/threads/:id/seats/replaceChange a seat's engine, model or placement. The seat keeps its name and its history. Returns changed: false when nothing differs.
PUT/threads/:id/seat-leaseRecord the lease that holds the thread. Body { "seatLease": "<id>" }, or null to clear it.
POST/threads/:id/handoffOffer the thread to another seat. Then read GET /threads/:id/handoffs, and answer with POST /threads/:id/handoffs/:fromSeq/accept or /decline.
POST/threads/:id/compactCompact the thread's context now.
POST/threads/:id/events, /gate, /mergeWrite cycle events for review and merge flows.
POST/threads/:id/subscriptionsSubscribe to the thread's events. The body can name a filter, a note and a deliver target. DELETE /threads/:id/subscriptions/:watch removes one.
POST/threads/:id/roleRetire a head to chat. No other role change is accepted.
POST/threads/:id/ui/generateGenerate an interface for the thread.

These routes have side effects, and each has its own body schema. Use the SDK methods, or read the schema in http.ts, rather than guessing a body from the route name.

Acceptance means the host recorded the request. It does not mean the work succeeded. Read the durable log after an accepted request to see the outcome.

Control a turn

MethodRoutePurpose
POST/threads/:id/interruptStop the running turn.
POST/threads/:id/approvals/:approvalIdResolve a tool approval. Body { "decision": "allow" } or { "decision": "deny" }. An approval that is already closed answers with code: approval-closed.
GET/threads/:id/questions/:requestIdThe status of a question the seat asked.
POST/threads/:id/questions/:requestId/answer, /dismiss, /continueAnswer a question, dismiss it, or continue without an answer.
POST/threads/:id/recoverRecover a thread.
DELETE/threads/:id?reason=Retire a thread. Nothing is deleted. The host appends one archive event to the thread's log.
POST/threads/:id/restoreBring a retired thread back. Returns 409 when the workspace's head slot was taken in the meantime.

Workspaces and memory

MethodRoutePurpose
GET/workspacesThe configured workspaces.
GET/workspaces/:nameOne workspace, with its GitHub slug.
PUT/workspaces/:name/policyReplace the workspace policy.
GET/workspaces/:name/headThe thread that holds the workspace's head role.
GET/workspaces/:name/approvalsEvery tool call waiting on approval in the workspace, each with the thread it stops.
GET/workspaces/:name/questionsOpen questions across the workspace.
GET/workspaces/:name/eventsServer-sent workspace events.
GET/workspaces/:name/changesServer-sent changes to the workspace's threads. Name the threads to watch with threads.
GET/workspaces/:name/tailRecent events across the workspace.
POST/workspaces/:name/memory/recall, /retain, /reflectRecall, store and reflect on workspace memory.
GET/workspaces/:name/memoryBrowse workspace memory.

A workspace has one head. A POST /spawn that asks for a second head in the same workspace is refused with a 409.

Review rooms and pull requests

MethodRoutePurpose
POST/workspaces/:name/roomsArm a review room for a PR, or adopt an existing one by key.
GET/rooms/:idThe room's state and its seats.
GET/workspaces/:name/rooms?repo=&pr=Find a PR's room and its newest round.
GET/rooms/capabilitiesWhether the verdict poster is available.
POST/rooms/:id/roundsFire a review round. Body { "prompt": "...", "seat": "..." }, both optional. Returns 409 when the room is not armed, or a round is running or the reviewer is busy.
GET/rooms/:id/rounds/:nOne round.
POST/rooms/:id/finalizePost a round's verdict as a GitHub review. Returns 409 when the verdict is not ready, has been superseded, or is stale.
POST/workspaces/:name/prs/:number/agentsStart a build thread on the PR's branch, in a worktree the runtime owns.
GET/workspaces/:name/prs/:number/receipt?repo=A read-only merge receipt: the verdict and CI at the exact head, the base chain, stacked children and the merge plan.

Code and GitHub reads

MethodRoutePurpose
GET/code/roots, /code/outline, /code/files, /code/readBrowse and read the registered repositories.
POST/code/searchSearch code with a pattern. This is a POST because patterns do not fit a query string well.
GET/github/:owner/:repo/prsThe repository's pull requests.
GET/github/:owner/:repo/prs/:number/diff, /comments, /checksThe diff, the comments and the checks for one PR.
POST/github/overlapRun one duplicate-work pass. It is a dry run unless CONDENSATE_PR_OVERLAP=on.

Completions endpoint

POST /v1/chat/completions and GET /v1/models expose an OpenAI-compatible completions endpoint. Each call is one model call. It runs on one of the workspace's accounts, and fails over to another account if the first one refuses.

A completions call is not an agent thread. It has no tools and no durable multi-turn state. For agent work, use POST /spawn.

Each call writes one completion event to the workspace's completions thread, so the usage is recorded there.

HeaderPurpose
x-condensate-workspaceThe workspace the call is charged to. Required, unless the host sets CONDENSATE_COMPLETIONS_DEFAULT_WORKSPACE.
x-condensate-sessionAn optional session label, up to 128 characters.
x-condensate-trafficwork (the default) or smoke.

Set stream: true in the body to receive server-sent deltas. Image content is not supported in this version. GET /v1/models lists the models the endpoint can route, without the deprecated ones.

Errors use OpenAI's shape, { "error": { "message", "type", "code" } }. Codes include unknown_workspace (400), model_not_found (404), no_eligible_account and no_spendable_account (409), accounts_exhausted (429) and rate_limited (429, with Retry-After when the provider gave a wait).

Operating the host

MethodRoutePurpose
GET/healthThe build commit, the fields /spawn accepts, and any open memory alert.
GET/liveLive threads, and any open account-pool exhaustion episodes.
GET/limitsThe build seat limit, read from the hub.
GET/host-loadLoad averages, CPU count and the booted iOS Simulators on the host.
GET/timingsPer-request timings.
GET/metrics, /metrics/history, /metrics/estate, /metrics/keeper, /metrics/cost-per-merged-prRuntime metrics.
GET, POST, DELETE/drainRead, arm or disarm a drain for a restart. /drain/claim manages claims.
GET/keeper/spawn-pause, POST /keeper/spawn-pause/clearRead or clear the spawn pause.
GET/attention, /attention/changes, /attention/follow, /attention/:idItems that need a person.
GET/incidentsFailed-turn incidents.
GET/peers, POST /peers/external, DELETE /peers/external/:namePeers, and external peers that record threads without a local engine.
POST/previews, GET /previews, DELETE /previews/:idLocal server previews.

Account routes under /accounts are covered in Accounts.

Some operator routes check the Host and Origin headers and refuse callers that are not local. These include the Codex reset-credit route and the model-refusal clear.

During a drain or a spawn pause, new threads are refused with a 503 and a Retry-After header. Running turns continue. A draining response reports how many threads are still mid-turn.