---
title: "Chat"
description: "Live stream chat API — real-time messaging, moderation, and bot integration"
canonical: "https://docs.ainative.studio/docs/live-streaming/chat"
last-updated: "2026-10-22T17:48:46.689Z"
---

# Chat

Source: https://docs.ainative.studio/docs/live-streaming/chat

> Live stream chat API — real-time messaging, moderation, and bot integration

## 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:

```json
{
  "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

```json
{
  "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:

```json
{ "type": "ping" }
```

The server responds with:

```json
{ "type": "pong" }
```

---

## Server → client messages

### Chat message

Received when any user sends a message:

```json
{
  "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:

```json
{
  "type": "message_deleted",
  "data": { "message_id": "msg-uuid" }
}
```

### Viewer count update

Sent periodically with the current concurrent viewer count:

```json
{
  "type": "viewer_count",
  "data": { "count": 142 }
}
```

### Error

Sent when a client action fails (e.g., invalid message format, rate limit):

```json
{
  "type": "error",
  "message": "Message too long"
}
```

### Pong

Response to a client ping:

```json
{ "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)

<span className="badge badge--get">GET</span> `/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:

```bash
## 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

```json
{
  "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

<span className="badge badge--post">POST</span> `/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)

```json
{
  "user_id": "target-user-uuid",
  "badge_type": "moderator"
}
```

`badge_type` values: `"moderator"` or `"vip"`.

### Remove moderator

<span className="badge badge--delete">DELETE</span> `/api/v1/streams/{stream_id}/moderators/{user_id}`

Remove a moderator or VIP badge. Broadcaster only. Returns `204 No Content`.

### List moderators

<span className="badge badge--get">GET</span> `/api/v1/streams/{stream_id}/moderators`

Get all moderators and VIPs for a stream.

```json
{
  "moderators": [
    {
      "id": "...",
      "user_id": "...",
      "username": "bob",
      "badge_type": "moderator",
      "granted_at": "2026-04-25T09:00:00Z"
    }
  ],
  "total": 3
}
```

Filter by badge type:

```bash
GET /api/v1/streams/{stream_id}/moderators?badge_type=moderator
```

### Get user badges

<span className="badge badge--get">GET</span> `/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 |

```json
{
  "badges": ["moderator", "verified"]
}
```

---

## JavaScript example

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