> ## Documentation Index
> Fetch the complete documentation index at: https://meepa.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Bot Gateway

> WebSocket gateway for bot applications

The Bot Gateway is a WebSocket endpoint that provides real-time events for bot applications. Bots authenticate with their bot token and receive events from channels they've been added to as members.

## Getting Started

<Steps>
  <Step title="Create a Bot">
    Create a bot using any of these methods:

    * **Web UI**: Server Settings → Bots → Create Bot
    * **CLI**: `meepachat bots create --username mybot --server <serverID>`
    * **API**: `POST /api/bots` with a human user's Bearer token

    All three return a bot token — save it immediately, it's only shown once.
  </Step>

  <Step title="Add Bot to a Server">
    The bot is automatically added to the server specified during creation. To add it to additional servers:

    * **Web UI**: Server Settings → Bots → Add Existing Bot
    * **CLI**: `meepachat bots add-to-server <botID> --server <serverID>`
    * **API**: `POST /api/servers/{serverID}/bots/{botID}`
  </Step>

  <Step title="Connect to the Gateway">
    Open a WebSocket connection to `/api/bot-gateway` using the bot token. The bot receives a `ready` event with its current state.
  </Step>

  <Step title="Handle Events">
    Listen for real-time events (`message.created`, `reaction.sync`, etc.) and respond via the REST API using the bot token.
  </Step>
</Steps>

## Connection

**Endpoint**: `GET /api/bot-gateway`

Authenticate using the `Authorization` header:

```
GET /api/bot-gateway
Authorization: Bot <bot_token>
```

Bot tokens follow the format `<bot_id>.<secret>` and are obtained when creating a bot via `POST /api/bots`.

```javascript theme={null}
const WebSocket = require('ws');

const botToken = 'bot-id.bot-secret';
const ws = new WebSocket('wss://your-meepa-instance.example.com/api/bot-gateway', {
  headers: {
    'Authorization': `Bot ${botToken}`
  }
});
```

<Note>
  Passing the token as a `?token=` query parameter works but is deprecated. Use the `Authorization: Bot <token>` header instead.
</Note>

## Connection Parameters

| Parameter            | Value                                                          |
| -------------------- | -------------------------------------------------------------- |
| Max message size     | 4096 bytes                                                     |
| Ping interval        | Server sends WebSocket ping frames every 30 seconds            |
| Pong timeout         | Bot must respond within 60 seconds or the connection is closed |
| Write deadline       | 10 seconds                                                     |
| Channel subscription | Bots receive events only from channels they are members of     |

## Ready Event

Immediately after connecting, the bot receives a `ready` event containing:

* Bot user object (id, username, avatar, etc.)
* All servers the bot is a member of, with channels the bot has been added to
* All DM channels the bot is a participant in

This is the **only** time the bot receives this initialization data. Store it for the duration of the session.

```json theme={null}
{
  "type": "ready",
  "data": {
    "user": {
      "id": "bot-user-id",
      "username": "MyBot",
      "bot": true,
      "createdAt": "2026-02-14T10:00:00Z",
      "avatarUrl": null,
      "botOwnerId": "owner-user-id"
    },
    "servers": [
      {
        "id": "server-id-1",
        "name": "My Server",
        "channels": [
          {
            "id": "channel-id-1",
            "serverId": "server-id-1",
            "name": "general",
            "type": "text",
            "groupId": null,
            "position": 0,
            "createdAt": "2026-02-14T10:00:00Z"
          },
          {
            "id": "channel-id-2",
            "serverId": "server-id-1",
            "name": "bot-commands",
            "type": "text",
            "groupId": null,
            "position": 1,
            "createdAt": "2026-02-14T10:00:00Z"
          }
        ]
      }
    ],
    "dmChannels": [
      {
        "id": "dm-channel-id",
        "type": "dm",
        "memberIds": ["bot-user-id", "human-user-id"],
        "createdAt": "2026-02-14T11:00:00Z"
      }
    ]
  }
}
```

## Client-to-Server Events

### `ping`

Heartbeat. The server responds with a `pong` event.

<CodeGroup>
  ```json Send theme={null}
  {
    "type": "ping"
  }
  ```

  ```json Receive theme={null}
  {
    "type": "pong"
  }
  ```
</CodeGroup>

### `subscribe`

Subscribe to additional channels at the WebSocket level. Use this for channels added to the bot's membership after the initial connection, or for DM channels opened after connect. This only adds the channel to the in-memory subscription -- the bot must already be a member of the channel to receive events via the REST API.

```json theme={null}
{
  "type": "subscribe",
  "data": {
    "channelIds": ["550e8400-e29b-41d4-a716-446655440000"]
  }
}
```

### `typing`

Broadcast a typing indicator to a channel. The bot must be subscribed to the channel.

```json theme={null}
{
  "type": "typing",
  "data": {
    "channelId": "550e8400-e29b-41d4-a716-446655440000"
  }
}
```

Other clients in the channel will receive a `typing` event with the bot's user ID.

## Server-to-Client Events

Bots receive the same real-time events as human WebSocket clients. All events follow this structure:

```json theme={null}
{
  "type": "event_type",
  "data": { ... }
}
```

| Event              | Description                                                      |
| ------------------ | ---------------------------------------------------------------- |
| `pong`             | Response to bot `ping` event                                     |
| `message.created`  | A new message was posted in a subscribed channel                 |
| `message.updated`  | A message was edited                                             |
| `message.deleted`  | A message was deleted                                            |
| `reaction.sync`    | Reactions were added or removed (full state)                     |
| `channel.created`  | A new channel was created in a bot's server                      |
| `channel.updated`  | A channel was renamed, moved, or reordered                       |
| `channel.deleted`  | A channel was deleted                                            |
| `typing`           | A user is typing in a subscribed channel                         |
| `presence.initial` | All currently online user IDs (sent on connection)               |
| `presence.update`  | A user went online or offline                                    |
| `server.added`     | Bot was added to a new server (includes server and channel list) |

See the [WebSocket](/meepachat/websocket) page for detailed payload documentation for each event type.

## Sending Messages

Bots send messages via the standard REST API using their bot token:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://your-meepa-instance.example.com/api/servers/{serverId}/channels/{channelId}/messages \
    -H "Authorization: Bot bot-id.bot-secret" \
    -H "Content-Type: application/json" \
    -d '{"content": "Hello from bot!"}'
  ```

  ```javascript Node.js theme={null}
  async function sendMessage(serverId, channelId, content) {
    const res = await fetch(
      `${BASE_URL}/api/servers/${serverId}/channels/${channelId}/messages`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bot ${BOT_TOKEN}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ content })
      }
    );

    if (!res.ok) {
      console.error('Failed to send message:', await res.text());
    }
  }
  ```
</CodeGroup>

The bot will receive its own `message.created` event over the gateway.

## Full Example Bot (Node.js)

<CodeGroup>
  ```javascript bot.js theme={null}
  const WebSocket = require('ws');
  const fetch = require('node-fetch');

  const BOT_TOKEN = 'your-bot-id.your-bot-secret';
  const BASE_URL = 'https://your-meepa-instance.example.com';

  let ws;
  let botUser;
  let channels = new Map(); // channelId -> channel object

  function connect() {
    ws = new WebSocket(`${BASE_URL}/api/bot-gateway`, {
      headers: { 'Authorization': `Bot ${BOT_TOKEN}` }
    });

    ws.on('open', () => {
      console.log('Connected to bot gateway');
    });

    ws.on('message', (data) => {
      const event = JSON.parse(data);
      handleEvent(event);
    });

    ws.on('close', () => {
      console.log('Disconnected, reconnecting in 5s...');
      setTimeout(connect, 5000);
    });

    ws.on('error', (err) => {
      console.error('WebSocket error:', err);
    });
  }

  function handleEvent(event) {
    switch (event.type) {
      case 'ready':
        botUser = event.data.user;
        console.log(`Bot ready: ${botUser.username} (${botUser.id})`);

        // Store channel data
        for (const server of event.data.servers) {
          for (const channel of server.channels) {
            channels.set(channel.id, channel);
          }
        }
        console.log(`Subscribed to ${channels.size} channels`);
        break;

      case 'message.created':
        handleMessage(event.data);
        break;

      case 'channel.created':
        // Bot will only receive this if it's a member of the channel
        channels.set(event.data.id, event.data);
        // Subscribe at the WebSocket level to receive real-time events
        ws.send(JSON.stringify({
          type: 'subscribe',
          data: { channelIds: [event.data.id] }
        }));
        break;

      case 'channel.deleted':
        channels.delete(event.data.channelId);
        break;
    }
  }

  async function handleMessage(msg) {
    // Ignore own messages
    if (msg.userId === botUser.id) return;

    const channel = channels.get(msg.channelId);
    if (!channel) return;

    console.log(`[${channel.name}] ${msg.content}`);

    // Respond to !ping command
    if (msg.content === '!ping') {
      await sendMessage(channel.serverId, channel.id, 'Pong!');
    }
  }

  async function sendMessage(serverId, channelId, content) {
    const res = await fetch(
      `${BASE_URL}/api/servers/${serverId}/channels/${channelId}/messages`,
      {
        method: 'POST',
        headers: {
          'Authorization': `Bot ${BOT_TOKEN}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ content })
      }
    );

    if (!res.ok) {
      console.error('Failed to send message:', await res.text());
    }
  }

  // Start bot
  connect();

  // Send heartbeat every 30s
  setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ type: 'ping' }));
    }
  }, 30000);
  ```
</CodeGroup>

## Reconnection Strategy

Bots should implement reconnection with exponential backoff:

1. Wait 1 second before the first retry
2. Double the wait time on each failure (max 30 seconds)
3. Reset the wait time after a successful connection
4. After reconnecting, the server sends a new `ready` event with current state
5. Use the `ready` event to resync channel subscriptions and local state

## Lifecycle Events

When a bot is removed from a server via `DELETE /api/servers/{serverID}/bots/{botID}`, the server forcibly closes the bot's gateway connection. The bot should reconnect and will receive an updated `ready` event reflecting the new server list.

## Rate Limits

Bots share the same rate limits as human users:

| Limit                  | Value                                                           |
| ---------------------- | --------------------------------------------------------------- |
| HTTP API               | 240 requests/minute, burst 60 (per IP)                          |
| WebSocket message size | 4096 bytes                                                      |
| Gateway connections    | One per bot token (new connections disconnect the previous one) |

<Note>
  The WebSocket gateway connection itself is not rate limited. Once connected, real-time events bypass the HTTP rate limiter. Only REST API calls (sending messages, etc.) count against the rate limit.
</Note>

## Security Best Practices

<Warning>
  Bot tokens grant full access to all servers the bot belongs to. Treat them like passwords.
</Warning>

* **Keep bot tokens secret**: Never commit tokens to source control or expose them in client-side code.
* **Rotate compromised tokens**: If a token is leaked, regenerate it immediately via `POST /api/bots/{botID}/regenerate-token`.
* **Validate event data**: Do not trust message content blindly. Sanitize before processing or displaying.
* **Log security events**: Track unusual patterns such as spam or unauthorized access attempts.
* **Use environment variables**: Store the bot token in an environment variable, not in source code.
