Skip to content

WebSocket events

Every client holds one WebSocket at GET /api/ws (wss://api.novaterra.world/api/ws in production), authenticated by the same session cookie as the REST API. This is how the desktop tells you what you need to know at the moment you need to know it: widgets subscribe by topic and the server pushes typed events.

Both directions are discriminated unions on type, so a client can switch exhaustively:

import { ServerEvent } from '@novaterra/contracts';
socket.addEventListener('message', (raw) => {
const event = ServerEvent.parse(JSON.parse(raw.data));
if (event.type === 'signal.created') showSignal(event.signal);
});

Server → client

Pushed by the API. Anything that changes while you are looking at the world arrives here.

typePayload
hellobeingId: Id
serverTime: ISODate
pongserverTime: ISODate
signal.createdsignal: Signal
signal.updatedsignal: Signal
message.deltathreadId: Id
messageId: Id
delta: string
message.createdmessage: Message
message.toolthreadId: Id
messageId: Id
toolCall: Json
task.updatedtask: Task
project.updatedproject: Project
traceevent: TraceEvent
memory.createdmemory: Memory
widget.suggestwidgetType: string
reason: string
config?: Json
presence.updatebeingId: Id
status: 'online' | 'away' | 'offline'
doing?: string
wallet.updatedwallet: Wallet
twin.activitykind: string
summary: string
at: ISODate
square.postpost: Message
room.updatedthreadId: Id
meetingId: Id | null
floorBeingId: Id | null
presentBeingIds: Id[]
session: Json | null
ambientmood: string
hint?: string
muse.floorthreadId: Id
messageId: Id
country: string | null
line: { name: string; number: string } | null
directoryUrl: string
doc.syncdocId: Id
update: string (max 1398104, regex)
stateVector: string (max 1398104, regex)
doc.updatedocId: Id
update: string (max 1398104, regex)
doc.awarenessdocId: Id
update: string (max 1398104, regex)
doc.presencedocId: Id
present: { beingId, tabs }[]
doc.refuseddocId: Id
reason: 'not_found' | 'too_large' | 'malformed'

Client → server

Sent by the browser to declare interest and presence.

typePayload
subscribetopics: string[]
presencestatus: 'online' | 'away'
doing?: string
ping—
doc.joindocId: Id
stateVector: string (max 1398104, regex)
doc.updatedocId: Id
update: string (max 1398104, regex)
doc.awarenessdocId: Id
update: string (max 1398104, regex)
doc.leavedocId: Id