Backbuild Sheets (spreadsheets) with cell comments, Backbuild Slides decks, the collaboration and realtime Yjs WebSocket transports, and offline sync.
GET /v1/bbsheet/{documentId}/export
Export a spreadsheet
Streams a freshly materialized export artifact (SQLite, XLSX, CSV, or TSV) for a spreadsheet the caller can read. Authentication uses a JWT in the `token` query parameter (self-authenticating, like the WebSocket endpoint, because a browser anchor download cannot easily set `Authorization` headers). The handler verifies the JWT, checks `token_version` revocation, runs the effective-access gate (requires `resolved_kind=spreadsheet` and `can_read`), then drives the grid Durable Object export op and streams the decoded bytes. The response includes a `Content-Disposition: attachment` header. Exports are never cached (`Cache-Control: no-store`) and the content is bounded by the DO's the configured export-cell limit limit.
Auth One-time ?token= ticket
Parameters
| Name | In | Type | Required | Description |
documentId | path | string<uuid> | yes | Spreadsheet document identifier (UUID). |
token | query | string | yes | A valid session JWT. |
format | query | string (enum) | yes | Export format. |
sheet_id | query | integer | no | Zero-based sheet (tab) index. Required by the DO for `csv`/`tsv`; optional for `sqlite`/`xlsx`. |
header_row | query | string (enum) | no | Whether to include a header row in the export (`true` or `false`). |
value_mode | query | string (enum) | no | Whether to export displayed (formatted) or raw cell values. Defaults to `displayed`. |
filename | query | string | no | Optional base filename for the downloaded file (max 200 chars). Sanitized server-side; the extension is appended automatically. |
Responses
| Status | Description |
200 | Export artifact streamed as a binary file download. |
400 | `VALIDATION_ERROR` (missing or invalid query parameters; export too large; unknown sheet), `INVALID_ID_FORMAT`. |
401 | `AUTH_TOKEN_INVALID` or `AUTH_TOKEN_EXPIRED`. |
403 | `PERMISSION_DENIED` (no read access or document is not a spreadsheet). |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
GET /v1/bbsheet/ws
Open BB Sheets grid WebSocket
Upgrades to a WebSocket connection to the `BbSheetGridDO` for a specific spreadsheet. Authentication uses the full session JWT passed as a `?token=` query parameter (not the `Authorization` header — the browser WebSocket API cannot set custom headers). Unlike `POST /v1/collaboration/ticket`, there is no intermediate ticket exchange; the JWT is verified directly. The handler checks `token_version` revocation, runs the spreadsheet effective-access gate (`resolved_kind` must be `spreadsheet`), and derives the socket capability (`read`/`write`) from `can_write`. The Durable Object key is `bbsheet:<orgId>:<documentId>`. The caller must send an `Upgrade: websocket` header.
Auth One-time ?token= ticket
Parameters
| Name | In | Type | Required | Description |
token | query | string | yes | A valid session JWT. Required because the browser WebSocket API cannot set `Authorization` headers. |
documentId | query | string<uuid> | yes | Spreadsheet document identifier (UUID). |
Responses
| Status | Description |
101 | WebSocket upgrade accepted. The connection is forwarded to the `BbSheetGridDO`. |
400 | `VALIDATION_ERROR` (missing or invalid query parameters) or 426 (missing `Upgrade: websocket` header). |
401 | `AUTH_TOKEN_INVALID` (JWT malformed or revoked) or `AUTH_TOKEN_EXPIRED` (JWT expired). |
403 | `PERMISSION_DENIED` (no read access, document is not a spreadsheet, or effective-access gate errored). |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
POST /v1/collaboration/ticket
Exchange a JWT for a single-use collaboration WebSocket ticket
Exchanges a valid session JWT (supplied in the standard `Authorization: Bearer` header) for a cryptographically random, single-use, short-lived (60 s) ticket stored in KV. The ticket is then passed as `?ticket=` on the `/v1/collaboration/ws` WebSocket upgrade. This two-step flow is required because the browser WebSocket API does not support custom headers during the HTTP upgrade handshake. Token-version revocation is checked before the ticket is issued.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
scope | string | no | Optional advisory scope string stored with the ticket. Not security-enforced; for client routing only. |
Responses
| Status | Description |
200 | Ticket issued. |
401 | `AUTH_REQUIRED` (missing Authorization header), `AUTH_TOKEN_INVALID` (JWT malformed, revoked, or missing required claims), `AUTH_TOKEN_EXPIRED` (JWT expired). |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
ticket | string | 64-character hex-encoded single-use WebSocket ticket. |
expiresInSeconds | integer | Ticket TTL in seconds (60). |
GET /v1/collaboration/ws
Open collaboration WebSocket
Upgrades to a WebSocket connection to the `CollaborationDO` for the caller's organization. The preferred authentication path uses a single-use `?ticket=` obtained from `POST /v1/collaboration/ticket`. A deprecated fallback accepts a direct JWT as `?token=` (a deprecation warning is logged; this path will be removed in a future release). The DO key is `collab:<orgId>`. The caller must send an `Upgrade: websocket` header.
Auth One-time ?token= ticket
Parameters
| Name | In | Type | Required | Description |
ticket | query | string | no | Single-use ticket obtained from `POST /v1/collaboration/ticket`. Preferred authentication path. |
token | query | string | no | **Deprecated.** Session JWT passed directly in the query string. Will be removed in a future release. |
Responses
| Status | Description |
101 | WebSocket upgrade accepted. The connection is forwarded to the `CollaborationDO`. |
400 | `VALIDATION_ERROR` (neither `ticket` nor `token` supplied, or the Upgrade header is missing). |
401 | `AUTH_TOKEN_INVALID` (ticket invalid/expired, JWT malformed/revoked), `AUTH_TOKEN_EXPIRED` (JWT expired). |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
GET /v1/preferences/offline-sync
Get offline sync preferences (client shortcut)
Returns a simplified offline sync preferences summary derived from `api.sync_preferences_get`. This is a client-facing shortcut that normalizes the user's sync settings into a flat object (`enabled`, `max_items`, `sync_on_reconnect`, `storage_limit_mb`). On any database error it returns safe defaults rather than an error response, ensuring the client always has a usable baseline.
Auth Session JWT (Bearer)
Responses
| Status | Description |
200 | Offline sync preferences summary (or safe defaults on error). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
200 response body: data fields
| Field | Type | Description |
enabled | boolean | Whether offline sync is enabled (`auto_sync`). |
max_items | integer | Maximum items to sync (currently always 500). |
sync_on_reconnect | boolean | Whether to sync automatically on reconnect (`auto_sync`). |
storage_limit_mb | integer | Local storage limit in MB (`max_local_size_mb`, default 50). |
POST /v1/projects/{projectId}/bbsheets
Create a BB Sheets spreadsheet
Atomically creates a spreadsheet catalog row and the backing `bb.bbsheet_documents` row via `api.bbsheet_create`, then grants the creating user WRITE access. After creation the spreadsheet is accessible via the grid WebSocket at `/v1/bbsheet/ws`. `org_id` is pinned from the session and is never accepted in the body. The body is strict — unknown keys are rejected with `VALIDATION_ERROR`. Requires the `documents` system feature flag.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
projectId | path | string<uuid> | yes | Project identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
name | string | yes | Spreadsheet display name (trimmed, 1–500 chars). |
fromTemplateId | string<uuid> | no | Optional template spreadsheet id to copy the initial grid state from. |
{
"name": "Q2 Budget Tracker"
}
Responses
| Status | Description |
201 | Spreadsheet created. The full document record is returned under `data.document`. |
400 | `INVALID_JSON` (unparseable body), `VALIDATION_ERROR` (a field failed schema validation; unknown keys are rejected by the strict schema), `INVALID_ID_FORMAT` (malformed projectId). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | `FORBIDDEN` (no `document.create` permission or project membership; enforced by the stored procedure). |
404 | `NOT_FOUND` (project not found or not visible to caller). |
409 | `CONFLICT` (duplicate spreadsheet in this project). |
500 | `DATABASE_ERROR` (unrecognized SP error) or `INTERNAL_ERROR` (unexpected server error). |
201 response body: data fields
| Field | Type | Description |
document | any | The created spreadsheet document record. |
POST /v1/projects/{projectId}/bbslides
Create a BB Slides deck
Atomically creates a presentation deck (catalog row with `content_type='application/x-bbslides'`), a backing `bb.bbprove_documents` row, and the owner WRITE grant via `api.bbslides_create`. The deck reuses the BB Docs editor and its CRDT transport lives at `/v1/yjs/ws`. `org_id` is pinned from the session and is never accepted in the body. The body is strict — unknown keys are rejected. Requires the `documents` system feature flag.
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
projectId | path | string<uuid> | yes | Project identifier (UUIDv7). |
Request Body
| Field | Type | Required | Description |
name | string | yes | Deck display name (trimmed, 1–500 chars). |
fromTemplateId | string<uuid> | no | Optional template deck id to copy the initial content from. |
{
"name": "Product Launch Deck"
}
Responses
| Status | Description |
201 | Deck created. The full document record is returned under `data.document`. |
400 | `INVALID_JSON`, `VALIDATION_ERROR` (body field failed or unknown key present), `INVALID_ID_FORMAT`. |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | `FORBIDDEN` (no `document.create` permission or project membership). |
404 | `NOT_FOUND` (project not found). |
409 | `CONFLICT` (duplicate deck in this project). |
500 | `DATABASE_ERROR` (unrecognized SP error) or `INTERNAL_ERROR`. |
201 response body: data fields
| Field | Type | Description |
document | any | |
GET /v1/sync/{resourceType}
Delta-sync pull for offline sync
Serves the offline sync engine's bulk download (omit `since`) and incremental changed-since pulls (`since` epoch-ms) with keyset pagination. Supports 6 resource types: `entities`, `relationships`, `documents`, `tasks`, `comments`, `specs`. Incremental items may carry `_deleted: true` tombstones so clients can evict rows. Response is camelCased to the client contract (`items`, `hasMore`, `nextCursor`, `watermark`).
Auth Session JWT (Bearer)
Parameters
| Name | In | Type | Required | Description |
resourceType | path | string (enum) | yes | The resource type to sync. Must be one of the 6 supported types; any other value is a 404. |
since | query | integer | no | Epoch milliseconds timestamp (non-negative integer up to 15 digits). When supplied, returns only records changed after this timestamp (incremental pull). Omit for a full bulk download. |
limit | query | integer | no | Maximum number of records to return per page (1–500, default 500). |
cursor | query | string | no | Keyset pagination cursor in the format `<epoch-microseconds>:<uuid>` (max 200 chars). |
project_ids | query | string | no | Comma-separated list of project UUIDs to filter by (max 50 IDs). |
Responses
| Status | Description |
200 | Sync page. |
400 | `VALIDATION_ERROR` (malformed `since`, `limit`, `cursor`, or `project_ids`). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | `FORBIDDEN` (offline sync is not enabled for this resource type in the org config). |
404 | `NOT_FOUND` (unknown resource type — not in the 6-type allowlist). |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
items | array of object | Records in this sync page. May include `_deleted: true` tombstones for deleted records. |
hasMore | boolean | Whether more records exist beyond this page. |
nextCursor | string | Keyset cursor for the next page in the format `<epoch-microseconds>:<uuid>`. Absent when `hasMore` is false. |
watermark | any | Server-side high-water mark for use as `since` on the next incremental pull. |
GET /v1/sync/config
Get organization offline sync configuration
Returns the organization's offline sync configuration, which determines which entity types are allowed for offline access. If no configuration exists, returns a default with an empty allowed-types list. Organization and RLS scope are derived from the access token.
Auth Session JWT (Bearer)
Responses
| Status | Description |
200 | The organization sync configuration (or default). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
org_id | string<uuid> | Organization identifier. |
allowed_sync_types | array of enum entitiesrelationshipsdocumentstaskscommentsspecs | Resource types the organization permits for offline sync. |
max_offline_size_mb | integer | Maximum total offline storage permitted (MB, 10–10000). |
sync_interval_seconds | integer | Minimum sync interval in seconds (30–86400). |
metadata | object | Optional bounded metadata object. |
PUT /v1/sync/config
Update organization offline sync configuration
Replaces the organization's offline sync configuration. Requires the `org.update` permission. Updates which entity types are permitted for offline sync and optional storage/interval caps. Per-org policy is enforced inside the stored procedures via `bb.org_sync_configs`.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
allowed_sync_types | array of enum entitiesrelationshipsdocumentstaskscommentsspecs | yes | Resource types to allow for offline sync (0–20 items). |
max_offline_size_mb | integer | no | Optional maximum total offline storage (MB, 10–10000). |
sync_interval_seconds | integer | no | Optional minimum sync interval (seconds, 30–86400). |
metadata | object | no | Optional bounded metadata object. |
Responses
| Status | Description |
200 | Updated sync configuration. |
400 | `INVALID_JSON` or `VALIDATION_ERROR` (invalid field value, out-of-range limit, or disallowed sync type). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
403 | `PERMISSION_DENIED` (missing `org.update` permission). |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
org_id | string<uuid> | Organization identifier. |
allowed_sync_types | array of enum entitiesrelationshipsdocumentstaskscommentsspecs | Resource types the organization permits for offline sync. |
max_offline_size_mb | integer | Maximum total offline storage permitted (MB, 10–10000). |
sync_interval_seconds | integer | Minimum sync interval in seconds (30–86400). |
metadata | object | Optional bounded metadata object. |
GET /v1/sync/preferences
Get user offline sync preferences
Returns the authenticated user's offline sync preferences within their current organization. If no preferences exist, returns defaults (empty enabled types, `auto_sync: true`, `sync_on_wifi_only: false`, `max_local_size_mb: 500`).
Auth Session JWT (Bearer)
Responses
| Status | Description |
200 | The user's sync preferences (or defaults). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
user_id | string<uuid> | User identifier. |
org_id | string<uuid> | Organization identifier. |
enabled_sync_types | array of enum entitiesrelationshipsdocumentstaskscommentsspecs | Resource types the user has opted into syncing (must be a subset of the org's `allowed_sync_types`). |
auto_sync | boolean | Whether automatic background sync is enabled. |
sync_on_wifi_only | boolean | Whether to sync only on Wi-Fi connections. |
max_local_size_mb | integer | Maximum local storage the user allows for offline data (MB, 10–5000). |
metadata | object | Optional bounded metadata object. |
PUT /v1/sync/preferences
Update user offline sync preferences
Replaces the authenticated user's offline sync preferences. The requested `enabled_sync_types` must be a subset of the organization's `allowed_sync_types`; types not permitted by the org config are rejected with `VALIDATION_ERROR`.
Auth Session JWT (Bearer)
Request Body
| Field | Type | Required | Description |
enabled_sync_types | array of enum entitiesrelationshipsdocumentstaskscommentsspecs | yes | Resource types to opt into syncing (must be allowed by the org config; 0–20 items). |
auto_sync | boolean | no | Whether to enable automatic background sync. |
sync_on_wifi_only | boolean | no | Whether to restrict sync to Wi-Fi connections only. |
max_local_size_mb | integer | no | Maximum local storage to allow for offline data (MB, 10–5000). |
metadata | object | no | Optional bounded metadata object. |
Responses
| Status | Description |
200 | Updated sync preferences. |
400 | `INVALID_JSON`, `VALIDATION_ERROR` (field validation failure, or requested types not allowed by org config). |
401 | 401 Unauthorized — missing, expired, malformed, or revoked credentials. |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |
200 response body: data fields
| Field | Type | Description |
user_id | string<uuid> | User identifier. |
org_id | string<uuid> | Organization identifier. |
enabled_sync_types | array of enum entitiesrelationshipsdocumentstaskscommentsspecs | Resource types the user has opted into syncing (must be a subset of the org's `allowed_sync_types`). |
auto_sync | boolean | Whether automatic background sync is enabled. |
sync_on_wifi_only | boolean | Whether to sync only on Wi-Fi connections. |
max_local_size_mb | integer | Maximum local storage the user allows for offline data (MB, 10–5000). |
metadata | object | Optional bounded metadata object. |
GET /v1/yjs/ws
Open Yjs CRDT document WebSocket
Upgrades to a WebSocket connection to the `YjsDocDO` for a specific document. Used by BB Docs, BB Slides, and entity collaborative editing. Authentication is performed via the JWT in the `token` query parameter. The handler verifies the JWT, checks `token_version` revocation, and runs the `api.yjs_document_effective_access` gate to resolve the server-authoritative document kind (`bbprove_document` for BB Docs / BB Slides, or `entity`). The client-supplied `?docKind=` is intentionally ignored; the kind is resolved server-side to prevent doc-kind spoofing attacks. The derived capability (`read`/`write`) and access level are forwarded to the DO; the DO re-verifies on join. The DO key is `yjs:<orgId>:<documentId>`.
Auth One-time ?token= ticket
Parameters
| Name | In | Type | Required | Description |
token | query | string | yes | A valid session JWT. |
documentId | query | string<uuid> | yes | Document identifier (UUID). Used by both `bbprove_document` and `entity` kinds. |
docKind | query | string (enum) | no | **Ignored by the server.** Accepted for backward-compatibility with existing clients but never trusted; the server resolves the document kind from `documentId`. |
Responses
| Status | Description |
101 | WebSocket upgrade accepted. The connection is forwarded to the `YjsDocDO`. |
400 | `VALIDATION_ERROR` (missing or invalid query parameters, or missing `Upgrade: websocket` header). |
401 | `AUTH_TOKEN_INVALID` (JWT malformed or revoked), `AUTH_TOKEN_EXPIRED` (JWT expired). |
403 | `PERMISSION_DENIED` (no read access, document not found under caller org, or effective-access gate errored fail-closed). |
500 | 500 Internal Server Error — an unexpected server-side error. The message is sanitized; the correlation id is logged server-side. |