← Blog

How to Build a Claude-Powered Discord Bot

2026-11-02 · 5 min read · SubToAPI Team

Building a Claude-powered Discord bot comes down to three parts: a Discord client that listens for messages, an HTTP client that sends those messages to Claude, and some glue code that manages conversation history and rate limits. None of this is complicated, but there are a handful of details — streaming into Discord's message edit limits, handling multi-user channels, and avoiding API throttling — that trip people up on the first attempt.

This guide walks through the full setup using discord.js for the bot layer and a Claude-compatible HTTPS endpoint for the AI layer, with working code you can copy into a real project today.

Architecture overview

A Claude Discord bot needs to:

  1. Connect to Discord's gateway and listen for messageCreate events (or slash commands).
  2. Build a prompt from the message, optionally including recent channel history.
  3. Call Claude and get a response, ideally streamed so users see text appear live.
  4. Edit the Discord message incrementally as tokens arrive, or send the full reply at once.
  5. Track per-channel or per-user conversation state so the bot has memory within a thread.

The tricky part for most developers isn't the Discord side — it's getting Claude access into a format that's easy to call from a Node.js backend without wrestling with SDK auth flows or managing separate credentials for every environment. That's exactly what SubToAPI is for: it turns your existing Claude access into a standard HTTPS API with its own sub_live_... keys, so your bot code just makes normal POST requests.

Setting up the Discord bot

Start with a basic discord.js client:

import { Client, GatewayIntentBits } from "discord.js";

const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
  ],
});

client.once("ready", () => console.log(`Logged in as ${client.user.tag}`));
client.login(process.env.DISCORD_TOKEN);

Create the bot application and invite link through the Discord Developer Portal, and make sure Message Content Intent is enabled — without it, your bot won't receive message text at all.

Calling Claude from the bot

Once a message comes in, send it to Claude. If you're using SubToAPI, the request looks like a normal REST call:

async function askClaude(prompt) {
  const res = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-sonnet-4",
      max_tokens: 1024,
      messages: [{ role: "user", content: prompt }],
    }),
  });

  const data = await res.json();
  return data.content[0].text;
}

Wire it into the message handler:

client.on("messageCreate", async (message) => {
  if (message.author.bot) return;
  if (!message.mentions.has(client.user)) return;

  const prompt = message.content.replace(/<@!?\d+>/, "").trim();
  const reply = await askClaude(prompt);
  await message.reply(reply.slice(0, 2000)); // Discord message limit
});

That's a working bot. Full setup details, authentication, and request/response shapes are in the quickstart and messages docs.

Streaming responses into Discord

Users expect fast feedback, and Claude's full response can take several seconds. Streaming tokens and editing the Discord message as they arrive feels much more responsive:

async function streamToMessage(prompt, discordMessage) {
  const res = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-sonnet-4",
      max_tokens: 1024,
      stream: true,
      messages: [{ role: "user", content: prompt }],
    }),
  });

  let buffer = "";
  let lastEdit = Date.now();

  for await (const chunk of res.body) {
    buffer += parseChunk(chunk); // extract text deltas from SSE events
    if (Date.now() - lastEdit > 800) {
      await discordMessage.edit(buffer.slice(0, 2000));
      lastEdit = Date.now();
    }
  }
  await discordMessage.edit(buffer.slice(0, 2000));
}

Throttle your edits. Discord rate-limits message edits per channel, so editing on every token will get your bot throttled fast — 500–1000ms between edits is a safe interval. See streaming for the exact event format.

Giving the bot memory

For a bot that feels conversational rather than stateless, keep a short rolling history per channel or thread:

const history = new Map(); // channelId -> messages[]

function buildMessages(channelId, userText) {
  const prior = history.get(channelId) || [];
  const messages = [...prior, { role: "user", content: userText }];
  history.set(channelId, messages.slice(-10)); // keep last 10 turns
  return messages;
}

Pass buildMessages(channelId, userText) as the messages array instead of a single string. This is the same pattern used in any multi-turn Claude integration — nothing Discord-specific about it beyond using the channel ID as your session key.

Adding tool use (optional but powerful)

If you want the bot to do things — look up a user's server roles, query a database, fetch weather — define tools and let Claude decide when to call them. This works the same way through a standard HTTPS API; see tools for the request format and how to return tool results back into the conversation.

Rate limits and reliability

Discord bots in active servers can generate bursts of traffic, especially right after launch. A few practical safeguards:

If you're running the bot for a team or across multiple servers, a dashboard with per-key usage and seats makes it much easier to see what's consuming tokens. SubToAPI includes usage metadata on every response and team seats starting on the pricing page, so you're not flying blind once the bot is live.

Deploying the bot

A single Node process running on a small VPS, container, or serverless worker is enough for most servers. Keep the Discord token and your Claude API key in environment variables, never in code, and restart the process automatically on crash (pm2, a systemd service, or a container restart policy all work fine).

questions

Do I need Claude's own SDK to build a Discord bot? No. Any HTTP client works since Claude access is just a REST API under the hood. Using a service like SubToAPI gives you a stable sub_live_... key and standard endpoints, which simplifies auth compared to managing provider-specific credentials.

How do I stop the bot from hitting Discord's message edit rate limit during streaming? Buffer the streamed text and only call message.edit() every 500–1000ms instead of on every token. Send the final edit once the stream completes to make sure no text is lost.

Can the bot remember context across messages in a channel? Yes — store a short rolling array of recent messages keyed by channel or thread ID, and pass that array as the conversation history on each new request instead of sending only the latest message.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →