Reference
HTTP API and events
app.serve() is the normal HTTP surface for function-first applications. It creates function routes
and validates inputs and outputs. The same generated Worker exposes persistent agent routes. Mount
createFlaryRunRouter only when you need the lower-level generic run API or a custom Flue
integration.
app.serve() also registers the persistent-agent API for each app.agent() export. You do not
write an app.get() or app.post() route for each action. Use its prefix option or mount the
returned Hono application in an existing Hono server.
Function routes
For a function named support, app.serve({ support }) creates:
| Method | Route | Purpose |
|---|---|---|
| POST | /functions/support | Validate input and return output |
| POST | /functions/support/runs | Admit a durable function run |
| GET | /functions/support/runs/:runId | Read status and output |
| POST | /functions/support/runs/:runId/cancel | Request cancellation |
| GET | /functions/support/runs/:runId/approvals | List pending approvals |
| POST | /functions/support/runs/:runId/approvals/:approvalId | Decide approval |
| GET | /functions/support/runs/:runId/user-input | List pending questions |
| POST | /functions/support/runs/:runId/user-input/:requestId | Resume with input |
The function client calls these routes through client.support() and client.support.start(). The
client currently polls durable status for a remote function run. Use the low-level event route when
the host requires an SSE cursor.
Persistent agent routes
For an agent named coder, app.serve({ coder }) creates authenticated routes for these resource
families:
| Resource | Examples |
|---|---|
| Threads | create, list, inspect, rename, archive, unarchive, pin, delete |
| Turns | send, queue, steer, edit, interrupt, rollback, compact, read history |
| Models | get, list allowed, set future default, read model history |
| Human control | approvals, user input, goals, unread cursor |
| Subagents | spawn, list, send, wait, interrupt, resume, close |
| Operations | audit, schedules, Recall, checkpoints, diff, restore, processes, Browser Run |
Use the typed client instead of constructing these paths by hand:
const thread = await api.coder.threads.open({
organizationId: "acme",
threadId: savedThreadId,
});
await thread.send({ message: "Continue the task." });
for await (const event of thread.stream({ after: savedCursor })) {
render(event);
}
The model stream uses the generated Flue route. Control and projection routes use the Flary host API. Both validate the same authenticated thread binding.
Hibernating WebSocket control
thread.connect() first creates a short-lived, single-use ticket through an authenticated HTTP
route. The client then opens the returned wss: URL. The Thread Control Durable Object consumes the
ticket and can hibernate while the connection stays open.
Client frames are versioned Zod records. Commands include a request ID and an idempotency key.
Supported commands cover send, steer, interrupt, approval, user input, subagent control, process
control, and Browser Run takeover. Server frames are ready, events, accepted, result,
error, resync_required, and pong.
The client acknowledges durable numeric cursors. If its cursor is outside the bounded socket replay
window, the server sends resync_required; use HTTP or SSE to read the older records before
reconnecting.
Low-level run routes
Mount createFlaryRunRouter below a route that already authenticates the request. Flary resolves
trusted context through the host callback.
Routes
| Method | Route | Purpose |
|---|---|---|
| POST | /runs | Admit one agent or workflow run |
| GET | /runs/:runId | Read current status and result |
| GET | /runs/:runId/events | Replay and stream normalized SSE |
| POST | /runs/:runId/input | Resume a waiting run |
| POST | /runs/:runId/cancel | Request cancellation |
The create body includes requestId, channelId, input, optional execution, profileId, idempotencyKey, requestedAt, traceContext, and metadata. It does not include tenantId, agentId, roles, scopes, or credentials.
SSE cursor
Send either afterSequence as a query value or Last-Event-ID as a header:
GET /v1/agents/support/runs/run_123/events?afterSequence=41
Last-Event-ID: 41
Accept: text/event-stream
The server sends a numeric event ID, an event type, and a JSON payload:
id: 42
event: message.delta
data: {"sequence":42,"type":"message.delta","payload":{"delta":"Hello"}}
Heartbeat events keep an active connection visible. The client must ignore heartbeats when rendering message content.
Event families
Normalized events include run admission and settlement, model and reasoning deltas, tool start and completion, approval requests and decisions, waiting and resumed state, user input, usage, checkpoint, completion, failure, and cancellation.
Validate every event with RunEventSchema before rendering or storing it:
import { RunEventSchema } from "flary/contracts";
const event = RunEventSchema.parse(incomingEvent);
if (event.type === "tool.started") {
showToolActivity(event.payload);
}
Errors
The host router returns a stable error envelope:
{
"error": {
"type": "invalid_request",
"message": "The Flary run request is invalid",
"details": []
}
}
Provider failures, missing credentials, approval denial, invalid mode access, and uncertain tool outcomes use typed error codes. Do not expose provider tokens, authorization codes, or raw upstream responses to the client.