Witbitz docs HomeTrustAll docs

Accounts and connections — the backup, the device, connected services

An account is a verified email — the one on a sign-in, never a field a page fills in. It holds what belongs to the person rather than to one room: the list of their rooms and its backup, their devices, the services they connect, and their Siri inbox. Signing in is on People.

Part of the SDK · every call: the reference.


Account backup and the recovery code

A person's rooms are in their links, and a lost device loses the links. wb.backup keeps them in the same encrypted account backup the Witbitz app uses, opened by a short recovery code:

javascript
const wb = new Witbitz({ backup: JSON.parse(localStorage.getItem('wb-backup') || 'null') })
wb.backup.onState = (s) => localStorage.setItem('wb-backup', JSON.stringify(s))

const { code } = await wb.backup.enable()   // e.g. 44YW-572I-… — show it, for the person to keep

// on a new device:
const { rooms } = await wb.backup.restore({ code })
// → [{ room, link, kind: 'space' | 'lane', label?, shared? }]: reopen with wb.openSpace(link) / wb.openLane(link, { space })

Once it is on, every room this client creates or opens is recorded.

One account on every device

The backup brings a person's rooms back on a new device. wb.account keeps the devices already running in step: a room made on the phone appears on the laptop, and archiving, renaming or forgetting a room reaches every device. It turns on with the backup and needs nothing else.

javascript
const wb = new Witbitz({ backup: saved, deviceName: 'Dana’s phone' })
wb.account.onChange = ({ rooms, devices }) => render(rooms)
await wb.account.sync()   // any time → { ok, rooms, devices }

wb.account.rooms                       // [{ room, link, kind, label?, shared?, archived? }], archived ones included
wb.account.archive(room)               // archive(room, false) undoes it
wb.account.rename(room, 'Kyoto trip')
wb.account.forget(room)                // off the account on every device; the room itself is not deleted

wb.account.devices                     // [{ id, name, seenAt, signedOut, current }]; wb.account.deviceId is this one
wb.account.signOut(id)                 // that device drops the account at its next sync
wb.account.onSignedOut = () => { localStorage.removeItem('wb-backup') }

What the account used

Every reply costs something, and in a shared room someone else may pay: the room's admin, your app, or Witbitz. wb.account.usage() answers what this account's turns cost in a month, across all its rooms, whoever paid:

javascript
const u = await wb.account.usage()   // this month (UTC); or usage({ month: '2026-10' })
u.replies; u.costUsd                 // the replies this account's turns got, and their cost
u.byPayer                            // { you, admin, app, witbitz }: micro-dollars each paid
u.byDay                              // { 23: 1234, … }: micro-dollars by day of the month

Protect what the app keeps on the device

Your app keeps things on the device: the sign-in session, the backup state, room snapshots and links (which carry room keys), and the Siri and A2A state (private keys). In localStorage, any infostealer that copies the browser profile can read them. wb.protect seals them under a key a platform passkey derives — Windows Hello, Touch ID, Android — and that key is nowhere on disk:

javascript
import { Witbitz, Protect } from 'https://witbitz-spaces.pages.dev/witbitz-sdk.js'

const protect = new Protect()   // first: the session and the backup are kept there
if (protect.state.locked) await protect.unlock()   // one Face ID / Touch ID / Windows Hello prompt
const wb = new Witbitz({ protect, session: await protect.load('wb:session'), backup: await protect.load('wb-backup') })

wb.backup.onState = (s) => wb.protect.save('wb-backup', s)
await wb.protect.save('wb:session', wb.session)
if (wb.protect.available() && !wb.protect.state.on) await wb.protect.enable({ label: 'My app' })

wb.protect.autoLock(5)                                   // lock after five minutes away
wb.protect.on('locked', () => showYourLockScreen())      // the SDK draws nothing

Connections

A connection is a credential the room's assistant can spend but never see: an API key, an OAuth grant, an MCP server. It belongs to the account. The Sentinel — an attested, always-on enclave — holds the secret; a room's assistant gets a tool that calls through it, and each call is a proposal a member approves — except an MCP tool granted to run on its own (auto).

javascript
const gh = await wb.connections.attach({ label: 'GitHub', host: 'api.github.com', secret: token })
await space.connections.grant(gh)   // this room's assistant may now propose connection_call
await wb.connections.list()         // [{ id, label, host }]: never a secret, never a callKey
await wb.connections.detach(gh.id, { spaces: [space] })   // everywhere at once, and out of each room you pass

MCP servers

The Spaces app's ⋯ → Connections → Other MCP server, for your page. The server's own sign-in runs inside the Sentinel, which registers an app with the server and keeps the PKCE verifier; your page gets a URL and a state, and the code it receives goes back to the Sentinel sealed. The server's token never reaches your page.

javascript
const st = await wb.connections.startMcp({ url: 'https://mcp.linear.app/mcp', label: 'Linear' })
let conn
if (st.needsSignIn) {
  location.assign(st.authUrl)   // the member consents; your callback page receives ?code=&state=
  conn = await wb.connections.finishMcp({ state, code, label: 'Linear', host: st.host })   // on the callback page
} else {
  conn = await wb.connections.attachMcp({ url: 'https://mcp.deepwiki.com/mcp', label: 'DeepWiki' })   // or { token }
}
await wb.connections.mcpTools(conn.id)   // { tools: [{ name, title, description, readOnly, … }], server }
await space.connections.grant(conn)      // its recommended tools: reads run on their own, changes ask
await space.connections.grant(conn, { tools: ['list_issues', 'create_issue'], auto: ['list_issues'] })   // or your choice

A new Sentinel build

When Witbitz moves the standing Sentinel to a new build, the SDK follows it: a build Witbitz publishes at /sentinel-claim.json, on its own site and checked against the attestation like the pinned one, is accepted.

The Siri inbox

"Hey Siri, Witbitz": a Shortcut posts the dictated text to an endpoint whose TLS ends inside the enclave; the words are sealed there to the room's inbox key, and a device that holds the key collects them.

javascript
const { key, endpoint, alias } = await space.siri.enable({ alias: 'groceries', makeDefault: true })
// show the member the Shortcut: Dictate Text → Get Contents of URL, POST `endpoint`, JSON { key, text }
const st = wb.siri.state   // persist it: without it, drops can never be opened
const { delivered, elsewhere } = await space.siri.collect()   // posts each drop as the member (🎙-marked)
await space.siri.collect({ deliver: async (text, meta) => myInbox.add(text, meta) })   // or take each one yourself

Machine-readable source: sdk-accounts.md · Generated 2026-10-27T07:05:42Z · build f301388c · every doc in one fetch: llms-full.txt (HTML)