How to Connect a Discord Bot to Usable
How to Connect a Discord Bot to Usable
Category: Integrations | Difficulty: Intermediate | Last Updated: 2025-10-16
Overview
Learn how to build a Discord bot that automatically creates Usable memory fragments from Discord forum posts. This integration enables seamless knowledge management, turning Discord discussions into searchable, organized documentation.
What You’ll Learn
- Set up a Discord bot application with proper permissions
 - Integrate Usable’s API for fragment creation
 - Listen for Discord forum thread events
 - Format Discord messages for Usable storage
 - Deploy the bot to production
 
Prerequisites
- Basic TypeScript/JavaScript knowledge
 - Discord account with server admin access
 - Usable account with API access
 - Node.js or Bun installed
 - Understanding of REST APIs
 
Estimated Time
⏱️ 30-45 minutes to complete
Concepts
Discord Bot Architecture
A Discord bot is a program that:
- Connects to Discord’s Gateway (WebSocket connection)
 - Listens for events (messages, threads, reactions)
 - Responds to events by performing actions
 - Runs continuously (24/7)
 
Usable Memory Fragments
Memory fragments are the core knowledge unit in Usable:
- Title: Brief description
 - Content: Full text with markdown formatting
 - Tags: For organization and search
 - Type: Classification (issue, knowledge, recipe, etc.)
 - Workspace: Where the fragment is stored
 
Integration Flow
sequenceDiagram    participant User    participant Discord    participant Bot    participant Usable
    User->>Discord: Create forum post    Discord->>Bot: threadCreate event    Bot->>Bot: Format message    Bot->>Usable: POST /memory-fragments    Usable-->>Bot: Fragment ID    Bot->>Discord: Reply with confirmation    Discord-->>User: See bot replyKey Terminology
| Term | Definition | 
|---|---|
| Guild | Discord server (API terminology) | 
| Thread | Discussion within a forum channel | 
| Gateway | WebSocket connection to Discord | 
| Fragment | Knowledge unit in Usable | 
| OAuth2 | Discord’s authorization system | 
| Intent | Permission to receive specific events | 
Tutorial / How-To
Step 1: Create Discord Bot Application
- Go to Discord Developer Portal
 - Click “New Application”
 - Name it “Usable Bot” (or your preference)
 - Click “Create”
 
What this does: Creates your bot application container that holds credentials and settings.
Step 2: Configure Bot Settings
- Navigate to the “Bot” section in left sidebar
 - Click “Add Bot” → “Yes, do it!”
 - Under “Token”, click “Reset Token” and copy it (save for later)
 - Enable Privileged Gateway Intents:
- ☑️ Message Content Intent (required)
 - ☑️ Server Members Intent (optional)
 
 - Click “Save Changes”
 
Why this matters: Message Content Intent allows your bot to read message text, which is essential for creating fragments.
Step 3: Generate Bot Invite URL
- Go to “OAuth2” → “URL Generator”
 - Select Scopes:
- ☑️ 
bot 
 - ☑️ 
 - Select Bot Permissions:
- ☑️ View Channels
 - ☑️ Send Messages
 - ☑️ Send Messages in Threads
 - ☑️ Read Message History
 
 - Copy the Generated URL at the bottom
 - Open URL in browser and add bot to your test server
 
Expected result: Bot appears in your server’s member list (offline until you run the code).
Step 4: Get Usable API Credentials
- Log in to Usable
 - Navigate to Settings → API Keys
 - Click “Generate New Key”
 - Copy the API key (starts with 
usa_key_...) - Copy your Workspace ID from workspace settings
 
Security note: Never commit API keys to git. Use environment variables.
Step 5: Set Up Project
Create project directory:
mkdir usable-discord-botcd usable-discord-botInitialize with Bun (recommended):
bun init -ybun add discord.js axios zodbun add -d @types/node typescriptOr with npm:
npm init -ynpm install discord.js axios zodnpm install -D @types/node typescriptStep 6: Create Environment Configuration
Create .env file:
# Discord ConfigurationDISCORD_BOT_TOKEN=your_bot_token_hereDISCORD_CLIENT_ID=your_client_id_here
# Usable API ConfigurationUSABLE_API_URL=https://api.usable.dev/apiUSABLE_API_KEY=your_usable_api_key_hereUSABLE_WORKSPACE_ID=your_workspace_id_hereUSABLE_ISSUE_FRAGMENT_TYPE_ID=your_fragment_type_id_here
# Bot ConfigurationNODE_ENV=developmentLOG_LEVEL=infoGet Fragment Type ID:
- In Usable, go to workspace settings
 - Navigate to Fragment Types
 - Copy the UUID for “Issue” type
 
Step 7: Implement Usable API Client
Create src/services/usable-api.service.ts:
import axios, { AxiosInstance } from 'axios';
export class UsableApiService {  private client: AxiosInstance;
  constructor() {    this.client = axios.create({      baseURL: process.env.USABLE_API_URL,      headers: {        Authorization: `Bearer ${process.env.USABLE_API_KEY}`,        'Content-Type': 'application/json',      },    });  }
  async createFragment(data: {    title: string;    content: string;    workspaceId: string;    fragmentTypeId: string;    tags?: string[];  }) {    const response = await this.client.post('/memory-fragments', data);    return response.data;  }}
export const usableApi = new UsableApiService();Explanation:
- Line 6-12: Initialize axios client with Usable API URL and auth header
 - Line 14-22: Create fragment method that POSTs to Usable API
 - Line 25: Export singleton instance for easy imports
 
Step 8: Create Discord Bot
Create src/bot.ts:
import { Client, GatewayIntentBits, Events } from 'discord.js';import { usableApi } from './services/usable-api.service';
const client = new Client({  intents: [    GatewayIntentBits.Guilds,    GatewayIntentBits.GuildMessages,    GatewayIntentBits.MessageContent,  ],});
// Bot ready eventclient.once(Events.ClientReady, (c) => {  console.log(`✅ Bot is ready! Logged in as ${c.user.tag}`);});
// Listen for new threads in forum channelsclient.on(Events.ThreadCreate, async (thread) => {  // Only process forum threads  if (!thread.parent || thread.parent.type !== 15) return;
  try {    // Fetch starter message    const starterMessage = await thread.fetchStarterMessage();
    // Create Usable fragment    const fragment = await usableApi.createFragment({      title: thread.name,      content: `## Discord Forum Post\n\n**Author:** ${starterMessage.author.username}\n**Server:** ${thread.guild.name}\n**Channel:** ${thread.parent.name}\n\n---\n\n${starterMessage.content}`,      workspaceId: process.env.USABLE_WORKSPACE_ID!,      fragmentTypeId: process.env.USABLE_ISSUE_FRAGMENT_TYPE_ID!,      tags: ['discord', 'forum-post', `server:${thread.guild.name}`],    });
    // Reply to thread    await thread.send(      `✅ **Issue registered in Usable!**\n📝 Fragment ID: \`${fragment.fragmentId}\`\n📌 Title: ${fragment.title}`    );  } catch (error) {    console.error('Error:', error);    await thread.send('❌ Failed to register issue in Usable.');  }});
// Login to Discordclient.login(process.env.DISCORD_BOT_TOKEN);Explanation:
- Line 4-10: Initialize Discord client with required intents
 - Line 17-43: Listen for forum thread creation events
 - Line 19: Filter to only forum channels (type 15)
 - Line 23: Fetch the first message in the thread
 - Line 26-32: Create Usable fragment with formatted content
 - Line 35-37: Reply to thread with confirmation
 
Step 9: Run the Bot
Development:
bun run src/bot.tsExpected output:
✅ Bot is ready! Logged in as Usable Bot#1234Step 10: Test the Integration
- Go to your Discord server
 - Create a Forum Channel if you don’t have one
 - Create a new Forum Post:
- Title: “Test: Integration Works”
 - Message: “Testing Usable integration!”
 
 - Wait 2-3 seconds
 - Bot should reply with fragment ID
 
Expected bot reply:
✅ Issue registered in Usable!📝 Fragment ID: `abc123-def456-...`📌 Title: Test: Integration Works- Go to Usable and search for the fragment
 - Verify it contains the Discord context
 
Code Examples
Basic Example
Scenario: Minimal bot that creates fragments from forum posts
import { Client, GatewayIntentBits } from 'discord.js';import axios from 'axios';
const client = new Client({  intents: [    GatewayIntentBits.Guilds,    GatewayIntentBits.MessageContent,  ],});
client.on('threadCreate', async (thread) => {  if (thread.parent?.type === 15) { // Forum channel    const msg = await thread.fetchStarterMessage();
    await axios.post(      'https://api.usable.dev/api/memory-fragments',      {        title: thread.name,        content: msg.content,        workspaceId: 'your-workspace-id',        fragmentTypeId: 'your-fragment-type-id',      },      {        headers: {          Authorization: `Bearer ${process.env.USABLE_API_KEY}`,        },      }    );  }});
client.login(process.env.DISCORD_BOT_TOKEN);Advanced Example with Error Handling
Scenario: Production-ready implementation with retry logic
import { Client, ThreadChannel } from 'discord.js';import axios, { AxiosError } from 'axios';
async function createFragmentWithRetry(  data: FragmentData,  retries = 3): Promise<Fragment> {  for (let i = 0; i < retries; i++) {    try {      const response = await axios.post(        'https://api.usable.dev/api/memory-fragments',        data,        {          headers: {            Authorization: `Bearer ${process.env.USABLE_API_KEY}`,          },        }      );      return response.data;    } catch (error) {      if (i === retries - 1) throw error;      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));    }  }}
client.on('threadCreate', async (thread: ThreadChannel) => {  try {    const fragment = await createFragmentWithRetry({      title: thread.name,      content: await formatThreadContent(thread),      workspaceId: process.env.USABLE_WORKSPACE_ID!,      fragmentTypeId: process.env.USABLE_ISSUE_FRAGMENT_TYPE_ID!,    });
    await thread.send(`✅ Created: ${fragment.fragmentId}`);  } catch (error) {    console.error('Failed after retries:', error);    await thread.send('❌ Error creating fragment');  }});Common Patterns
Pattern 1: Rich Metadata Formatting
Use when: You want to preserve Discord context in fragments
function formatThreadContent(thread: ThreadChannel, message: Message): string {  return `## Discord Thread Message
**Server:** ${thread.guild.name}**Channel:** ${thread.parent?.name}**Thread:** ${thread.name}**Author:** ${message.author.username}**Posted:** ${message.createdAt.toISOString()}
---
${message.content}`;}Benefits:
- Full context preserved
 - Easy to search in Usable
 - Clear formatting
 
Pattern 2: Tag Generation
Use when: You want automatic, consistent tagging
function generateTags(thread: ThreadChannel): string[] {  const tags = ['discord', 'forum-post'];
  if (thread.guild.name) {    tags.push(`server:${thread.guild.name.toLowerCase().replace(/\s+/g, '-')}`);  }
  if (thread.parent?.name) {    tags.push(`channel:${thread.parent.name.toLowerCase().replace(/\s+/g, '-')}`);  }
  return tags;}Configuration
Required Environment Variables
| Variable | Type | Description | 
|---|---|---|
DISCORD_BOT_TOKEN | string | Bot authentication token from Discord | 
USABLE_API_KEY | string | API key from Usable settings | 
USABLE_WORKSPACE_ID | string | Target workspace UUID | 
USABLE_ISSUE_FRAGMENT_TYPE_ID | string | Fragment type for issues | 
Optional Variables
| Variable | Type | Default | Description | 
|---|---|---|---|
USABLE_API_URL | string | https://api.usable.dev/api | Usable API endpoint | 
LOG_LEVEL | string | info | Logging verbosity | 
NODE_ENV | string | development | Environment mode | 
Diagrams
Architecture Diagram
graph TD    A[Discord User] -->|Create Post| B[Discord Forum]    B -->|threadCreate Event| C[Discord Gateway]    C -->|WebSocket| D[Discord Bot]    D -->|Format Data| E[Usable API Client]    E -->|POST /memory-fragments| F[Usable API]    F -->|Fragment Created| E    E -->|Fragment ID| D    D -->|Send Message| B    B -->|Bot Reply| AEvent Flow
sequenceDiagram    participant User    participant Discord    participant Bot    participant Usable
    User->>Discord: Create forum thread    Discord->>Bot: ThreadCreate event    Bot->>Discord: Fetch starter message    Discord-->>Bot: Message content    Bot->>Bot: Format for Usable    Bot->>Usable: Create fragment    Usable-->>Bot: Fragment ID    Bot->>Discord: Reply to thread    Discord-->>User: See confirmationBest Practices
✅ Do’s
- Use environment variables for all secrets
 - Validate data before sending to Usable
 - Include error handling for network failures
 - Log events for debugging and monitoring
 - Format content with Discord context
 - Test locally before deploying
 
❌ Don’ts
- Don’t commit 
.envfiles or secrets - Don’t skip Message Content Intent
 - Avoid blocking the main thread with sync operations
 - Don’t ignore rate limits
 - Never hardcode API keys in source code
 
💡 Tips
- Use TypeScript for type safety
 - Implement retry logic for API calls
 - Add health checks for monitoring
 - Store thread-to-fragment mapping in a database for updates
 - Use structured logging for production
 
Troubleshooting
Issue: Bot doesn’t respond to forum posts
Symptoms:
- Bot shows as online
 - No reply when creating forum threads
 - No errors in console
 
Cause: Missing Message Content Intent or wrong channel type
Solution:
- Check Discord Developer Portal → Bot → Privileged Gateway Intents
 - Ensure “Message Content Intent” is enabled
 - Verify forum channel type in code:
 
if (thread.parent?.type !== 15) {  console.log('Not a forum channel, skipping');  return;}Prevention: Always enable required intents before launching bot
Issue: Usable API returns 401 Unauthorized
Symptoms:
- Error: “Request failed with status code 401”
 - Fragment not created
 - Bot sends error message to Discord
 
Cause: Invalid or expired API key
Solution:
# Regenerate API key in Usable# Update .env fileUSABLE_API_KEY=usa_key_new_key_here
# Restart botPrevention: Use environment validation on startup
Issue: Fragment created but missing content
Symptoms:
- Fragment exists in Usable
 - Content field is empty or null
 - Title is correct
 
Cause: Starter message fetch failed or returned null
Solution:
const starterMessage = await thread.fetchStarterMessage();if (!starterMessage) {  console.error('Could not fetch starter message');  return;}Prevention: Always validate fetched data before using it
Performance Considerations
Optimization Tips
- Batch operations if processing multiple threads
 - Cache fragment type IDs to reduce API calls
 - Use connection pooling for HTTP requests
 - Implement rate limiting to avoid Discord/Usable API limits
 - Queue long-running tasks instead of blocking
 
Rate Limits
| Service | Limit | Notes | 
|---|---|---|
| Discord Gateway | 120 events/min | Per bot connection | 
| Discord API | 50 requests/sec | Global rate limit | 
| Usable API | 100 requests/min | Per API key | 
Real-World Examples
Example 1: Support Forum Integration
Scenario: Customer support team uses Discord forum for issues
Implementation:
client.on('threadCreate', async (thread) => {  if (thread.parent?.name === 'customer-support') {    const fragment = await usableApi.createFragment({      title: `[Support] ${thread.name}`,      content: formatSupportTicket(thread),      workspaceId: process.env.USABLE_WORKSPACE_ID!,      fragmentTypeId: process.env.USABLE_ISSUE_FRAGMENT_TYPE_ID!,      tags: ['support', 'customer', thread.guild.name],    });
    await thread.send(      `🎫 Support ticket created!\nTicket ID: ${fragment.fragmentId}`    );  }});Outcome: All support requests automatically tracked in Usable, searchable by team
Example 2: Feature Request Collection
Scenario: Community submits feature requests via Discord
Implementation:
client.on('threadCreate', async (thread) => {  if (thread.appliedTags?.includes('feature-request')) {    await usableApi.createFragment({      title: thread.name,      content: formatFeatureRequest(thread),      fragmentTypeId: process.env.USABLE_FEATURE_FRAGMENT_TYPE_ID!,      tags: ['feature-request', 'community', 'discord'],    });  }});Outcome: Product team has organized list of community feature requests
Related Resources
Documentation
- Discord.js Guide - Official Discord.js documentation
 - Usable API Reference - Usable API endpoints
 - Discord Developer Portal - Discord API docs
 
Tutorials
- Building Discord Bots with TypeScript - Getting started guide
 - Usable API Integration Patterns - Best practices
 
External Resources
- Discord.js Discord Server - Community support
 - Usable Community - Get help
 
FAQs
Q: Can I update fragments when threads receive replies?
A: Yes! Store thread-to-fragment mapping in a database, then listen for messageCreate events in threads and update the corresponding fragment.
Q: What fragment types should I use?
A:
- Support tickets → “Issue” type
 - Feature requests → “Feature Request” type
 - Documentation → “Knowledge” type
 - Tutorials → “Recipe” type
 
Q: How do I handle rate limits?
A: Implement a queue system with exponential backoff. Libraries like bottleneck or p-queue can help manage rate limits automatically.
Q: Can I use this with Slack instead?
A: Yes! The Usable API integration part is the same. You’d just replace the Discord bot code with Slack’s Bolt framework.
Changelog
Version 1.0.0 (2025-10-16)
- Initial documentation
 - Complete tutorial from setup to deployment
 - Code examples for common patterns
 - Troubleshooting guide
 - Production best practices
 
Contributing
Found an issue or want to improve this documentation?
- Report issues in the Usable Public workspace
 - Suggest improvements via Discord community
 - Submit examples of your integration
 
Footer Note: This documentation is part of the Usable Public workspace and is automatically synced to the docs site.