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

# Discord Bot

> Build a Discord bot that integrates with TierZero to enable real-time incident response and automation directly in your Discord channels.

## Overview

This guide walks you through building a Discord bot that connects to TierZero, enabling your team to receive alerts, interact with AI responses, and manage incidents directly within Discord channels.

<Note>
  The Discord bot acts as a bridge between Discord and TierZero.
</Note>

## Architecture

Your Discord bot will:

* **Listen** to Discord events (mentions, reactions, guild joins, guild leaves)
* **Forward** relevant events to TierZero via webhooks
* **Receive** commands from TierZero to send messages, reactions, and updates
* **Handle** authentication and rate limiting

<CardGroup cols={2}>
  <Card title="Outbound Events" icon="arrow-right">
    Discord → TierZero
    Bot mentions, reactions, server joins
  </Card>

  <Card title="Inbound Commands" icon="arrow-left">
    TierZero → Discord
    Send messages, add reactions, fetch data
  </Card>
</CardGroup>

## Prerequisites

* Discord Developer Application and Bot Token
* HTTPS endpoint for webhook reception
* TierZero API key (32-character hex string)

## Data Model

### Core Objects

<Note>
  All timestamps are RFC-3339/ISO-8601 format in UTC. Field order is illustrative; implementations may reorder fields.
</Note>

<AccordionGroup>
  <Accordion title="Server (Discord Guild)">
    Represents a Discord server where your bot is installed.

    ```json theme={null}
    {
      "id": "1111222233334444",
      "name": "My Engineering Team",
      "icon_url": "https://cdn.discordapp.com/icons/...",
      "owner_id": "9999000011112222"
    }
    ```
  </Accordion>

  <Accordion title="Channel">
    Text or voice channels within a server. Channel types include `GUILD_TEXT`, `GUILD_VOICE`, `GUILD_PUBLIC_THREAD`, `GUILD_PRIVATE_THREAD`, and others.

    ```json theme={null}
    {
      "id": "5555666677778888",
      "server_id": "1111222233334444",
      "name": "incidents",
      "type": "GUILD_TEXT",
      "parent_id": null,
      "archived": false,
      "created_at": "2025-07-02T18:00:00Z"
    }
    ```

    <Note>
      Fields marked with `?` in the spec (like `parent_id?`, `archived?`) are optional and may be `null` or omitted.
    </Note>
  </Accordion>

  <Accordion title="Thread">
    Specialized channels that are treated as threads (includes all Channel fields plus thread-specific metadata).

    ```json theme={null}
    {
      "id": "7777888899990000",
      "server_id": "1111222233334444",
      "name": "incident-discussion",
      "type": "GUILD_PUBLIC_THREAD",
      "parent_id": "5555666677778888",
      "archived": false,
      "created_at": "2025-07-02T18:30:00Z",
      "starter_message_id": "123456789012345678",
      "member_count": 8,
      "message_count": 42
    }
    ```
  </Accordion>

  <Accordion title="User">
    Discord members with profile information.

    ```json theme={null}
    {
      "id": "9999000011112222",
      "username": "alice",
      "global_name": "Alice Chen",
      "avatar_url": "https://cdn.discordapp.com/avatars/9999000011112222/abc123.png",
      "bot": false
    }
    ```
  </Accordion>

  <Accordion title="Message">
    Chat messages with full context and metadata.

    ```json theme={null}
    {
      "id": "123456789012345678",
      "server_id": "1111222233334444",
      "channel_id": "5555666677778888",
      "thread_id": null,
      "author": {
        "id": "9999000011112222",
        "username": "alice",
        "global_name": "Alice Chen",
        "avatar_url": "https://cdn.discordapp.com/avatars/...",
        "bot": false
      },
      "body": "@tierzero-bot check system status",
      "timestamp": "2025-07-02T19:01:57Z",
      "mentions_bot": true
    }
    ```
  </Accordion>

  <Accordion title="Reaction">
    Emoji reactions on messages with count and metadata.

    ```json theme={null}
    {
      "emoji": {
        "id": "123456789012345678",
        "name": "custom_emoji"
      }
    }
    ```

    For standard Unicode emojis:

    ```json theme={null}
    {
      "emoji": {
        "id": null,
        "name": "👍"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

## Security & Authentication

<Warning>
  Store your API key securely and never expose it in client-side code or logs.
</Warning>

### Transport Security

* **HTTPS 1.2+** required for all communications
* **TLS certificate validation** enforced

### Authentication Header

Include this header in all requests (both directions):

```http theme={null}
x-tierzero-discord-key: <32-char-hex-api-key>
```

### Replay Protection

Each outbound event includes an `idempotency_key` (UUID-v4). TierZero rejects duplicates within 24 hours.

## Webhook Envelope Format

All webhook calls use this consistent envelope structure:

```json theme={null}
{
  "idempotency_key": "49c3e8c1-df6d-4caf-b7c7-f9236f6b5954",
  "sent_at": "2025-07-02T18:41:11Z",
  "event": "bot_mentioned",
  "version": "1.0",
  "data": {
    // Event-specific payload
  }
}
```

## Outbound Events (Discord → TierZero)

Your bot should send these events to TierZero when they occur in Discord:

### 1. Server Joined

**Trigger**: `GUILD_CREATE` - Bot added to a new Discord server\
**Endpoint**: `POST https://api.tierzero.ai/discord/webhooks/server_joined`

```json theme={null}
{
  "event": "server_joined",
  "data": {
    "server": {
      "id": "1111222233334444",
      "name": "Engineering Team",
      "icon_url": "https://cdn.discordapp.com/icons/...",
      "owner_id": "9999000011112222"
    }
  }
}
```

### 2. Bot Mentioned

**Trigger**: `MESSAGE_CREATE` - User mentions your bot in a message\
**Endpoint**: `POST https://api.tierzero.ai/discord/webhooks/bot_mentioned`

```json theme={null}
{
  "event": "bot_mentioned",
  "data": {
    "message": {
      "id": "123456789012345678",
      "server_id": "1111222233334444",
      "channel_id": "5555666677778888",
      "author": {
        "id": "9999000011112222",
        "username": "alice",
        "global_name": "Alice Chen",
        "bot": false
      },
      "body": "@tierzero-bot can you check latency?",
      "timestamp": "2025-07-02T19:01:57Z",
      "mentions_bot": true
    }
  }
}
```

### 3. Reaction Added

**Trigger**: `MESSAGE_REACTION_ADD` - User adds a reaction to a message\
**Endpoint**: `POST https://api.tierzero.ai/discord/webhooks/reaction_added`

```json theme={null}
{
  "event": "reaction_added",
  "data": {
    "message_id": "123456789012345678",
    "user": { /* User object */ },
    "reaction": {
      "emoji": { "id": null, "name": "👍" }
    }
  }
}
```

### 4. Server Left

**Trigger**: `GUILD_DELETE` - Bot removed from server\
**Endpoint**: `POST https://api.tierzero.ai/discord/webhooks/server_left`

```json theme={null}
{
  "event": "server_left",
  "data": {
    "server_id": "1111222233334444"
  }
}
```

## Inbound Webhooks (TierZero → Discord)

Your bot must expose these webhook endpoints for TierZero to call:

### Read Operations

<AccordionGroup>
  <Accordion title="Get Users">
    **Path**: `POST /webhooks/get_users`

    Lists all users in servers where the bot is present.

    ```json theme={null}
    // Request
    { "data": {} }

    // Response
    {
      "status": "OK",
      "users": [
        {
          "id": "9999000011112222",
          "username": "alice",
          "global_name": "Alice Chen",
          "bot": false
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Get Server Channels">
    **Path**: `POST /webhooks/get_server_channels`

    Lists all channels in a specific server.

    ```json theme={null}
    // Request
    {
      "data": {
        "server_id": "1111222233334444"
      }
    }

    // Response
    {
      "status": "OK",
      "channels": [
        {
          "id": "5555666677778888",
          "server_id": "1111222233334444",
          "name": "general",
          "type": "GUILD_TEXT",
          "parent_id": null,
          "archived": false,
          "created_at": "2025-07-02T18:00:00Z"
        },
        {
          "id": "7777888899990000",
          "server_id": "1111222233334444",
          "name": "incidents",
          "type": "GUILD_TEXT",
          "parent_id": null,
          "archived": false,
          "created_at": "2025-07-02T18:30:00Z"
        }
      ]
    }
    ```
  </Accordion>

  <Accordion title="Get Channel Messages">
    **Path**: `POST /webhooks/get_channel_messages`

    Retrieves messages from a specific channel.

    ```json theme={null}
    // Request
    {
      "data": {
        "channel_id": "5555666677778888",
        "limit": 50,
        "after": "123456789012345678",
        "before": "987654321098765432"
      }
    }

    // Response
    {
      "status": "OK",
      "messages": [ /* Array of Message objects */ ]
    }
    ```
  </Accordion>

  <Accordion title="Get Channel Threads">
    **Path**: `POST /webhooks/get_channel_threads`

    Lists all threads in a channel.

    ```json theme={null}
    // Request
    {
      "data": {
        "channel_id": "5555666677778888",
        "include_archived": true
      }
    }

    // Response
    {
      "status": "OK",
      "threads": [ /* Array of Thread objects */ ]
    }
    ```
  </Accordion>

  <Accordion title="Get Thread Messages">
    **Path**: `POST /webhooks/get_thread_messages`

    Retrieves messages from a specific thread.

    ```json theme={null}
    // Request
    {
      "data": {
        "thread_id": "7777888899990000",
        "after": "123456789012345678",
        "limit": 100
      }
    }

    // Response
    {
      "status": "OK",
      "messages": [ /* Array of Message objects */ ]
    }
    ```
  </Accordion>
</AccordionGroup>

### Write Operations

<AccordionGroup>
  <Accordion title="Send Message">
    **Path**: `POST /webhooks/send_message`

    Sends a message to a channel or thread.

    <Info>
      This single endpoint handles both channel and thread messages. For thread messages, `thread_id` will be included in the request data instead of `channel_id`.
    </Info>

    ```json theme={null}
    // Request (Channel Message)
    {
      "data": {
        "channel_id": "5555666677778888",
        "body": "🚨 **Alert Detected**\n\nHigh CPU usage on prod-server-01\n\nUse `/t0 investigate` for details.",
        "embeds": [
          {
            "title": "System Alert",
            "description": "CPU usage exceeded threshold",
            "color": 16711680,
            "timestamp": "2025-07-02T19:05:00Z"
          }
        ],
        "components": [
          {
            "type": 1,
            "components": [
              {
                "type": 2,
                "style": 3,
                "label": "Investigate",
                "custom_id": "investigate_alert"
              }
            ]
          }
        ]
      }
    }

    // Request (Thread Message)
    {
      "data": {
        "thread_id": "7777888899990000",
        "body": "🔍 **Investigation Update**\n\nRoot cause identified: Database connection pool exhausted\n\nApplying fix now...",
        "embeds": [
          {
            "title": "Thread Update",
            "description": "Investigation in progress",
            "color": 16776960,
            "timestamp": "2025-07-02T19:10:00Z"
          }
        ]
      }
    }

    // Response
    {
      "status": "OK",
      "message": {
        "id": "135792468013579246",
        "timestamp": "2025-07-02T19:05:01Z"
      }
    }
    ```
  </Accordion>

  <Accordion title="Add Reaction">
    **Path**: `POST /webhooks/add_reaction`

    Adds a reaction to a message.

    ```json theme={null}
    // Request
    {
      "data": {
        "message_id": "123456789012345678",
        "emoji": { "name": "✅" }
      }
    }

    // Response
    {
      "status": "OK"
    }
    ```
  </Accordion>

  <Accordion title="Edit Message">
    **Path**: `POST /webhooks/edit_message`

    Edits a message sent by the bot.

    ```json theme={null}
    // Request
    {
      "data": {
        "message_id": "135792468013579246",
        "new_body": "✅ **Alert Resolved**\n\nCPU usage back to normal levels.",
        "embeds": [
          {
            "title": "Status: Resolved",
            "description": "System metrics have returned to normal",
            "color": 65280,
            "timestamp": "2025-07-02T19:15:00Z"
          }
        ]
      }
    }

    // Response
    {
      "status": "OK"
    }
    ```
  </Accordion>

  <Accordion title="Delete Message">
    **Path**: `POST /webhooks/delete_message`

    Deletes a message sent by the bot.

    ```json theme={null}
    // Request
    {
      "data": {
        "message_id": "135792468013579246"
      }
    }

    // Response
    {
      "status": "OK"
    }
    ```
  </Accordion>
</AccordionGroup>

## Error Handling

Your bot should handle these HTTP status codes appropriately:

| Code  | Meaning               | Response                                                         |
| ----- | --------------------- | ---------------------------------------------------------------- |
| `400` | Invalid request       | `{"error": "VALIDATION_ERROR", "details": "Missing channel_id"}` |
| `401` | Authentication failed | `{"error": "UNAUTHORIZED", "details": "Invalid API key"}`        |
| `404` | Resource not found    | `{"error": "NOT_FOUND", "details": "Channel not found"}`         |
| `429` | Rate limit exceeded   | `{"error": "RATE_LIMITED", "retry_at": "2025-07-02T19:10:00Z"}`  |
| `5xx` | Server error          | `{"error": "INTERNAL_ERROR", "details": "Temporary failure"}`    |

<Tip>
  Implement exponential backoff for 5xx errors and respect the `retry_at` timestamp for 429 responses.
</Tip>

### Best Practices

1. **Queue requests** internally to avoid hitting limits
2. **Use the same `idempotency_key`** for retries
3. **Implement backoff** strategies for rate limit responses
4. **Cache frequently accessed data** to reduce API calls

## Implementation Example

Here's a basic TypeScript implementation structure using discord.js:

```typescript theme={null}
import { Client, GatewayIntentBits, Message, Guild, MessageReaction, User } from 'discord.js';
import express from 'express';
import { v4 as uuidv4 } from 'uuid';

interface TierZeroWebhookEnvelope {
  idempotency_key: string;
  sent_at: string;
  event: string;
  version: string;
  data: any;
}

class TierZeroBot {
  private client: Client;
  private tierZeroApiKey: string;
  private tierZeroBaseUrl: string = 'https://api.tierzero.ai';
  private app: express.Application;

  constructor(discordToken: string, tierZeroApiKey: string, port: number = 3000) {
    this.tierZeroApiKey = tierZeroApiKey;
    
    // Initialize Discord client
    this.client = new Client({
      intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
        GatewayIntentBits.GuildMessageReactions
      ]
    });

    // Initialize Express server for webhooks
    this.app = express();
    this.app.use(express.json());
    this.setupWebhookRoutes();
    this.setupDiscordEvents();

    this.client.login(discordToken);
    this.app.listen(port, () => {
      console.log(`TierZero Discord bot webhook server running on port ${port}`);
    });
  }

  private async sendToTierZero(event: string, data: any): Promise<void> {
    const payload: TierZeroWebhookEnvelope = {
      idempotency_key: uuidv4(),
      sent_at: new Date().toISOString(),
      event,
      version: '1.0',
      data
    };

    try {
      const response = await fetch(`${this.tierZeroBaseUrl}/discord/webhooks/${event}`, {
        method: 'POST',
        headers: {
          'x-tierzero-discord-key': this.tierZeroApiKey,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(payload)
      });

      if (!response.ok) {
        console.error(`TierZero API error: ${response.status}`);
      }
    } catch (error) {
      console.error('Failed to send event to TierZero:', error);
    }
  }

  private setupDiscordEvents(): void {
    this.client.on('guildCreate', async (guild: Guild) => {
      const serverData = {
        id: guild.id,
        name: guild.name,
        icon_url: guild.iconURL(),
        owner_id: guild.ownerId
      };
      
      await this.sendToTierZero('server_joined', { server: serverData });
    });

    this.client.on('messageCreate', async (message: Message) => {
      if (message.author.bot) return;
      
      if (message.mentions.has(this.client.user!)) {
        const messageData = {
          id: message.id,
          server_id: message.guild?.id || null,
          channel_id: message.channel.id,
          thread_id: message.channel.isThread() ? message.channel.id : null,
          author: {
            id: message.author.id,
            username: message.author.username,
            global_name: message.author.globalName,
            avatar_url: message.author.avatarURL(),
            bot: message.author.bot
          },
          body: message.content,
          embeds: message.embeds,
          attachments: message.attachments.map(a => ({ url: a.url, name: a.name })),
          reactions: [],
          timestamp: message.createdAt.toISOString(),
          edited_timestamp: message.editedAt?.toISOString() || null,
          mentions_bot: true
        };
        
        await this.sendToTierZero('bot_mentioned', { message: messageData });
      }
    });

    this.client.on('messageReactionAdd', async (reaction: MessageReaction, user: User) => {
      if (user.bot) return;
      
      const reactionData = {
        message_id: reaction.message.id,
        channel_id: reaction.message.channel.id,
        thread_id: reaction.message.channel.isThread() ? reaction.message.channel.id : null,
        user: {
          id: user.id,
          username: user.username,
          global_name: user.globalName,
          avatar_url: user.avatarURL(),
          bot: user.bot
        },
        reaction: {
          emoji: {
            id: reaction.emoji.id,
            name: reaction.emoji.name
          },
          count: reaction.count,
          me: reaction.users.cache.has(this.client.user!.id)
        }
      };
      
      await this.sendToTierZero('reaction_added', reactionData);
    });
  }

  private setupWebhookRoutes(): void {
    // Middleware to verify TierZero API key
    const verifyApiKey = (req: express.Request, res: express.Response, next: express.NextFunction) => {
      const apiKey = req.headers['x-tierzero-discord-key'];
      if (apiKey !== this.tierZeroApiKey) {
        return res.status(401).json({ error: 'UNAUTHORIZED', details: 'Invalid API key' });
      }
      next();
    };

    this.app.use('/webhooks', verifyApiKey);

    // Send message endpoint
    this.app.post('/webhooks/send_message', async (req, res) => {
      try {
        const { channel_id, thread_id, body, embeds, components } = req.body.data;
        const targetId = thread_id || channel_id;
        
        const channel = await this.client.channels.fetch(targetId);
        if (!channel?.isTextBased()) {
          return res.status(404).json({ error: 'NOT_FOUND', details: 'Channel not found' });
        }

        const message = await channel.send({
          content: body,
          embeds: embeds || [],
          components: components || []
        });

        res.json({
          status: 'OK',
          message: {
            id: message.id,
            timestamp: message.createdAt.toISOString()
          }
        });
      } catch (error) {
        res.status(500).json({ error: 'INTERNAL_ERROR', details: 'Failed to send message' });
      }
    });

    // Add reaction endpoint
    this.app.post('/webhooks/add_reaction', async (req, res) => {
      try {
        const { message_id, channel_id, emoji } = req.body.data;
        
        const channel = await this.client.channels.fetch(channel_id);
        if (!channel?.isTextBased()) {
          return res.status(404).json({ error: 'NOT_FOUND', details: 'Channel not found' });
        }

        const message = await channel.messages.fetch(message_id);
        await message.react(emoji.id ? `${emoji.name}:${emoji.id}` : emoji.name);

        res.json({ status: 'OK' });
      } catch (error) {
        res.status(500).json({ error: 'INTERNAL_ERROR', details: 'Failed to add reaction' });
      }
    });
  }
}

// Usage
const bot = new TierZeroBot(
  process.env.DISCORD_TOKEN!,
  process.env.TIERZERO_API_KEY!,
  3000
);
```

## Troubleshooting

### TierZero API Errors

**Symptoms**: 401, 403, or 5xx responses from TierZero

**Solutions**:

* Verify `x-tierzero-discord-key` header is correct
* Check request payload matches documented schema
* Ensure HTTPS is used for all requests

## Support

For technical support with your Discord bot integration:

* **Discord API**: Consult the [Discord Developer Documentation](https://discord.com/developers/docs)
