WebSocket events
The WebSocket stream at wss://<host>/v1/stream?token=<jwt> carries
every real-time event the caller can see. JSON-over-WS; each frame
is one event with a type discriminator.
See Subscribe to WebSocket events for connection lifecycle.
Common envelope#
Every event has:
{
"type": "swp.room.message",
"id": "evt-…", // unique event id (for swp.* types)
"room_id": "room-…", // for room-scoped events
"tenant_id": "abc-…",
"ts": "2026-05-17T11:00:00Z"
}
Plus type-specific fields below.
Hello#
Sent once by the server after a successful connection.
1 | |
If you don't see this within ~2 seconds of upgrade, the connection failed (likely token).
Room message events#
swp.room.message#
1 2 3 4 5 6 7 8 9 | |
swp.room.message.chunk#
Streaming chunk during AI generation. Multiple chunks per response;
the final one has content.done=true.
1 2 3 4 5 6 | |
swp.room.redaction#
1 2 3 4 5 6 7 | |
swp.room.reaction#
1 2 3 4 5 6 7 8 | |
swp.room.member#
{
"type": "swp.room.member",
"room_id": "room-…",
"participant_id": "5e4d…",
"membership": "join", // join | leave | ban | invite | knock
"changed_by": "5e4d…"
}
swp.room.name / swp.room.topic#
1 2 3 4 5 | |
swp.room.read_marker#
Fires when you update your read marker (cross-device sync). Doesn't broadcast other people's read markers.
1 2 3 4 5 6 | |
swp.room.ai_engagement#
1 2 3 4 5 6 | |
AI events#
swp.ai.status#
{
"type": "swp.ai.status",
"room_id": "room-…",
"participant_id": "ai-…",
"status": "calling web_search" // null when idle
}
swp.ai.context_usage#
1 2 3 4 5 6 | |
swp.plan.updated#
Full plan snapshot whenever the plan transitions or a step changes.
{
"type": "swp.plan.updated",
"room_id": "room-…",
"content": {
"plan": { /* full Plan dict — see Sidekicks and plans API */ }
}
}
Sidekicks#
swp.sidekick.spawned#
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
swp.sidekick.result#
1 2 3 4 5 6 7 8 9 | |
swp.sidekick.cancel#
1 2 3 4 5 6 7 8 9 10 | |
Presence and typing#
typing#
1 2 3 4 5 6 | |
Auto-clears after 4 seconds without a refresh.
presence#
{
"type": "presence",
"participant_id": "5e4d…",
"status": "online" // online | idle | busy | appear_offline | offline
}
Not scoped to a room — fires across the whole tenant for whichever participants you have visibility into.
Calls#
swp.call.invite#
1 2 3 4 5 6 7 8 | |
swp.call.state#
State transitions: someone joined, left, muted, started screen share, recording started/stopped.
1 2 3 4 5 6 | |
Federation#
swp.federation.event#
Opaque wrapper for federation-state changes that don't fit other categories (peer first-seen, key rotation observed, …).
Bridges#
swp.bridge.message#
Foreign message arrived from a bridge. Looks like a regular
swp.room.message but with content._imported_sender set.
swp.bridge.status#
{
"type": "swp.bridge.status",
"bridge_id": "...",
"state": "active" // active | paused | error
}
Misc#
swp.model.error#
When AI generation fails (e.g. ScaiGrid error). Routed to the room so it appears in the timeline.
1 2 3 4 5 6 | |
Reconnect#
Server doesn't replay events on reconnect. Use /v1/sync?since=...
to bridge the gap.