Skip to main content
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

Sending Messages

After authentication, send chat messages using the send_message action:
action
string
required
Must be "send_message".
message
string
required
The natural language message to send.
integration_id
string
ID of the database integration to query. Required for database queries.
chat_id
string
ID of an existing chat to continue the conversation. Omit to create a new chat.
title
string
Custom title for new chats. If omitted, a title is generated automatically.
model
string
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:
EventDataDescription
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 payloadA 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:
{"action": "ping"}
Response:
{"event": "pong", "data": {}}

Interrupting a Response

Send an interrupt action while the AI is streaming to cancel the in-progress response:
{"action": "interrupt"}
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.
action
string
required
Must be "delete_message".
message_id
string
required
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.
action
string
required
Must be "edit_message".
message_id
string
required
ID of the user message to edit.
content
string
required
The new message content to replace the original.
integration_id
string
ID of the database integration to query for the re-streamed response.
model
string
AI model to use for the re-streamed response.
include_tool_events
boolean
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

CodeReason
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,
  }));
}
{"event": "authenticated", "data": {"status": "ok"}}