1. What Sync is
Sync exposes four object kinds, all addressed by aunique_name you choose
(letters, digits, ., _, :, and -, up to 256 characters) and all
scoped to your tenant — one tenant can never read or address another
tenant’s objects:
The durable kinds persist until you delete them (or a
ttl you set
expires). Every REST write publishes a change event to a per-object channel;
the WebSocket gateway relays those events to subscribed clients, and clients
re-hydrate current state through the REST GET endpoints after a
(re)connect. Objects live in a low-latency store chosen for fast, frequent
mutation — Sync is for coordination state, not the system of record. Keep
authoritative data in your own database and use Sync for the shared,
changing view of it.
2. Sync vs. Inbox, Notifications, and Webhooks
Orbit has several real-time-ish surfaces. Pick Sync when the shape is shared state, not notification:- Inbox — a durable alert feed (“case 123 needs a review”) where the item itself is the durable artifact. Sync documents expire or get overwritten; inbox items accumulate.
- Notifications — point-in-time alerts to an operator (“threshold crossed”). If the event is the message, that is a notification, not shared state.
- Webhooks — server-to-server push to your backend. Use Sync when the consumers are client applications (browser, mobile, desktop) that you do not want polling.
3. REST walkthrough
Every request carries anX-API-Key header (or a session Bearer token).
Reads are open to any authenticated member; writes require an operator role
(owner, admin, developer, or agent).
Documents
Create a document (201 on success; a duplicateunique_name returns 409):
data and revision:
document.removed event:
Maps
A Map is an unordered key → JSON-value collection. Set an item (create-or-replace), then list the map:revision. DELETE a single item or the whole
Map (204 either way, with map.item.removed / map.removed events).
Lists
A List is append-indexed. Appending returns the new item’sindex
(zero-based):
PUT …/items/:index), remove it
(DELETE …/items/:index), or delete the whole List (DELETE …/:name). All
emit the corresponding list.item.* / list.removed events.
Streams
A Stream is ephemeral: publish a message and every client subscribed to the stream at that moment receives it. Nothing is stored; a client that connects after the publish does not see it. Useful for live counters and presence aggregates you do not want to persist:4. WebSocket gateway
Point your client at the gateway and authenticate, then manage per-object subscriptions over the socket:Bearer.<jwt> for a session token or ApiKey.<key> for an
API key. The server scans the offered list for the auth token. Never embed
a live API key in code shipped to end users; mint scoped session tokens on
your backend.
{ "type": "hello", "tenantId", ... }.
Subscribe and unsubscribe with JSON frames:
type is one of document.updated, document.removed,
map.item.set, map.item.removed, map.removed, list.item.added,
list.item.updated, list.item.removed, list.removed, or
stream.message. revision is absent for streams; key appears on map
events; index on list events; data is absent on removal events. A
client ping frame gets a pong; malformed frames get an error frame.
Liveness and limits. The server pings every 30 seconds and closes a
socket after 90 seconds without activity (about three missed pongs).
Replicate the client-side close logic: on close, re-hydrate state with a
REST GET and re-subscribe. Two cluster-wide caps apply per tenant: at
most 50 simultaneous sockets, and at most 200 subscribed objects per
socket. If you exceed either, the server returns an error frame
(too_many_connections / too_many_subscriptions) and closes the socket.
5. End-to-end example: chat-room presence + a live viewer counter
A common pairing: a durable Map for the per-user presence roster (survives reconnects) and an ephemeral Stream for the “current viewer count” badge (no persistence means no cleanup). The client below runs in Node or a browser — keep the key out of shipped front-end code; in the browser variant use the subprotocol auth from Section 4.6. TTL and retention semantics
ttl(seconds, integer, max one year) means the object’s data expires after that many seconds.ttl: 0(the default) means no expiry — the object persists until youDELETEit.- On a Document and a Map you supply
ttlper object/write; resetting it sets a fresh expiry window. Once the window elapses the object vanishes: reads returnSYNC_OBJECT_NOT_FOUND, and subscriptions on it stop receiving updates. - Use a short
ttlon anything whose absence should clean up automatically (presence entries are the canonical case). Usettl: 0for state you manage explicitly, and delete it when the workflow ends. - A Stream has no retention at all — non-subscribers at publish time never see the message.
7. Error taxonomy
Sync surfaces the same error envelope as the rest of the API (error.code / error.message / error.status / optional details). The
relevant codes:
The per-family limit is 120 requests/minute per operation family
(reads vs. writes) — the details of that limiter live in the
rate-limits guide.
8. Permissions
Tenant scoping is absolute: a client only ever addresses objects inside its own tenant, and the WebSocket gateway binds subscriptions to the authenticated tenant’s scope.- Reads (
GETon any object family): any authenticated member of the organization. - Writes (create / update / delete / publish): the
owner,admin,developer, oragentrole. The dashboard’smemberrole can read but not write; mint scoped server-side keys for the writers your app embeds.
See also
- Sync API reference — endpoint-by-endpoint shapes
- Endpoint reference (auto-generated)
- Error handling by example — the envelope and retry logic
- Rate limits — headers and backoff