API routes
The HTTP + SSE wire contract — eight routes that any runtime client speaks, with request/response shapes for each.
API routes are one surface in the frontend integration thematic island — siblings of react, server, query, and devtools.
The eight routes below are the canonical HTTP + SSE wire protocol
that runtime clients speak. The Next.js handlers in @pleach/core
are the reference implementation, but the shapes are
language-agnostic — an independent native client consumes them in
production, which is what keeps the wire honest.
Mount the routes under /api/harness/* (the path prefix is
convention, not contract — re-mount wherever you like; the
relative shapes are what matters).
The shipped, published route factory is createPleachRoute from
@pleach/core/quickstart — a Web-standard POST handler for the
execute path:
// app/api/harness/sessions/[id]/execute/route.ts (Next.js)
import { createPleachRoute } from "@pleach/core/quickstart";
// Provider auto-detected from the environment; pass
// { provider, plugins, tools, storage } to override.
export const POST = createPleachRoute();The full eight-route handler set documented below
(HarnessServer + ROUTES) is an internal substrate surface — see
Server. It is NOT a published @pleach/core/server
subpath today, so mount it from the reference Next.js handlers or
implement the wire contract directly. createPleachRoute above is the
one published factory and covers the single-POST execute path.
Route catalog
| Route | Method | Purpose |
|---|---|---|
/api/harness/health | GET | Health check + component diagnostics |
/api/harness/sessions | GET | List sessions for the authenticated user |
/api/harness/sessions | POST | Create a new session |
/api/harness/sessions/[id] | GET | Retrieve session state |
/api/harness/sessions/[id] | PUT | Update session state |
/api/harness/sessions/[id] | DELETE | Delete a session |
/api/harness/sessions/[id]/sync | POST | Version-vector sync (conflict detection) |
/api/harness/sessions/[id]/execute | POST | Execute a message (SSE streaming response) |
GET /api/harness/health
Health probe + component-level diagnostics. Cheap; safe to hit from a load balancer.
Response 200
{
"ok": true,
"version": "1.1.0",
"components": {
"storage": { "ok": true },
"checkpointer": { "ok": true },
"providers": { "ok": true, "configured": ["anthropic"] }
}
}Response 503 — runtime disabled via FEATURE_HARNESS_V2_RUNTIME=false.
GET /api/harness/sessions
List sessions owned by the authenticated user.
Query params
| Param | Type | Purpose |
|---|---|---|
limit | number | Page size (default 50, max 200) |
cursor | string | Opaque pagination cursor |
archived | boolean | Include archived sessions (default false) |
Response 200
{
"sessions": [
{ "id": "018f...", "createdAt": "...", "lastActiveAt": "...", "title": "..." }
],
"nextCursor": "01jc..."
}POST /api/harness/sessions
Create a new session.
Request body
{
"provider": { "type": "anthropic" },
"model": { "id": "claude-sonnet-4-5" },
"tools": { "enabled": ["search", "calculator"] }
}All fields optional; defaults match SessionRuntime.createSession.
Response 201
{ "session": { "id": "018f...", "createdAt": "...", "..." } }GET /api/harness/sessions/[id]
Retrieve a session's full state.
Response 200 — the SessionState shape (messages,
pendingToolCalls, completedToolCalls, pendingJobs, artifacts,
versionVector, etc.).
Response 404 — session not found or not visible to the
authenticated user. Code 2001.
PUT /api/harness/sessions/[id]
Update session state. The request body is the next SessionState;
the server validates the version vector and rejects stale writes
with 3001.
Request body
{
"version": 7,
"versionVector": { "client_abc": 4, "server": 3 },
"messages": [/* ... */],
"tools": { "enabled": ["search"] }
}Response 200 — the updated state.
Response 409 — version conflict. Code 3001. Body:
{
"code": "3001",
"error": "Version conflict",
"remote": { /* the server's current state */ }
}The client should resolve the conflict (typically by surfacing a chooser UI) and re-PUT with the merged result.
DELETE /api/harness/sessions/[id]
Delete a session. Cascades into the checkpointer; audit-ledger rows are preserved (append-only).
Response 204 — no content.
POST /api/harness/sessions/[id]/sync
Version-vector sync. The client pushes its local vector + queued changes; the server returns missing-from-remote changes plus the merged vector.
Request body
{
"clientId": "client_abc",
"vector": { "client_abc": 5 },
"changes": [/* SessionChange[] */]
}Response 200
{
"mergedVector": { "client_abc": 5, "server": 3 },
"remoteChanges": [/* changes the client hasn't seen yet */],
"applied": 3,
"rejected": 0
}Response 409 — concurrent vector that the merger couldn't
resolve. Code 3001 or 3002.
POST /api/harness/sessions/[id]/execute
Execute a message. SSE streaming response. Each event from
runtime.executeMessage becomes one SSE data: frame.
Request body
{
"content": "Summarize the latest changelog entries.",
"fileReferences": []
}Response 200 — Content-Type: text/event-stream. Each line:
data: {"type":"message.delta","delta":"The"}\n\n
data: {"type":"tool.started","toolCall":{...}}\n\n
data: {"type":"tool.completed","toolCall":{...},"result":{...}}\n\n
data: {"type":"message.complete","message":{...}}\n\n
data: {"type":"step.end","step":"post-turn"}\n\nSee Stream events for the full event catalog.
Aborting mid-stream
Close the SSE connection (EventSource.close() or fetch
AbortController.abort()). The runtime cancels in-flight
provider calls and flushes a final ledger row with
outcome.status: "user-aborted".
Reconnection
The runtime emits stream.reconnecting and
stream.disconnected events on retry. A reference client
reconnects on EventSource.error and replays from the last
seen event id. The server is idempotent on messageId —
re-submitting the same message after a reconnect doesn't fork
the turn.
Auth headers
The reference handlers expect:
| Header | Purpose |
|---|---|
Authorization: Bearer <token> | User auth — surfaces as runtime.userId |
X-Organization-Id | Multi-tenant scoping |
X-Client-Id | Sync version-vector key for this client |
Mock-mode (HARNESS_MOCK_MODE=true) accepts all requests as
anonymous for local development.
Error response shape
Every non-2xx body follows the same shape:
{
"code": "2001",
"error": "Session not found",
"recoveryHint": "The session id may be stale; try refreshing the session list."
}code is the structured error code — see
Error codes for the catalog.
Where to go next
HarnessServer
Framework-agnostic handlers behind these routes — mount into Express, Hono, or raw node:http.
React
The client hooks that speak this wire contract.
Query
Server-side read API over the same persisted data these routes write.
DevTools
Browser-console surface for inspecting session state these routes return.
HarnessServer
Framework-agnostic HTTP handlers wrapping the runtime — mount the routes into Next.js, Express, Hono, or any HTTP layer.
Query
Server-only read API over persisted harness data — usage, transcripts, events, jobs, assets, tools, analytics. Uses a service-role Supabase client and bypasses RLS; never import into browser bundles.