WebSocket & events#

The WebSocket carries realtime events — streaming agent tokens, new messages, reactions, typing — and can also be used to call methods in place of HTTP.

GET /api/ws#

Upgrade to a WebSocket, passing the token as a query parameter:

ws://localhost:4000/api/ws?token=dev:ops

A bad or missing token is rejected with HTTP 401 before the upgrade.

On connect, the server immediately sends a hello frame so you know the socket is live:

{ "method": "events.hello", "params": { "username": "ops", "kind": "human", "display_name": "Ops" } }

Frame shape#

Every frame — in both directions — is a JSON envelope:

{ "method": "<string>", "params": { /* ... */ } }
  • Server → client: method is an events.* name; params is the payload.
  • Client → server: method is a callable method name; the server replies with an RPC response.

Calling methods over the socket#

You can send the same operations available over HTTP. The method names use the internal dotted form:

Socket methodHTTP equivalent
auth.loginsignIn
accounts.me · accounts.get · accounts.listgetMe · getUser · getUsers
conversations.list · conversations.get · conversations.creategetChats · getChat · startChat
messages.send · messages.historysendMessage · getChatHistory
messages.delete · messages.delete_for_medeleteMessages · deleteMessagesForMe
reactions.add · reactions.removeparts of setMessageReaction
typing.setsendChatAction
// client → server
{ "method": "messages.send", "params": { "conversation_id": "0d6c…", "content": "hi" } }

The reply uses the legacy envelope (the WebSocket keeps error.code + error.message, unlike HTTP's error_code + description):

// success
{ "ok": true, "result": { /* ... */ } }
// failure
{ "ok": false, "error": { "code": "not_found", "message": "not found: conversation" } }

Server events#

Events are pushed to every participant of the affected conversation. A typical agent turn produces: messageNew (placeholder) → many messageDelta messageComplete, bracketed by typing true/false.

events.hello#

Sent once on connect. params is the authenticated Account.

events.messageNew#

A new message (from a human, or an agent's empty placeholder before it streams). params is a Message. Match client_msg_id against your optimistic copy.

events.messageDelta#

A streamed chunk appended to an in-progress agent message.

{ "method": "events.messageDelta", "params": { "id": "a1…", "delta": "Systematic short", "offset": 16 } }
FieldTypeDescription
idUUIDMessage being streamed.
deltastringText to append.
offsetintegerRunning byte length after appending.

events.messageComplete#

The message finished streaming. params is the finalized Message (with finalized_at set).

events.typing#

{ "method": "events.typing", "params": { "conversation_id": "0d6c…", "actor": { "username": "ops:claude", "kind": "agent", "display_name": "Claude" }, "is_typing": true } }

events.reactionAdded#

params is the new Reaction.

events.reactionRemoved#

{ "method": "events.reactionRemoved", "params": { "message_id": "a1…", "reactor": { "username": "ops", "kind": "human", "display_name": "Ops" }, "emoji": "🔥" } }

events.conversationNew#

Sent to a caller's other clients when they start a conversation. params is the Conversation.

events.messageDeleted#

A delete for everyone. Drop these message ids from the conversation.

{ "method": "events.messageDeleted", "params": { "conversation_id": "0d6c…", "ids": ["a1…", "b2…"] } }

Event summary#

EventParams
events.helloAccount
events.messageNewMessage
events.messageDelta{ id, delta, offset }
events.messageCompleteMessage
events.typing{ conversation_id, actor, is_typing }
events.reactionAddedReaction
events.reactionRemoved{ message_id, reactor, emoji }
events.conversationNewConversation
events.messageDeleted{ conversation_id, ids }