Skip to main content

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:

TypeDescription
messageStandard chat message
systemSystem event (join, leave, ban notification)
moderationModeration 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

ParameterTypeDefaultDescription
limitinteger50Messages to return (max 100)
beforestringMessage 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:

BadgeDescription
broadcasterThe stream owner
moderatorAssigned by broadcaster
vipAssigned by broadcaster
subscriberActive channel subscriber
verifiedPlatform-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 }));
}