clone

a self-hosted sync node for lofi apps: jazz sync over iroh, ticket-gated, with no static ip or cloud needed

release/0.3.0
lofi-node / docs / site / http-surface.md
http-surface.md 4.7K rendered
1# HTTP surface23<!-- Source: FelineStateMachine/lofi-node src/gate.ts; normative ticket semantics in the4 app-ticket contract. -->56The gate is the node's public face: it owns the public port, validates tickets, and proxies to the7loopback-only Jazz server. This page is the route-level reference for a **ticket-gated** node; in8`open` mode the same routes exist without the `/t/<secret>` prefix or scope checks.910## Routes1112| Route | Ticket | Behavior |13| ------------------------------------- | ----------- | ------------------------------------------------------------------------- |14| `GET /health` | none | Liveness; proxied to Jazz. `200`, or `502` when the store is unreachable. |15| `GET /t/<secret>/store-status` | any scope | Metadata-only schema preflight (below). |16| `/t/<secret>/apps/<appId>/ws` | any scope | WebSocket sync; re-originated toward Jazz with subprotocol forwarded. |17| `/t/<secret>/apps/<appId>/…` | any scope | Catalogue reads and other app routes, prefix-stripped and proxied. |18| `/t/<secret>/apps/<appId>/admin/…` | `provision` | Store administration; the node's admin secret is injected server-side. |19| `POST /t/<secret>/derive-sync-ticket` | `provision` | Mints a parent-linked, sync-scoped ticket (below). |2021The `<secret>` is a 43-character base64url path segment; the gate compares digests in constant time,22strips the prefix, preserves the query string, and forwards.2324## store-status2526```jsonc27// 20028{29 "v": 1,30 "appId": "…",31 "schema": {32 "deployed": true, // false → the object carries only { "deployed": false }33 "headHash": "ff85ac…", // newest stored schema hash (ordered by publishedAt)34 "permissionsHead": "0195…" // current permissions head object id, or null35 }36}3738// 502 — the node is up but its store is not39{ "error": "store_unavailable" }40```4142Metadata only — never schema contents, policies, or secrets. This endpoint exists so a sync-scoped43client can classify a store (most importantly `no_schema`, where writes would hang) without any44admin capability.4546## derive-sync-ticket4748```jsonc49// POST /t/<provision-secret>/derive-sync-ticket50// optional body: { "label": "phone (sync)" }5152// 20053{ "v": 1, "id": "<ticket id>", "ticket": "lofisync1.…" }54```5556The scope-down exchange: a provision-scoped secret mints a complete sync-scoped ticket linked to its57parent, so an app that enrolls one pasted provision ticket can persist transport-only credential58material and custody the provision original separately. The label defaults to59`<parent label> (sync)`. Sync-scoped and unknown secrets get the standard `401` (nothing to60enumerate); non-POST methods get `405`. Revoking the parent revokes every ticket derived from it —61live derived sockets close with `4001` like any revocation.6263## Errors and close codes6465| Signal | Meaning |66| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |67| `401 {"error":"invalid_ticket"}` | Unknown ticket, revoked ticket (directly or through its revoked parent), or a sync-scoped ticket on an admin route — deliberately indistinguishable. On WebSocket, the 401 **is** the upgrade response. |68| WS close `4001` (`ticket revoked`) | The ticket was revoked mid-session; live sockets close within a couple of seconds. The app treats the sink as dead and surfaces re-enrollment. |69| WS close `1011` | Upstream dial or pump failure inside the gate. |70| `502 {"error":"store_unavailable"}` | Gate up, Jazz (or the tunnel to the root) unreachable. |7172## Header handling7374Inbound `X-Jazz-Admin-Secret` headers are **stripped unconditionally** in ticket mode; for75provision-scoped requests on admin routes the gate injects its own. Hop-by-hop headers76(`connection`, `keep-alive`, `transfer-encoding`, `upgrade`, `host`, `content-length`) are managed77by the gate in both directions; everything else passes through.