REST contract
MCP is the primary transport. This is the fallback for everything else — a vibe-coder can implement it in about 50 lines; reference servers in Node and Python live in examples/. Auth is a bearer token on every request.
GET /.well-known/parlane.json
Returns the manifest. 200 + application/json. See the manifest reference.
POST /chat → SSE stream
POST /chat
{
"conversation_id": "conv_123",
"message": "What's the reactor load?",
"attachments": []
}conversation_id is client-managed; your server may override it by returning a different id in the done event. Response: Content-Type: text/event-stream, one SSE event per line-group. The event types are the contract:
| event | data | meaning |
|---|---|---|
delta | { text } | An incremental chunk of assistant text. Concatenate in order. |
tool | { tool, status, label? } | Tool-call activity indicator. |
done | { conversation_id, message_id? } | End of turn. |
error | { code, message } | Terminal error; the stream ends. |
event: delta
data: {"text": "Reactor load is holding at "}
event: delta
data: {"text": "62%."}
event: tool
data: {"tool": "check_reactor", "status": "start", "label": "Checking reactor…"}
event: tool
data: {"tool": "check_reactor", "status": "end"}
event: done
data: {"conversation_id": "conv_123", "message_id": "msg_9"}The stream must end with exactly one done or one error. Clients paint the first delta within 150 ms of receipt — that's the target for a responsive-feeling composer, not a hard guarantee your network will hit.
GET /ui/{dashboard_id}
Returns a UI document (see the component catalog). 200 + application/json. dashboard_id matches a manifest dashboards[].id.
POST /action → { ui? | ack? }
POST /action
{
"action_id": "a1b2c3d4-0001",
"tool": "set_environment",
"dashboard_id": "systems",
"params": {
"source": "systems-dashboard",
"scrubber": true,
"targetTemp": 21.5
}
}Response — exactly one of ui / ack is meaningful:
{
"ui": {
"version": 1,
"id": "systems",
"root": { "type": "stack", "props": { "direction": "vertical" }, "children": [] }
}
}
// or, when nothing needs to change on screen:
{ "ack": true }If ui is present, the client replaces the current document entirely (diffed by id). Otherwise it applies the action envelope's refresh. A failed tool returns { error: { code?, message } }, surfaced non-fatally. There is no patch response — full-replace only.
POST /notify
Relay-side only. Your app-facing server never implements this directly — see relay self-hosting.
Auth
Bearer token on every request: Authorization: Bearer <token>. A server may print a QR encoding both its URL and a revocable, regenerable token so connecting is a single scan:
parlane://connect?url=https%3A%2F%2Fyour-server.example.com&token=sk_live_...Attachments
v1 = images, text, and URLs, with a 10 MB cap per attachment. Arbitrary files are deferred, based on real demand rather than a speculative feature.
| field | type | meaning |
|---|---|---|
kind | "image"|"text"|"url" | Attachment type. |
mime | string | MIME type. |
name | string? | Original filename, when known. |
data | string? | Base64 payload for image/text. ≤ 10 MB decoded. |
url | string? | For kind: "url", or a pre-uploaded blob URL. |
Exactly one of data / url is present.