Prototyper UI
Design Bridge

API Reference

REST API and WebSocket protocol reference for the Design Bridge server.

The bridge server exposes a REST API for session management and a WebSocket endpoint for real-time Yjs CRDT sync. Default base URL: http://localhost:4321.

Endpoints Overview

MethodPathDescription
GET/healthHealth check
POST/api/sessionsCreate a session
GET/api/sessionsList all sessions
GET/api/sessions/:idGet session metadata
GET/api/sessions/:id/stateGet full session state
POST/api/sessions/:id/opsApply spec operations
POST/api/sessions/:id/presenceUpdate agent presence
DELETE/api/sessions/:idDelete a session
WS/wsYjs WebSocket sync

All JSON endpoints return Content-Type: application/json and include CORS headers (Access-Control-Allow-Origin: *).


GET /health

Health check to verify the server is running.

Response:

{ "ok": true }

curl:

curl http://localhost:4321/health

POST /api/sessions

Create a new design session. Initializes a Yjs document with the provided spec and theme.

Request body:

FieldTypeRequiredDescription
namestringNoSession name (default: "Untitled")
descriptionstringNoHuman-readable description
specSpecNoInitial Compose spec
themeThemeParamsNoInitial theme parameters

Response (201):

{
  "ok": true,
  "session": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Login Page",
    "description": "A login form",
    "revision": 0,
    "createdAt": "2026-03-25T10:00:00.000Z",
    "updatedAt": "2026-03-25T10:00:00.000Z"
  }
}

curl:

curl -X POST http://localhost:4321/api/sessions \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Login Page",
    "description": "A login form with email and password",
    "spec": {
      "root": "container",
      "elements": {
        "container": {
          "type": "Card",
          "props": { "className": "p-6" },
          "children": ["heading"]
        },
        "heading": {
          "type": "Heading",
          "props": { "level": 2, "children": "Login" }
        }
      }
    }
  }'

GET /api/sessions

List all active sessions.

Response (200):

{
  "ok": true,
  "sessions": [
    {
      "id": "a1b2c3d4-...",
      "name": "Login Page",
      "description": "A login form",
      "revision": 5,
      "createdAt": "2026-03-25T10:00:00.000Z",
      "updatedAt": "2026-03-25T10:05:00.000Z"
    }
  ]
}

curl:

curl http://localhost:4321/api/sessions

GET /api/sessions/:id

Get metadata for a single session.

Response (200):

{
  "ok": true,
  "session": {
    "id": "a1b2c3d4-...",
    "name": "Login Page",
    "description": "A login form",
    "revision": 5,
    "createdAt": "2026-03-25T10:00:00.000Z",
    "updatedAt": "2026-03-25T10:05:00.000Z"
  }
}

Response (404):

{ "error": "Session not found" }

curl:

curl http://localhost:4321/api/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890

GET /api/sessions/:id/state

Get the full session state including spec, theme, theme CSS, and metadata.

Response (200):

{
  "ok": true,
  "spec": {
    "root": "container",
    "elements": {
      "container": {
        "type": "Card",
        "props": { "className": "p-6" },
        "children": ["heading", "form"]
      }
    }
  },
  "theme": {
    "hue": 264,
    "chroma": 0.24,
    "grayChroma": 0.01,
    "radius": 0.625
  },
  "themeCSS": ":root { --primary: 39.11% 0.084 264; ... }",
  "meta": {
    "id": "a1b2c3d4-...",
    "name": "Login Page",
    "description": "A login form",
    "revision": 5,
    "createdAt": "2026-03-25T10:00:00.000Z",
    "updatedAt": "2026-03-25T10:05:00.000Z"
  }
}

curl:

curl http://localhost:4321/api/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/state

POST /api/sessions/:id/ops

Apply spec operations to a session. Operations are applied to the Yjs document and synced to all connected browsers immediately.

Request body:

FieldTypeRequiredDescription
opsSpecOp[]YesArray of spec operations
agentIdstringNoAgent identifier for audit

SpecOp Types

type SpecOp =
  | { op: "add_element"; key: string; element: UIElement; parentKey: string; index?: number }
  | { op: "remove_element"; key: string }
  | { op: "update_props"; key: string; props: Record<string, unknown> }
  | { op: "set_prop"; key: string; prop: string; value: unknown }
  | { op: "remove_prop"; key: string; prop: string }
  | { op: "reorder_children"; key: string; children: string[] }
  | { op: "set_root"; root: string }
  | { op: "replace_spec"; spec: Spec }
  | { op: "set_theme"; theme: Partial<ThemeParams> }
  | { op: "set_theme_css"; css: string }

Response (200):

{
  "ok": true,
  "revision": 6,
  "applied": 3,
  "errors": []
}

curl:

curl -X POST http://localhost:4321/api/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/ops \
  -H "Content-Type: application/json" \
  -d '{
    "ops": [
      {
        "op": "add_element",
        "key": "submitBtn",
        "parentKey": "form",
        "element": {
          "type": "Button",
          "props": { "children": "Submit", "className": "w-full" }
        }
      },
      {
        "op": "set_prop",
        "key": "heading",
        "prop": "children",
        "value": "Welcome Back"
      }
    ],
    "agentId": "claude"
  }'

POST /api/sessions/:id/presence

Update agent presence for a session. Presence is stored in memory and visible to connected browsers.

Request body:

FieldTypeRequiredDescription
agentIdstringYesUnique agent identifier
namestringNoDisplay name
cursorobjectNo{ elementKey: string } — cursor target
statusstringNoAgent status (e.g. "working", "idle")

Response (200):

{
  "ok": true,
  "presence": {
    "claude": {
      "agentId": "claude",
      "status": "working",
      "updatedAt": "2026-03-25T10:05:00.000Z"
    }
  }
}

curl:

curl -X POST http://localhost:4321/api/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890/presence \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "claude",
    "status": "working",
    "name": "Claude"
  }'

DELETE /api/sessions/:id

Delete a session and its persisted Yjs document. Clears presence data.

Response (200):

{ "ok": true }

Response (404):

{ "error": "Session not found" }

curl:

curl -X DELETE http://localhost:4321/api/sessions/a1b2c3d4-e5f6-7890-abcd-ef1234567890

WebSocket: /ws

The /ws endpoint accepts WebSocket connections for Yjs CRDT sync via the Hocuspocus protocol.

Connection

Connect with a Yjs-compatible WebSocket provider. The session ID is the Hocuspocus document name:

import { HocuspocusProvider } from "@hocuspocus/provider";
import * as Y from "yjs";

const doc = new Y.Doc();
const provider = new HocuspocusProvider({
  url: "ws://localhost:4321/ws",
  name: sessionId, // the session ID is the document name
  document: doc,
});

Document Structure

The Yjs document contains these top-level maps:

MapTypeDescription
specY.MapCompose spec with nested elements
themeY.MapTheme parameters
themeCSSY.TextGenerated CSS token string
metaY.MapSession metadata (id, name, revision)

Reading the Spec

const specMap = doc.getMap("spec");
const root = specMap.get("root") as string;
const elements = specMap.get("elements") as Y.Map<Y.Map<unknown>>;

// Observe changes
specMap.observeDeep((events) => {
  // Re-render when spec changes
  const spec = yDocToSpec(doc);
  renderPreview(spec);
});

Awareness Protocol

Presence and cursors use the Yjs Awareness protocol, carried over the same WebSocket connection:

provider.awareness.setLocalState({
  agentId: "claude",
  status: "working",
  color: "#7c3aed",
});

provider.awareness.on("change", () => {
  const states = provider.awareness.getStates();
  // Render presence indicators
});

Type Reference

Spec

interface Spec {
  root: string;
  elements: Record<string, UIElement>;
  state?: Record<string, unknown>;
}

UIElement

interface UIElement {
  type: string;
  props: Record<string, unknown>;
  children?: string[];
}

ThemeParams

interface ThemeParams {
  hue: number;        // 0-360
  chroma: number;     // 0-0.4
  grayChroma: number; // 0-0.03
  radius: number;     // px
  font?: string;
}

SessionMeta

interface SessionMeta {
  id: string;
  name: string;
  description: string;
  revision: number;
  createdAt: string;  // ISO 8601
  updatedAt: string;  // ISO 8601
}

On this page