Skip to content

The HTTP and WS API

The server exposes one HTTP and WebSocket API on http://127.0.0.1:7878. The CLI and the browser SPA are both clients of it; a rib never reaches it, and an integration you write talks to the same surface they do. This page groups the endpoints by resource and documents the streams. The public wire shapes are typed in @keelson/shared, and the contract pages for each resource hold the exact payloads.

AspectBehavior
Base URLhttp://127.0.0.1:7878. The server binds to loopback only.
AuthNone. Keelson is single-user and local; state-changing endpoints are gated to loopback origins, not to a token.
Content typeJSON request and response bodies.
Typed shapesThe rib, snapshot, provider, and tool responses are zod-typed in @keelson/shared; import the type rather than hand-rolling it.
MethodPathPurpose
GET/api/healthLiveness and identity: the server name and schema version. Backs doctor’s server check.
GET/api/configThe schema and wire-protocol version handshake a client checks on connect.
POST/api/server/shutdownGraceful shutdown. Token-gated by the server.json bearer; operator-internal.
MethodPathPurpose
GET/api/ribsThe active ribs. Returns ListRibsResponse ({ ribs: RibSummary[], crossRibGrants? }). crossRibGrants is the operator’s grant map, Record<caller, Record<target, string[]>>; absent means “not reported” (an older server), an empty map means “reported, and there are none”.
POST/api/ribs/:id/actionDispatch an action to a rib’s onAction. Returns RibActionResponse. Loopback-trusted.
GET/api/toolsThe shared tool registry, including rib tools, with family and the advisory state_changing / requires_confirmation flags.
POST/api/mcpThe MCP gateway: the same tool registry over the Model Context Protocol, for external agents. Tokenless and exposing state-changing tools by default (restrict with exposeStateChanging: false); it answers any non-POST with 405 (no SSE stream). See Using keelson over MCP.

The harness also registers its own tools alongside the ribs’. workspace_lease creates a durable isolated checkout for a project, and workspace_release removes that checkout by lease id; both are state_changing: true. The op registry contributes five more: the read-only run_list, run_status, and run_events, plus the state-changing run_cancel and run_steer. They reach any long-running operation a rib put on the registry with registerOp. All seven appear in /api/tools and MCP.

See The Rib contract for the rib wire shapes.

Ribs contribute named chat agents and slash commands to the composer. These endpoints back them; a rib with no listAgents or listCommands hook simply contributes nothing here.

MethodPathPurpose
GET/api/agentsNamed chat agents ribs offer. Returns ListAgentsResponse ({ agents: AgentRef[] }), each a reusable turn template.
POST/api/agents/:ribId/:slug/resolveResolve one agent slug to an OpenChatSeed (system prompt, name, optional model). 404 on an unknown rib or slug.
GET/api/commandsThe rib slash commands the composer shows. Deduped by name across ribs; first registered wins.
GET/api/commands/:ribId/:name/completeArgument type-ahead. Returns the full candidate set for an empty prefix.
POST/api/commands/:ribId/:name/invokeInvoke a command. Returns the closed effect the surface performs (open an agent, run a workflow, or show a message). Loopback origin-gated.

See The Rib contract for the hooks behind these.

MethodPathPurpose
GET/api/providersThe registered providers and their capabilities.
GET/api/providers/:id/modelsThe provider’s live model list, the source the picker prefers over the curated baseline.

See Providers for the capability shape.

MethodPathPurpose
GET/api/gatewaysList configured gateways. Returns { gateways: GatewaySummary[] }: each summary includes name, baseUrl, protocol, model (optional), and signedIn (whether an API key is stored; the key itself is never returned).
PUT/api/gateways/:nameUpsert a gateway. Body: { baseUrl: string, protocol?: "openai", model?: string, apiKey?: string }. Omitting apiKey leaves any stored key unchanged. Returns the GatewaySummary for the upserted gateway. Origin-gated (CSRF guard).
DELETE/api/gateways/:nameRemove a gateway and its stored API key. Idempotent: returns 204 whether or not the gateway existed. Origin-gated.

Gateways are OpenAI-compatible endpoints (for example, a local Ollama instance) registered as providers named for the gateway. Non-secret metadata persists to config.json; the API key persists to the OS keychain and is never round-tripped to the browser. See Providers for the capability shape a registered gateway exposes.

MethodPathPurpose
GET/api/snapshotsThe index of registered snapshot keys.
GET/api/snapshots/:keyThe latest cached frame for a key (a SnapshotFrame).
WS/api/snapshots/:key/wsSubscribe to a key: a snapshot_update on every recompose. No frame is sent on connect, so GET /api/snapshots/:key first to hydrate, then subscribe.

See Snapshots and the canvas for the frame and payload shapes.

MethodPathPurpose
GET/api/conversationsList chat conversations. Excludes workflow-run conversations (providerId !== "workflow"); reach those through the /api/workflows/runs* endpoints.
POST/api/conversationsCreate a conversation. Rejects providerId "workflow" with a 400 pointing at POST /api/workflows/:name/runs.
GET/api/conversations/:idOne conversation with its messages.
PATCH/api/conversations/:idUpdate a conversation (for example, rename).
DELETE/api/conversations/:idDelete a conversation.
POST/api/chat/:cid/messages/:mid/rememberPromote a message into a memory draft.
WS/api/chat/wsThe chat turn stream: send a turn, receive MessageChunks until done.
MethodPathPurpose
GET/api/workflowsThe merged catalog.
GET/api/workflows/:nameOne workflow definition.
POST/api/workflows/:name/runsStart a run. isolation: "worktree" forces a managed checkout; "none" explicitly runs in the supplied directory. Omitted follows the workflow’s worktree.enabled policy.
POST/api/workflows/:name/refreshRe-run a workflow bound to a snapshot key (the board-refresh path).
GET/api/workflows/:name/runsRuns for one workflow.
GET/api/workflows/runsRecent runs across the catalog.
GET/api/workflows/runs/:runIdOne run with its per-node outputs.
POST/api/workflows/runs/:runId/resumeResume a paused run by replying to its approval node.
POST/api/workflows/runs/:runId/resume-runRe-start an interrupted (failed or cancelled) run from the last successfully completed node. Distinct from the approval-pause resume above. Required isolation must still be available or safely retryable.
GET/api/workflows/runs/:runId/artifactRead a file from a run’s sandboxed artifact directory.
DELETE/api/workflows/runs/:runIdCancel an in-flight run ({ cancelled: true }). With ?purge=1, purge the run and cascade its node outputs and conversation ({ deleted: true }). 404 for an unknown or already-completed run. Origin-gated.
POST/api/workflows/runs/bulk-deleteDelete several runs at once.
GET/api/workflows/worktree-pathsThe worktree directories isolated runs created.
WS/api/workflows/runs/:runId/wsThe run event stream: node start, stream, and settle, plus the approval-pause frame.

Run summaries and details carry error, workingDir, worktreePath, isolationEnabled, and worktreeEstablished. workingDir is the requested source. worktreePath is the managed execution checkout when one is retained. isolationEnabled is true, false, or null for unknown legacy intent. worktreeEstablished remains true after a successful checkout is cleaned up, so a null path does not imply that the run executed in place.

Required worktree setup is fail-closed. If the server cannot prove the source is a repository, prepare the checkout, or persist its identity, the run becomes failed with a run-level error before any node rows are written. Resume retries only a known-required setup failure with no node history. It rejects required runs that have node history but no retained checkout, as well as legacy runs whose isolation intent is unknown and whose checkout is unavailable.

MethodPathPurpose
GET/api/workspaces/leasesActive workspace leases, returned as serializable records without the in-process release closure. Backs keelson workspace list.
MethodPathPurpose
GET/api/approvalsList pending policy-ask approvals. Returns { approvals: Approval[] }. Clients typically watch the POLICY_APPROVALS_SNAPSHOT_KEY snapshot stream instead of polling.
POST/api/approvals/:idResolve one pending approval. Body: { decision: "accept" | "reject" }. Returns { ok: true } or 404 if the id is unknown or already resolved. Origin-gated (CSRF guard on the mutating route).
MethodPathPurpose
GET/api/memory/listList memory rows, filterable by project and review state.
POST/api/memory/recallThe read path: full-text search ranked by relevance and recency.
GET/api/memory/reviewThe pending review queue.
POST/api/memory/reviewRoute a pending memory (instruction-grade, evidence, restricted, rejected).
POST/api/memory/writebackDraft a memory through the guardrail gate.

See Memory and state for the governance model.

Read-only token-usage observability. These endpoints back the SPA’s Usage tab and the live pulse widget; they report token counts and the cost estimated from them at read time, and are a local observability surface, not a versioned public metrics contract. All five share a window param (24h, 7d, or 30d; default 7d) and, where noted, a groupBy (model, provider, source, rib, workflow, or sourceDetail). The live pulse widget subscribes to the USAGE_PULSE_SNAPSHOT_KEY snapshot stream (today’s totals plus a trailing series) rather than polling these routes.

MethodPathPurpose
GET/api/usage/summaryWindowed totals grouped by groupBy (default model). priceCards on the totals and each group lists the distinct rates and source (override, catalog, or bundled) its priced turns used.
GET/api/usage/seriesA time series over the window. bucket (hour | day) defaults to hour for 24h and day otherwise.
GET/api/usage/breakdownA two-dimensional split: groupBy (default source) crossed with splitBy (default model).
GET/api/usage/jobsPer-job token and cost rollups over the window. pricedEvents counts priced ledger rows, not runs.
GET/api/usage/eventsThe raw usage events, newest first, each with the priceCard it was priced at. limit caps at 500; filter by source (chat | workflow | rib), model, or status.

See Usage for the ledger model.

MethodPathPurpose
GET / POST/api/projectsList or register projects.
PATCH / DELETE/api/projects/:idUpdate or remove a project.
POST/api/projects/cloneRegister a project by cloning a repository.
GET / PUT/api/projects/:id/notebookRead or replace the project notebook.
POST/api/projects/:id/notebook/appendAppend to the notebook (the note_project path).
POST/api/projects/:id/notebook/tidyCompact the notebook.

POST /api/projects accepts { name, rootPath? }. Without rootPath, the host uses <workspaceRoot>/<name> (KEELSON_WORKSPACE, default ~/keelson). Explicit paths are trimmed, expand a leading ~ or ~/, and must be absolute. A missing or empty directory receives git init and one empty Initialize project commit using the operator’s configured Git identity, without touching project files. Existing Git repositories and populated non-Git folders are registered unchanged.

POST /api/projects/clone accepts { url, name? }. An omitted name derives from the URL’s final repository segment, lowercased and stripped of trailing slashes and .git. The destination is <workspaceRoot>/<name>; any existing destination, even an empty folder, is a conflict. Git runs noninteractively with a 60-second timeout.

Both requests reject unknown fields. Names are 1 to 64 characters, starting with a lowercase letter or digit and containing only lowercase letters, digits, -, or _. Success is 201 { project }, with the persisted id, name, resolved absolute rootPath, and creation time. Failure is { error }: 400 for invalid bodies or targets, 409 for duplicate names or exact canonical roots (including symlink aliases), 500 for initialization or registration failures, and 502 for clone failures. Nested projects beneath the default project are allowed. Mutations require an allowed loopback Origin; rejected origins receive 403 before any filesystem changes.

An initial-commit failure names missing user.name or user.email Git config. Cleanup removes only operation-created state and reports failures without hiding the original error. A registered non-Git project cannot host Write agents until it is a repository with a commit; host-initialized repositories are already ready. See Projects and worktrees.

MethodPathPurpose
GET/api/credentials/:serviceId/statusWhether a credential exists, never its value.
POST/api/credentials/:serviceIdStore a credential in the OS keychain.
DELETE/api/credentials/:serviceIdRemove a stored credential.
GET/api/credentials/copilot/cli-status, /api/credentials/claude/cli-statusThe provider CLI’s own auth status, for the sign-in surfaces.

Three resources stream over WebSocket, all under the same base URL. The chat and run streams replay current state on connect, then send incremental frames; the snapshot stream does not replay, so hydrate it with GET /api/snapshots/:key first.

StreamCarries
/api/chat/wsA chat turn: MessageChunks until a terminal done.
/api/workflows/runs/:runId/wsRun events: node start, output, settle, and the approval-pause frame.
/api/snapshots/:key/wsA snapshot_update per recompose. No on-connect replay; GET the key to hydrate first.