Docs Services Sign In
Building

API & the SDK

Every table and feature is reachable over a REST API under /api/*. From the frontend you use bm, a small typed client that is regenerated from your schema on every push — so bm.table('typo') is a TypeScript error, not a runtime surprise.

The typed client

Import the SDK and your generated types from 'bm'. The types come from src/bm.d.ts, which is rewritten automatically whenever you push a change to schema.prisma, app.yaml, flows.yaml, workflows.yaml, hooks.yaml, or your i18n files. Never edit it by hand.

static/app.tsx
import bm, { type User, type Contact } from 'bm';

const me: User | null = await bm.auth.me();
if (!me) { location.href = '/login.html'; return; }

// Strongly typed CRUD — list, get, create, update, delete.
const contacts: Contact[] = await bm.table('contacts').list({ limit: 50 });
const created = await bm.table('contacts').create({ name: 'Ada', email: '[email protected]' });
await bm.table('contacts').update(created.id, { status: 'won' });
await bm.table('contacts').delete(created.id);

What's on bm

SurfaceWhat it does
bm.table(name)Typed CRUD for a table: list, get, create, update, delete, count; restore on soft-delete tables; list({q}) on full-text tables; versions / revertTo on versioned tables.
bm.authme, signIn, signUp, signOut, refreshMe — session lifecycle.
bm.live(table, cb)Subscribe to real-time changes for a table over SSE.
bm.room(name)WebSocket room: broadcast, presence, peer-to-peer signaling.
bm.flows.<name>()Call a custom route from flows.yaml, typed from its inputs.
bm.workflow(table, id)transitionTo, available, current for state machines.
bm.notificationsIn-app inbox: list, markRead, markAllRead, onNew.
bm.upload(file)Multipart file upload; returns a stored URL.
bm.jobsstatus / wait — poll async-flow jobs.
bm.permissionsPer-row sharing: share, revoke, list.
bm.api.{get,post,patch,delete}Raw escape hatch — auto-CSRF and auto-JSON for any endpoint.

The raw API

You don't have to use the SDK — any HTTP client works. The contract is plain REST and JSON. The one rule for cookie-authenticated requests is CSRF: send the token from the auto-injected <meta name="csrf-token"> tag as an X-CSRF-Token header on every mutation. (Bearer-token clients are exempt — see Auth & sessions.)

Raw fetch
const csrf = document.querySelector('meta[name="csrf-token"]').content;

await fetch('/api/contacts', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf },
  credentials: 'same-origin',
  body: JSON.stringify({ name: 'Ada', email: '[email protected]' }),
});

Errors

Every error is JSON: { error, message?, fields?, hint? }. Switch on the machine-readable error code and show message to the user. Common codes: 401 authentication required, 403 invalid CSRF token, 404 not found, 409 row changed since last read, 422 validation failed (with a fields map), 429 rate limited.

Next: Frontend for how TSX is compiled and served, or Real-time for bm.live and bm.room.