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:opsA 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:
methodis anevents.*name;paramsis the payload. - Client → server:
methodis 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 method | HTTP equivalent |
|---|---|
auth.login | signIn |
accounts.me · accounts.get · accounts.list | getMe · getUser · getUsers |
conversations.list · conversations.get · conversations.create | getChats · getChat · startChat |
messages.send · messages.history | sendMessage · getChatHistory |
messages.delete · messages.delete_for_me | deleteMessages · deleteMessagesForMe |
reactions.add · reactions.remove | parts of setMessageReaction |
typing.set | sendChatAction |
// 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 } }| Field | Type | Description |
|---|---|---|
id | UUID | Message being streamed. |
delta | string | Text to append. |
offset | integer | Running 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#
| Event | Params |
|---|---|
events.hello | Account |
events.messageNew | Message |
events.messageDelta | { id, delta, offset } |
events.messageComplete | Message |
events.typing | { conversation_id, actor, is_typing } |
events.reactionAdded | Reaction |
events.reactionRemoved | { message_id, reactor, emoji } |
events.conversationNew | Conversation |
events.messageDeleted | { conversation_id, ids } |