Connect via WebSocket for real-time, bidirectional chat communication. WebSocket is ideal for applications that need to send multiple messages without reconnecting.
For event types and general chat concepts, see the Chat Overview .
Connection
Connect to the WebSocket endpoint with your JWT token:
wss://api.trellis.sh/v1/chats/ws?token=YOUR_TOKEN
Alternatively, authenticate after connecting by sending an authentication message first.
Authentication
Pass your JWT token as a query parameter when connecting: wss://api.trellis.sh/v1/chats/ws?token=YOUR_TOKEN
Upon successful connection, you’ll receive: { "event" : "authenticated" , "data" : { "status" : "ok" }}
Connect without a token, then send an authentication message: { "action" : "authenticate" , "token" : "YOUR_TOKEN" }
Upon success: { "event" : "authenticated" , "data" : { "status" : "ok" }}
If authentication fails, the connection will close with code 4001.
Sending Messages
After authentication, send chat messages using the send_message action:
The natural language message to send.
ID of the database integration to query. Required for database queries.
ID of an existing chat to continue the conversation. Omit to create a new chat.
Custom title for new chats. If omitted, a title is generated automatically.
AI model to use for this message. If omitted, the default model is used. See List Models for available values.
Example message
{
"action" : "send_message" ,
"message" : "How many orders were placed last month?" ,
"integration_id" : "550e8400-e29b-41d4-a716-446655440000" ,
"model" : "claude-sonnet-4-6"
}
Response Events
Events are delivered as JSON objects with event and data fields:
Event Data Description authenticated{"status": "ok"}Connection authenticated successfully chat_metadata{"id": "chat_...", "user_message_id": "msg_..."}Chat ID and persisted user message ID. Emitted at the start of every response. processing{"status": "thinking"}AI is processing the request visualizationFull chart or table payload A chart or table was generated. See Visualizations . message{"content": "...", "id": "msg_..."}The final response interrupted{"status": "interrupted"}The in-progress response was cancelled by an interrupt action deleted{"message_id": "msg_..."}A message was successfully deleted via delete_message error{"error": "..."}An error occurred pong{}Response to ping action
Example response stream
{ "event" : "chat_metadata" , "data" : { "id" : "chat_abc123" , "user_message_id" : "msg_001" }}
{ "event" : "processing" , "data" : { "status" : "thinking" }}
{ "event" : "processing" , "data" : { "status" : "analyzing" }}
{ "event" : "visualization" , "data" : { "id" : "ab12xy3456" , "type" : "chart" , "chart_type" : "bar" , "title" : "Orders by Region" , "data" : { "values" : [{ "label" : "North" , "value" : 412 }, { "label" : "South" , "value" : 289 }, { "label" : "East" , "value" : 531 }], "x_axis_label" : "Region" , "y_axis_label" : "Orders" }}}
{ "event" : "message" , "data" : { "content" : "There were 1,232 orders placed last month, with the East region leading at 531." , "id" : "msg_xyz" }}
Keep-Alive
Send periodic pings to keep the connection alive:
Response:
{ "event" : "pong" , "data" : {}}
Interrupting a Response
Send an interrupt action while the AI is streaming to cancel the in-progress response:
The server cancels the stream and responds with:
{ "event" : "interrupted" , "data" : { "status" : "interrupted" }}
If no response is currently streaming when interrupt is sent, an error event is returned instead.
For SSE connections, interruption is handled automatically when the HTTP connection is closed — no special action required.
Managing Messages
Delete a message
Send a delete_message action to remove a message and all subsequent messages from the chat. This is irreversible.
Must be "delete_message".
ID of the message to delete. The target message and every message after it are removed.
{ "action" : "delete_message" , "message_id" : "msg_xyz789" }
On success:
{ "event" : "deleted" , "data" : { "message_id" : "msg_xyz789" }}
Edit a message
Send an edit_message action to replace a user message’s content. All messages after the edited message are removed and a fresh agent response is streamed back.
Only user messages can be edited. Attempting to edit an assistant message returns an error event.
ID of the user message to edit.
The new message content to replace the original.
ID of the database integration to query for the re-streamed response.
AI model to use for the re-streamed response.
If true, internal tool events are included in the stream. Defaults to false.
{
"action" : "edit_message" ,
"message_id" : "msg_xyz789" ,
"content" : "Show me the top 10 projects by budget instead" ,
"integration_id" : "550e8400-e29b-41d4-a716-446655440000"
}
The server streams back a fresh response starting with chat_metadata:
{ "event" : "chat_metadata" , "data" : { "id" : "chat_abc123" , "user_message_id" : "msg_new001" }}
{ "event" : "processing" , "data" : { "status" : "thinking" }}
{ "event" : "message" , "data" : { "content" : "Here are the top 10 projects by budget." , "id" : "msg_new002" }}
Error Handling
Errors are delivered as events, not connection closures (unless authentication fails):
{ "event" : "error" , "data" : { "error" : "No datasource connected" }}
Connection close codes
Code Reason 4001Authentication required or invalid token 4002Invalid JSON message 1011Internal server error
Multi-Message Conversations
WebSocket connections persist, allowing you to send multiple messages in the same session:
// First message — creates new chat
{ "action" : "send_message" , "message" : "Show me sales data" , "integration_id" : "..." }
// Response includes chat_id and user_message_id
{ "event" : "chat_metadata" , "data" : { "id" : "chat_abc123" , "user_message_id" : "msg_001" }}
{ "event" : "visualization" , "data" : { "id" : "kl78mn9012" , "type" : "table" , "headers" : [ "Region" , "Sales" ], "rows" : [[ "North" , "1,234,000" ], [ "South" , "987,500" ], [ "East" , "2,100,000" ]]}}
{ "event" : "message" , "data" : { "content" : "Here's the sales data broken down by region." , "id" : "msg_002" }}
// Follow-up message — reuse chat_id
{ "action" : "send_message" , "message" : "Show that as a bar chart" , "integration_id" : "..." , "chat_id" : "chat_abc123" }
// Response
{ "event" : "chat_metadata" , "data" : { "id" : "chat_abc123" , "user_message_id" : "msg_003" }}
{ "event" : "processing" , "data" : { "status" : "thinking" }}
{ "event" : "visualization" , "data" : { "id" : "op90qr1234" , "type" : "chart" , "chart_type" : "bar" , "title" : "Sales by Region" , "data" : { "values" : [{ "label" : "North" , "value" : 1234000 }, { "label" : "South" , "value" : 987500 }, { "label" : "East" , "value" : 2100000 }], "x_axis_label" : "Region" , "y_axis_label" : "Sales (AED)" }}}
{ "event" : "message" , "data" : { "content" : "Here's the regional breakdown as a bar chart. The East region leads with AED 2.1M." , "id" : "msg_002" }}
const ws = new WebSocket ( "wss://api.trellis.sh/v1/chats/ws?token=YOUR_TOKEN" );
let streamingResponse = false ;
ws . onmessage = ( msg ) => {
const event = JSON . parse ( msg . data );
switch ( event . event ) {
case "chat_metadata" :
console . log ( "Chat:" , event . data . id , "| User message:" , event . data . user_message_id );
streamingResponse = true ;
break ;
case "visualization" :
if ( event . data . type === "chart" ) {
renderChart ( event . data . chart_type , event . data . title , event . data . data );
} else if ( event . data . type === "table" ) {
renderTable ( event . data . headers , event . data . rows );
}
break ;
case "message" :
displayMessage ( event . data . content );
streamingResponse = false ;
break ;
case "interrupted" :
console . log ( "Response was interrupted" );
streamingResponse = false ;
break ;
case "deleted" :
console . log ( "Message deleted:" , event . data . message_id );
break ;
case "error" :
showError ( event . data . error );
streamingResponse = false ;
break ;
}
};
ws . onopen = () => {
ws . send ( JSON . stringify ({
action: "send_message" ,
message: "Show me orders by region as a bar chart" ,
integration_id: "550e8400-e29b-41d4-a716-446655440000"
}));
};
// Interrupt a streaming response
function interruptResponse () {
if ( streamingResponse ) {
ws . send ( JSON . stringify ({ action: "interrupt" }));
}
}
// Delete a message
function deleteMessage ( messageId ) {
ws . send ( JSON . stringify ({ action: "delete_message" , message_id: messageId }));
}
// Edit a message
function editMessage ( messageId , newContent , integrationId ) {
ws . send ( JSON . stringify ({
action: "edit_message" ,
message_id: messageId ,
content: newContent ,
integration_id: integrationId ,
}));
}
Authenticated
Chat response with chart
Interrupted response
Message deleted
Error
{ "event" : "authenticated" , "data" : { "status" : "ok" }}