Skip to main content

Chat

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)
beforestring—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:

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 }));
}