Claude API Discord Bot Setup: A Step-by-Step Guide
Setting up a Claude-powered Discord bot means connecting three pieces: a Discord application with a bot token, a hosting environment that runs your bot code, and a way to call Claude's API to generate replies. This guide walks through each piece in order, including the gotchas that trip people up — rate limits, message length caps, and how to keep the bot responsive while Claude generates a response.
By the end you'll have a working bot that listens for mentions or slash commands in a Discord server, sends the message to Claude, and posts the reply back — with streaming so users see the response build up instead of waiting on a blank screen.
What you need before starting
- A Discord account and a server where you have permission to add bots
- Node.js 18+ (or Python 3.10+, examples below use Node)
- A Claude API key, either directly from Anthropic or through a proxy like SubToAPI that turns your existing Claude access into a standard HTTPS API
discord.jsv14, which handles the gateway connection and slash commands
Step 1: Register the Discord application
Go to the Discord Developer Portal, click New Application, name it, and open the Bot tab. Click Add Bot, then copy the bot token — you'll need it as an environment variable. Under Privileged Gateway Intents, enable Message Content Intent if you want the bot to read regular messages (not just slash commands).
Under OAuth2 → URL Generator, select the bot scope and permissions Send Messages, Read Message History, and Use Slash Commands. Use the generated URL to invite the bot to your server.
Step 2: Set up the project
mkdir claude-discord-bot && cd claude-discord-bot
npm init -y
npm install discord.js dotenv node-fetch
Create a .env file:
DISCORD_TOKEN=your_discord_bot_token
CLAUDE_API_KEY=your_api_key
CLAUDE_API_URL=https://api.subtoapi.app/v1/messages
Using an API gateway like SubToAPI here means your CLAUDE_API_URL and auth header stay the same whether you're testing locally or calling Claude directly — you're just hitting a standard /v1/messages endpoint with a bearer token.
Step 3: Write the bot logic
import { Client, GatewayIntentBits, Events } from "discord.js";
import "dotenv/config";
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
client.once(Events.ClientReady, () => {
console.log(`Logged in as ${client.user.tag}`);
});
client.on(Events.MessageCreate, async (message) => {
if (message.author.bot) return;
if (!message.mentions.has(client.user)) return;
const prompt = message.content.replace(/<@!?\d+>/g, "").trim();
await message.channel.sendTyping();
try {
const reply = await askClaude(prompt);
await sendChunked(message, reply);
} catch (err) {
console.error(err);
await message.reply("Something went wrong talking to Claude.");
}
});
async function askClaude(prompt) {
const res = await fetch(process.env.CLAUDE_API_URL, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CLAUDE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4",
max_tokens: 1024,
messages: [{ role: "user", content: prompt }],
}),
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
const data = await res.json();
return data.content[0].text;
}
// Discord caps messages at 2000 characters, so long replies need splitting
async function sendChunked(message, text) {
const chunks = text.match(/[\s\S]{1,1900}/g) || [text];
for (const chunk of chunks) {
await message.reply(chunk);
}
}
client.login(process.env.DISCORD_TOKEN);
Run it with node index.js (add "type": "module" to package.json, or rename the file .mjs). Mention the bot in a channel and it should reply with a Claude-generated response.
Step 4: Add streaming for faster perceived response
Discord doesn't let you stream text into a single message the way a chat UI does, but you can simulate it by editing a placeholder message every few hundred milliseconds as tokens arrive. If you're using Claude's streaming endpoint, buffer the incoming chunks and call message.edit() on an interval rather than on every token — Discord rate-limits edits to roughly one every 5 seconds per message. See /docs/streaming for the event format if you're building this against SubToAPI's streaming endpoint.
let buffer = "";
let lastEdit = 0;
const placeholder = await message.reply("Thinking…");
// inside your stream handler, for each text chunk:
buffer += chunk;
const now = Date.now();
if (now - lastEdit > 1500) {
await placeholder.edit(buffer.slice(0, 1900));
lastEdit = now;
}
Step 5: Handle rate limits and concurrency
Discord bots in active servers can trigger many Claude calls at once. Two practical patterns:
- Queue per channel. Process one Claude request at a time per channel so replies stay in order.
- Debounce rapid mentions. If a user sends multiple messages in quick succession, wait a second before firing the API call so you don't burn tokens on half-finished thoughts.
If you're routing through SubToAPI, usage metadata comes back with every response, so you can log token counts per server or per user without building your own counter — useful if you're running the bot for multiple Discord communities and want to track cost per guild. Check /docs/messages for the response shape.
Step 6: Deploy
A small bot like this runs fine on a $5/month VPS, a Fly.io instance, or a Railway deployment — anything that keeps a long-lived WebSocket connection open, since Discord bots use a persistent gateway connection rather than webhooks. Avoid serverless functions with short timeouts for the gateway connection itself (they're fine for the Claude call, just not for client.login).
Why use an API gateway instead of calling Claude directly
If you already have Claude access through a subscription rather than a pay-as-you-go API key, you can't normally call the API directly. SubToAPI issues standard sub_live_... API keys backed by that access, so your Discord bot code looks exactly like the examples above — same /v1/messages endpoint, same bearer auth, same streaming format — without needing separate API billing. Sign up and get a key at /signup, or start with /docs/quickstart to see the full request/response cycle before wiring it into Discord.
questions
Do I need a paid Claude API key to build a Discord bot? You need some way to send authenticated requests to Claude's Messages API. That can be a direct Anthropic API key or a service like SubToAPI that exposes your existing Claude access as a standard API key.
Why does my bot not respond to messages? Usually it's a missing intent. Enable Message Content Intent in the Discord Developer Portal and make sure GatewayIntentBits.MessageContent is included in your client setup — without it, message.content arrives empty.
Can I stream Claude's response into Discord in real time? Not token-by-token, since Discord rate-limits message edits. Buffer the stream and edit a placeholder message every 1-2 seconds for a near-real-time effect without hitting rate limits.