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.
Conventions
Section titled “Conventions”| Aspect | Behavior |
|---|---|
| Base URL | http://127.0.0.1:7878. The server binds to loopback only. |
| Auth | None. Keelson is single-user and local; state-changing endpoints are gated to loopback origins, not to a token. |
| Content type | JSON request and response bodies. |
| Typed shapes | The rib, snapshot, provider, and tool responses are zod-typed in @keelson/shared; import the type rather than hand-rolling it. |
Health and system
Section titled “Health and system”| Method | Path | Purpose |
|---|---|---|
GET | /api/health | Liveness and identity: the server name and schema version. Backs doctor’s server check. |
GET | /api/config | The schema and wire-protocol version handshake a client checks on connect. |
POST | /api/server/shutdown | Graceful shutdown. Token-gated by the server.json bearer; operator-internal. |
Ribs and tools
Section titled “Ribs and tools”| Method | Path | Purpose |
|---|---|---|
GET | /api/ribs | The 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/action | Dispatch an action to a rib’s onAction. Returns RibActionResponse. Loopback-trusted. |
GET | /api/tools | The shared tool registry, including rib tools, with family and the advisory state_changing / requires_confirmation flags. |
POST | /api/mcp | The 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.
Agents and commands
Section titled “Agents and commands”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.
| Method | Path | Purpose |
|---|---|---|
GET | /api/agents | Named chat agents ribs offer. Returns ListAgentsResponse ({ agents: AgentRef[] }), each a reusable turn template. |
POST | /api/agents/:ribId/:slug/resolve | Resolve one agent slug to an OpenChatSeed (system prompt, name, optional model). 404 on an unknown rib or slug. |
GET | /api/commands | The rib slash commands the composer shows. Deduped by name across ribs; first registered wins. |
GET | /api/commands/:ribId/:name/complete | Argument type-ahead. Returns the full candidate set for an empty prefix. |
POST | /api/commands/:ribId/:name/invoke | Invoke 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.
Providers
Section titled “Providers”| Method | Path | Purpose |
|---|---|---|
GET | /api/providers | The registered providers and their capabilities. |
GET | /api/providers/:id/models | The provider’s live model list, the source the picker prefers over the curated baseline. |
See Providers for the capability shape.
Gateways
Section titled “Gateways”| Method | Path | Purpose |
|---|---|---|
GET | /api/gateways | List 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/:name | Upsert 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/:name | Remove 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.
Snapshots
Section titled “Snapshots”| Method | Path | Purpose |
|---|---|---|
GET | /api/snapshots | The index of registered snapshot keys. |
GET | /api/snapshots/:key | The latest cached frame for a key (a SnapshotFrame). |
WS | /api/snapshots/:key/ws | Subscribe 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.
Chat and conversations
Section titled “Chat and conversations”| Method | Path | Purpose |
|---|---|---|
GET | /api/conversations | List chat conversations. Excludes workflow-run conversations (providerId !== "workflow"); reach those through the /api/workflows/runs* endpoints. |
POST | /api/conversations | Create a conversation. Rejects providerId "workflow" with a 400 pointing at POST /api/workflows/:name/runs. |
GET | /api/conversations/:id | One conversation with its messages. |
PATCH | /api/conversations/:id | Update a conversation (for example, rename). |
DELETE | /api/conversations/:id | Delete a conversation. |
POST | /api/chat/:cid/messages/:mid/remember | Promote a message into a memory draft. |
WS | /api/chat/ws | The chat turn stream: send a turn, receive MessageChunks until done. |
Workflows
Section titled “Workflows”| Method | Path | Purpose |
|---|---|---|
GET | /api/workflows | The merged catalog. |
GET | /api/workflows/:name | One workflow definition. |
POST | /api/workflows/:name/runs | Start 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/refresh | Re-run a workflow bound to a snapshot key (the board-refresh path). |
GET | /api/workflows/:name/runs | Runs for one workflow. |
GET | /api/workflows/runs | Recent runs across the catalog. |
GET | /api/workflows/runs/:runId | One run with its per-node outputs. |
POST | /api/workflows/runs/:runId/resume | Resume a paused run by replying to its approval node. |
POST | /api/workflows/runs/:runId/resume-run | Re-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/artifact | Read a file from a run’s sandboxed artifact directory. |
DELETE | /api/workflows/runs/:runId | Cancel 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-delete | Delete several runs at once. |
GET | /api/workflows/worktree-paths | The worktree directories isolated runs created. |
WS | /api/workflows/runs/:runId/ws | The 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.
Workspaces
Section titled “Workspaces”| Method | Path | Purpose |
|---|---|---|
GET | /api/workspaces/leases | Active workspace leases, returned as serializable records without the in-process release closure. Backs keelson workspace list. |
Approvals
Section titled “Approvals”| Method | Path | Purpose |
|---|---|---|
GET | /api/approvals | List pending policy-ask approvals. Returns { approvals: Approval[] }. Clients typically watch the POLICY_APPROVALS_SNAPSHOT_KEY snapshot stream instead of polling. |
POST | /api/approvals/:id | Resolve 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). |
Memory
Section titled “Memory”| Method | Path | Purpose |
|---|---|---|
GET | /api/memory/list | List memory rows, filterable by project and review state. |
POST | /api/memory/recall | The read path: full-text search ranked by relevance and recency. |
GET | /api/memory/review | The pending review queue. |
POST | /api/memory/review | Route a pending memory (instruction-grade, evidence, restricted, rejected). |
POST | /api/memory/writeback | Draft 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.
| Method | Path | Purpose |
|---|---|---|
GET | /api/usage/summary | Windowed 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/series | A time series over the window. bucket (hour | day) defaults to hour for 24h and day otherwise. |
GET | /api/usage/breakdown | A two-dimensional split: groupBy (default source) crossed with splitBy (default model). |
GET | /api/usage/jobs | Per-job token and cost rollups over the window. pricedEvents counts priced ledger rows, not runs. |
GET | /api/usage/events | The 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.
Projects
Section titled “Projects”| Method | Path | Purpose |
|---|---|---|
GET / POST | /api/projects | List or register projects. |
PATCH / DELETE | /api/projects/:id | Update or remove a project. |
POST | /api/projects/clone | Register a project by cloning a repository. |
GET / PUT | /api/projects/:id/notebook | Read or replace the project notebook. |
POST | /api/projects/:id/notebook/append | Append to the notebook (the note_project path). |
POST | /api/projects/:id/notebook/tidy | Compact 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.
Credentials
Section titled “Credentials”| Method | Path | Purpose |
|---|---|---|
GET | /api/credentials/:serviceId/status | Whether a credential exists, never its value. |
POST | /api/credentials/:serviceId | Store a credential in the OS keychain. |
DELETE | /api/credentials/:serviceId | Remove a stored credential. |
GET | /api/credentials/copilot/cli-status, /api/credentials/claude/cli-status | The provider CLI’s own auth status, for the sign-in surfaces. |
WebSocket streams
Section titled “WebSocket streams”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.
| Stream | Carries |
|---|---|
/api/chat/ws | A chat turn: MessageChunks until a terminal done. |
/api/workflows/runs/:runId/ws | Run events: node start, output, settle, and the approval-pause frame. |
/api/snapshots/:key/ws | A snapshot_update per recompose. No on-connect replay; GET the key to hydrate first. |
Related
Section titled “Related”- The CLI: the scriptable client over this surface, with the JSON envelope and exit-code contract.
- The Rib contract: the typed rib endpoints and their shapes.
- Snapshots and the canvas: the frame the snapshot stream carries.