WebSockets

The Sixb API streams domain changes, run logs, and agent output over WebSockets. Use the Client SDK for typed subscriptions in your app, or the protocol below when building another client.

Connect

Connect to the API origin using wss:// in production. Connections use an authenticated browser session from an allowed browser origin, or a signed-in native client's access token in an Authorization: Bearer header. Personal access tokens and service-account tokens cannot authenticate WebSockets.

A shared link's session opens /ws/artifacts and /ws/events only, naming its grant in the URL: ?shareGrant=<grantId>. It watches the artifacts the link reaches and receives their events, nothing else. Once the link is revoked or its session ends, the server closes the socket with code 4401 within seconds; reconnecting is refused. The Client SDK does this for you.

EndpointStreamsAccess
/ws/eventsDomain events.Events are filtered by the caller's resource permissions.
/ws/logsHandler logs.Requires can.observe(project.logs).
/ws/agentsAgent run output and activity.Requires access to the agent and requested run.
/ws/artifactsArtifacts edited live.Changing an artifact edits it; reading it watches.

After receiving connected, send a subscription message (/ws/artifacts has its own messages, below). The server acknowledges it with subscribed. Send unsubscribe or close the socket when finished. Errors arrive as a message with type: "error" and a message string. A message you send can be at most 64 KiB; the server closes a connection that sends a larger one.

Session identity is established when connecting. WebSocket traffic does not renew the browser session; reconnect after signing in again or changing accounts.

Domain events

Subscribe to a topic, event types, or a specific object:

JSON
{
  "type": "subscribe",
  "topic": "objects",
  "types": ["object.created", "object.updated"],
  "objectTypeId": "Invoice",
  "limit": 100
}

Other optional filters are primaryId and runId. limit is the page size used to replay from a cursor, up to 500, rather than a total event limit.

Each event arrives in an event frame:

JSON
{
  "type": "event",
  "event": {
    "id": "event-1",
    "type": "object.updated",
    "topic": "objects",
    "cursor": "opaque-cursor",
    "occurredAt": "2026-09-22T12:00:00.000Z"
  }
}

This abbreviated example shows common fields. The event also carries its subject, payload, and project context. The SixbEvent type from @sixb/client describes the complete event union.

TopicExample events
objectsobject.created, object.updated, object.deleted
linkslink.created, link.updated, link.deleted
telemetrytelemetry.appended
workflowsworkflow.run.started, workflow.run.finished
rulesrule.triggered, rule.resolved
schedulesschedule.triggered
datasetsdataset.version.committed
syncs, pipelinesRun lifecycle events.
usersuser.joined, user.invited, user.suspended, user.reactivated, user.profile.updated
artifactsartifact.updated, artifact.commented

Visibility follows the subject's permissions. For example, invoice events require view access to invoices, and link events require view access to both endpoint types. Member events require can.view(project.users), like the member directory. Artifact events go to whoever can read the artifact. There is no separate grant to read all domain events.

Replay events

Without a cursor, a new event connection starts with live changes. To resume, send the last received cursor as afterCursor in the subscription:

JSON
{
  "type": "subscribe",
  "topic": "objects",
  "afterCursor": "last-received-cursor"
}

Treat cursors as opaque. Events have bounded retention: when a cursor has expired, the server sends an error frame and continues with live events, so refetch current state. Tolerate repeated events by their ID. For paginated history over HTTP, use GET /api/events in your API's /docs reference.

Run logs

Subscribe to a run, kinds, or exact levels. Omit filters to receive all logs you can observe:

JSON
{
  "type": "subscribe",
  "run": { "kind": "action", "id": "run-1" },
  "levels": ["warn", "error"]
}

Logs arrive in logs frames containing a logs array. Each line includes its message, level, fields, run context, and cursor. Supply afterCursor to resume after a previous log. Without it, the subscription starts with new lines.

If the cursor has expired, the server sends a reset frame with reason cursor_expired. Refetch retained logs before continuing with live output.

Use Logging to configure capture or the logs builder to read retained lines.

Agent runs

Subscribe to one run:

JSON
{
  "type": "subscribe",
  "runId": "run-1"
}

The server sends retained output followed by live record frames, plus a run.snapshot frame with the current run. Supply afterCursor to resume. If retained output no longer covers that cursor, the server resumes from the earliest available output and acknowledges a null cursor.

A sub-agent's run streams the same way to the owner and participants of its conversation, its run.snapshot with kind: "subagent" and the parentRunId of the turn it works under. Observers poll instead.

Send replay with a run ID, optional cursor, and limit to read a bounded page. subscribe.activity selects project agent activity instead of an individual run. A socket has one active subscription.

React apps can use useAgentRunStream from @sixb/client/hooks instead of handling these messages directly.

Live artifacts

/ws/artifacts keeps a canvas's current version in step between everyone editing it. Each connection holds the version as a Yjs document and exchanges y-protocols sync and awareness messages, base64-encoded.

You send
{ "type": "join", "artifactId" }Start editing, or watching, an artifact. A connection holds at most 16
{ "type": "sync", "artifactId", "data", "more"? }A sync message: step 1 after joined, then your document's updates. A larger one goes in parts of at most 32 KiB, more: true on all but the last
{ "type": "awareness", "artifactId", "data" }Who you are and what you point at
{ "type": "leave", "artifactId" }Stop
You receive
joinedaccess (edit or watch), revision, and user, who you are to the others
sync, awarenessApply them to your document and your awareness
statusWhether changes are written: saved at revision, unsaved, saving, or failed with an error; snapshot is what is written, as a Yjs snapshot, so you can forget the changes it covers
accessYou may now only watch
leftYou no longer hold it: left (asked), not_found, not_shared, not_live
  • A watcher's updates are refused with an error; it may still send step 1 and awareness.
  • Keep your document when you reconnect, to any server. After joined, the server's own step 1 asks for what your document holds that it lacks, and your answer brings back what you changed while away.
  • The server sets user in every awareness state it shares (id, name and color), whatever your own state says. It shares your states only for awareness clients nobody else uses. name is the person's profile name, never their email: it is empty for someone who gave none, whom the app shows as a team member. A shared link's guest has guest: true and the name they gave.
  • While an Agent turn changes boards, the server's own awareness state holds sixbAgent: each turn's runId and the boards it changed. A client's state never holds it.

Search docs

Search the documentation