Skip to content
Lakefront
Esc
↑↓navigate↵open⌘Jpreview
On this page

Wire protocol

The HTTP envelope, headers, status codes, and SSE event format every Lakefront client and router speaks.

This page is the transport-level reference; procedure input/output shapes live in the wire contract.

POST /rpc/<procedure>

Every read and write goes through POST /rpc/<procedure> with a JSON body matching that procedure’s input schema. <procedure> is one of query.execute, mutate.insert, mutate.update, mutate.delete, changes.read, node.stats. See the contract reference for procedure input and output types.

Envelope discipline

A handler that ran always answers 200, with the outcome in the body:

type WireEnvelope =
  { status: "ok"; value: unknown } | { status: "error"; error: { _tag: string; message: string } };

error._tag is the same discriminant the engine produced: a client branches on it directly rather than reverse-engineering an HTTP status. The full tag list is at the error catalog.

HTTP status codes are reserved for requests that never reached a handler:

Status Tag When
401 Unauthenticated Missing or invalid bearer token, when auth is on.
401 MissingBoardEnvelope No x-lakefront-board header and the token doesn’t pin exactly one board (or auth is off and no header was sent).
403 Forbidden Token’s claims don’t allow the procedure’s required verb on this board.
404 UnknownProcedure <procedure> isn’t in the contract.
400 InvalidInput Body isn’t valid JSON, or fails the procedure’s input schema.

A non-200 response still carries a JSON body of the same { status: "error", error: { _tag, message } } shape: a client can decode it the same way regardless of which side of the 200 line it landed on.

Headers

Header Direction Notes
content-type request/response application/json on /rpc/*; text/event-stream on /watch.
x-lakefront-board request The board a call targets. Omissible when the bearer token pins exactly one board (server derives it from the claim).
authorization request Bearer <token>. Absent entirely when the node runs with auth off.
x-lakefront-idempotent request Marks a keyed mutation retryable, so the router can hedge/fail over it without parsing the body. Set by clients exactly when the request body carries an idempotencyKey.
x-lakefront-served-by response (router only) The node id that actually served the request.
x-lakefront-hedged response (router only) "true" / "false": whether a hedge request was in flight when the winner answered.

GET /watch (SSE)

Server-sent-events change stream: catch-up from a watermark, then follow.

Aspect Value
Query param after: a watermark this engine handed out. Defaults to the node’s current head if omitted.
Auth verb read
content-type text/event-stream
Events change: one { seq, kind, payload, actor? } record. resync: { from }, when the requested range is beyond retention; re-read current state with a query and continue from from. error: { _tag, message }.
Heartbeat A : hb comment line every 15s.
Freshness Writes on this node push to the stream immediately (via an internal change listener). Writes landing on other nodes are picked up by a 500ms poll fallback. The stream is complete either way; the listener only sharpens latency.
Lifetime The stream ends once the token’s exp passes: a stream must not outlive its token by more than one poll interval.

Tail-only retention, what resync means for a consumer, and how to rebuild are covered in the watch guide.

GET /health

{ ok: true, nodeId }. No auth, no engine work. A cold node still answers.

Board id validity

A board id must match /^[A-Za-z0-9_]{1,64}$/u (core/types.ts, not the contract package; noted here since it gates every request that names a board). Board ids are interpolated into table names, so they’re validated rather than escaped; anything outside this charset is rejected with InvalidBoardId.

Examples

A query:

curl http://127.0.0.1:8080/rpc/query.execute \
  -H 'content-type: application/json' \
  -H 'x-lakefront-board: acme_roadmap' \
  -d '{"sql":"SELECT id, status FROM {{table}} LIMIT 10","tables":[{"token":"table","entity":"items"}]}'

A keyed mutation:

curl http://127.0.0.1:8080/rpc/mutate.insert \
  -H 'content-type: application/json' \
  -H 'x-lakefront-board: acme_roadmap' \
  -H 'x-lakefront-idempotent: 1' \
  -d '{"rows":[{"id":"i1","state":"active","title":"first item"}],"idempotencyKey":"create-i1"}'

An SSE subscribe:

curl -N http://127.0.0.1:8080/watch?after=0 \
  -H 'x-lakefront-board: acme_roadmap'

Last updated on September 10, 2026

Was this page helpful?