Chat
Stream chat is powered by a WebSocket connection with a defined message protocol. REST endpoints provide chat history with cursor-based pagination and moderator management.
WebSocket connection
wss://api.ainative.studio/api/v1/streams/{stream_id}/chat/ws
Connect to this endpoint to participate in real-time chat. Authentication is via a ?token= query parameter — Bearer tokens in headers are not supported for WebSocket upgrades.
wss://api.ainative.studio/api/v1/streams/a1b2c3d4-.../chat/ws?token=your_jwt_token
Authentication
The token query parameter is required. An invalid or missing token results in immediate disconnection. Unauthenticated users cannot send messages.
On connect
Immediately after establishing a connection, the server sends the most recent chat messages as a history event:
{
"type": "history",
"data": [
{
"id": "msg-uuid",
"stream_id": "stream-uuid",
"user": {
"id": "user-uuid",
"username": "alice",
"display_name": "Alice Chen",
"avatar_url": "https://..."
},
"content": "Hello everyone!",
"message_type": "message",
"created_at": "2026-04-25T10:05:30Z"
}
]
}
Client → server messages
Send a message
{
"type": "message",
"content": "Hello everyone!"
}
content must be a non-empty string. The server broadcasts the message to all connected clients.
Ping
Send a ping to keep the connection alive:
{ "type": "ping" }
The server responds with:
{ "type": "pong" }
Server → client messages
Chat message
Received when any user sends a message:
{
"type": "message",
"data": {
"id": "msg-uuid",
"stream_id": "stream-uuid",
"user": {
"id": "user-uuid",
"username": "alice",
"display_name": "Alice Chen",
"avatar_url": "https://..."
},
"content": "Great stream!",
"message_type": "message",
"created_at": "2026-04-25T10:06:00Z"
}
}
Message deleted
Broadcasted when a moderator deletes a message:
{
"type": "message_deleted",
"data": { "message_id": "msg-uuid" }
}
Viewer count update
Sent periodically with the current concurrent viewer count:
{
"type": "viewer_count",
"data": { "count": 142 }
}
Error
Sent when a client action fails (e.g., invalid message format, rate limit):
{
"type": "error",
"message": "Message too long"
}
Pong
Response to a client ping:
{ "type": "pong" }
Message types
The message_type field on messages indicates the kind of chat event:
| Type | Description |
|---|---|
message | Standard chat message |
system | System event (join, leave, ban notification) |
moderation | Moderation action notification |
Chat history (REST)
GET /api/v1/streams/{stream_id}/chat/history
Retrieve persisted chat messages using cursor-based pagination. Messages are returned in chronological order (oldest first).
Auth required: Yes
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
limit | integer | 50 | Messages to return (max 100) |
before | string | — | Message ID cursor — returns messages before this ID |
Pagination
Use next_cursor from the response as the before parameter to load older messages:
# First page (most recent 50)
GET /api/v1/streams/{stream_id}/chat/history
# Next page (messages before the oldest in the first batch)
GET /api/v1/streams/{stream_id}/chat/history?before=msg-uuid
Response
{
"messages": [
{
"id": "msg-uuid",
"stream_id": "stream-uuid",
"user": {
"id": "user-uuid",
"username": "alice",
"display_name": "Alice Chen",
"avatar_url": "https://..."
},
"content": "Hello everyone!",
"message_type": "message",
"created_at": "2026-04-25T10:05:30Z"
}
],
"has_more": true,
"next_cursor": "oldest-msg-id-in-batch"
}
When has_more is false, you have reached the beginning of the chat history.
Moderation
Moderation is managed through the badge system and message deletion. Moderators and the broadcaster can delete messages and issue timeouts.
Add moderator
POST /api/v1/streams/{stream_id}/moderators
Grant a user the moderator or vip badge. Only the broadcaster can add moderators.
Auth required: Yes (broadcaster only)
{
"user_id": "target-user-uuid",
"badge_type": "moderator"
}
badge_type values: "moderator" or "vip".
Remove moderator
DELETE /api/v1/streams/{stream_id}/moderators/{user_id}
Remove a moderator or VIP badge. Broadcaster only. Returns 204 No Content.
List moderators
GET /api/v1/streams/{stream_id}/moderators
Get all moderators and VIPs for a stream.
{
"moderators": [
{
"id": "...",
"user_id": "...",
"username": "bob",
"badge_type": "moderator",
"granted_at": "2026-04-25T09:00:00Z"
}
],
"total": 3
}
Filter by badge type:
GET /api/v1/streams/{stream_id}/moderators?badge_type=moderator
Get user badges
GET /api/v1/streams/{stream_id}/users/{user_id}/badges
Get all badges a specific user holds in a stream. Possible badges:
| Badge | Description |
|---|---|
broadcaster | The stream owner |
moderator | Assigned by broadcaster |
vip | Assigned by broadcaster |
subscriber | Active channel subscriber |
verified | Platform-verified user |
{
"badges": ["moderator", "verified"]
}
JavaScript example
const token = 'your_jwt_token';
const streamId = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';
const ws = new WebSocket(
`wss://api.ainative.studio/api/v1/streams/${streamId}/chat/ws?token=${token}`
);
ws.addEventListener('open', () => {
console.log('Connected to chat');
// Keep connection alive
setInterval(() => ws.send(JSON.stringify({ type: 'ping' })), 30_000);
});
ws.addEventListener('message', (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'history') {
// Initial message history
renderHistory(msg.data);
} else if (msg.type === 'message') {
// New message
appendMessage(msg.data);
} else if (msg.type === 'viewer_count') {
updateViewerCount(msg.data.count);
} else if (msg.type === 'message_deleted') {
removeMessage(msg.data.message_id);
}
});
// Send a message
function sendMessage(text) {
ws.send(JSON.stringify({ type: 'message', content: text }));
}