Claude API Discord Bot Setup Tutorial
Building a Discord bot that talks to Claude involves three pieces: a Discord application with a bot token, a Node.js (or Python) process listening for messages, and an API key that lets your code call Claude. This tutorial covers all three, using discord.js and a Claude-compatible endpoint, so you end up with a working bot that answers questions, holds short conversations, and can be extended with tools later.
The fastest path to a reliable setup is separating "Discord plumbing" from "Claude calls." You'll write a small Discord event handler that forwards user messages to an API endpoint and posts the response back. That endpoint can be Anthropic's API directly, or a gateway like SubToAPI that gives you a stable sub_live_... key, streaming, and usage tracking without you having to manage API key rotation or billing dashboards yourself.
Step 1: Create the Discord Application
- Go to the Discord Developer Portal and create a New Application.
- Under Bot, click Add Bot and copy the bot token — you'll need it as an environment variable.
- Under Privileged Gateway Intents, enable Message Content Intent. Without this, your bot can't read message text.
- Generate an invite URL under OAuth2 → URL Generator with the
botscope and permissions forSend MessagesandRead Message History. Use that URL to add the bot to your test server.
Step 2: Scaffold the Bot Project
Set up a minimal Node.js project:
mkdir claude-discord-bot && cd claude-discord-bot
npm init -y
npm install discord.js node-fetch dotenv
Create a .env file:
DISCORD_TOKEN=your-discord-bot-token
SUBTOAPI_KEY=sub_live_xxxxxxxxxxxxxxxx
If you're using Anthropic's API directly instead, swap in your own key name and endpoint — the bot logic below stays almost identical.
Step 3: Handle Messages and Call Claude
Create index.js:
import { Client, GatewayIntentBits } from "discord.js";
import fetch from "node-fetch";
import "dotenv/config";
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
client.on("messageCreate", async (message) => {
if (message.author.bot) return;
if (!message.mentions.has(client.user)) return;
const prompt = message.content.replace(/<@!?\d+>/g, "").trim();
if (!prompt) return;
await message.channel.sendTyping();
try {
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
},
body: JSON.stringify({
model: "claude-3-5-sonnet",
max_tokens: 500,
messages: [{ role: "user", content: prompt }],
}),
});
const data = await response.json();
const reply = data.content?.[0]?.text ?? "Sorry, I couldn't generate a reply.";
await message.reply(reply.slice(0, 2000));
} catch (err) {
console.error(err);
await message.reply("Something went wrong talking to Claude.");
}
});
client.login(process.env.DISCORD_TOKEN);
This bot responds only when mentioned, strips the mention tag from the prompt, shows a typing indicator, and truncates the reply to Discord's 2000-character message limit. Run it with:
node index.js
Mention the bot in your test server (@YourBot what's the capital of Peru?) and you should get a reply within a few seconds.
Step 4: Handle Long Responses with Streaming
Claude responses can exceed Discord's message length or take a few seconds to generate fully. Instead of waiting for the full response, you can stream tokens and edit the message progressively, which feels much more responsive in a chat context. If you're using SubToAPI, streaming works over server-sent events — see the full reference in the streaming docs. A simplified pattern:
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
},
body: JSON.stringify({
model: "claude-3-5-sonnet",
max_tokens: 500,
stream: true,
messages: [{ role: "user", content: prompt }],
}),
});
let buffer = "";
let sentMessage = await message.reply("Thinking...");
for await (const chunk of res.body) {
buffer += chunk.toString();
// parse SSE events, extract text deltas, append to buffer
if (buffer.length % 200 === 0) {
await sentMessage.edit(buffer.slice(0, 2000));
}
}
await sentMessage.edit(buffer.slice(0, 2000));
Editing a message too often will hit Discord's rate limits, so batch edits every few hundred characters or every second rather than on every token.
Step 5: Add Conversation Memory (Optional)
By default, each mention is treated as a fresh prompt. To support multi-turn conversations per channel or thread, keep a small in-memory map of recent messages:
const history = new Map(); // channelId -> messages[]
function getHistory(channelId) {
if (!history.has(channelId)) history.set(channelId, []);
return history.get(channelId);
}
Append each user message and Claude's reply to the array for that channel, cap it at the last 10–15 messages, and send the whole array as messages in your request. For production bots, back this with Redis or a database instead of an in-memory map, since the process restarting wipes the history.
Step 6: Give the Bot Tools (Optional)
If you want the bot to look up server data, call an internal API, or run commands, use tool use instead of parsing free text. Define a tool schema in your request and handle the tool_use response block before replying — the tool use docs walk through the request/response shape in detail.
Step 7: Deploy
For a small server, a single process on a low-cost VPS or a platform like Railway or Fly.io is enough — just keep DISCORD_TOKEN and your API key as environment secrets, never in code. For larger bots serving multiple guilds, consider sharding with discord.js's built-in ShardingManager once you cross a few thousand guilds.
If you're choosing between Anthropic's direct API and a gateway, the main trade-off is operational: a gateway like SubToAPI gives you a single sub_live_... key per bot, usage metadata per request, and team seats if multiple people manage the bot, without you building billing or key-rotation logic yourself. Check pricing or start with a free trial if that matters for your project. The quickstart guide and messages API reference cover the request format if you want to go beyond this tutorial.
questions
Do I need a paid Discord developer account to run a Claude bot? No. Creating a Discord application and bot token is free. You only pay for the Claude API usage itself, whether through Anthropic directly or a gateway service.
Why isn't my bot reading message content? Enable the Message Content Intent in the Discord Developer Portal under your bot's settings, and make sure your client includes GatewayIntentBits.MessageContent when initializing.
How do I stop the bot from responding to every message in a channel? Check message.mentions.has(client.user) before processing, so the bot only replies when explicitly mentioned, or restrict it to a specific channel ID or slash command instead of listening on all messageCreate events.