Claude API Integration with Discord Bot: A How-To Guide
Integrating Claude into a Discord bot means connecting your bot's message handler to Claude's API so it can read incoming Discord messages and reply with generated text, summaries, or tool-driven actions. The core pattern is simple: your bot listens for messages, forwards the relevant text to Claude over HTTPS, and sends the response back to the channel — the hard part is doing this reliably at scale, with streaming, rate limits, and multiple server members hitting the bot at once.
This guide walks through a working setup using discord.js, covers how to stream Claude's output into Discord messages so users don't wait on a long reply, and shows how to add tool calling so the bot can do things like look up data or run commands instead of just chatting.
What you need before you start
- A Discord application and bot token from the Discord Developer Portal
- Node.js 18+ and
discord.jsinstalled - An API key for Claude — either directly from Anthropic or through a proxy like SubToAPI that gives you a
sub_live_...key tied to your existing Claude access - A plan for conversation state: Discord channels aren't sessions, so you need to decide how much history to send with each request
If you're building this for a team — a community server, an internal support bot, a game server — managing raw Anthropic credentials across multiple bot instances gets messy fast. SubToAPI wraps your Claude access into a standard REST API with its own keys, so each bot (or each environment: dev, staging, prod) can have its own revocable key without sharing the underlying account. See /docs/quickstart for the setup flow.
Basic bot skeleton
Start with a minimal discord.js bot that reacts to mentions:
import { Client, GatewayIntentBits, Events } from "discord.js";
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
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();
const reply = await askClaude(prompt);
await message.reply(reply);
});
client.login(process.env.DISCORD_TOKEN);
Everything interesting happens inside askClaude.
Calling Claude from the bot
A single request/response call is the easiest starting point. Here it's wired against SubToAPI's Messages endpoint, which follows the same request shape as the standard Claude Messages API:
async function askClaude(userMessage) {
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: 500,
messages: [{ role: "user", content: userMessage }],
}),
});
const data = await res.json();
return data.content[0].text;
}
Full parameter details and response shapes are in /docs/messages. This works fine for short replies, but Discord has a ~2000 character message limit and users notice a multi-second silence before anything appears. For a better experience, stream the response and edit the message as tokens arrive.
Streaming replies into Discord
Discord doesn't support true token-by-token streaming in the UI, but you can simulate it by editing a sent message every few hundred milliseconds as chunks come in:
async function streamClaudeReply(message, userMessage) {
const sent = await message.reply("…");
let buffer = "";
let lastEdit = Date.now();
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: 500,
stream: true,
messages: [{ role: "user", content: userMessage }],
}),
});
for await (const chunk of res.body) {
const text = decodeChunk(chunk); // parse SSE "delta" events
if (text) buffer += text;
if (Date.now() - lastEdit > 700) {
await sent.edit(buffer.slice(0, 1990));
lastEdit = Date.now();
}
}
await sent.edit(buffer.slice(0, 1990));
}
The exact SSE event parsing (decodeChunk above) follows the same content_block_delta structure as the standard Claude streaming API — see /docs/streaming for the event types and a full parser example. Throttling edits to roughly one per 700ms keeps you well under Discord's rate limits for message edits while still feeling live.
Giving the bot tools
A Discord bot that can only chat is limited. Tool calling lets Claude trigger real actions — rolling dice, querying a database, checking a ticket system — by returning structured calls instead of plain text:
const tools = [
{
name: "roll_dice",
description: "Roll N dice with S sides",
input_schema: {
type: "object",
properties: {
count: { type: "integer" },
sides: { type: "integer" },
},
required: ["count", "sides"],
},
},
];
Pass tools in the request body, check the response for a tool_use block, run the matching function in your bot code, and send the result back as a tool_result message in a follow-up call. The full schema and multi-turn flow are documented in /docs/tools.
Handling multiple servers and rate limits
If your bot runs across many guilds, concurrent requests from different channels can hit rate limits quickly. A few practical rules:
- Queue requests per-channel rather than per-bot, so one busy channel doesn't starve others
- Cap conversation history per channel (e.g., last 10 messages) to control token usage and latency
- Log token usage per guild if you're running this commercially — SubToAPI exposes usage metadata per request so you can attribute cost per server or per team seat without building your own metering
- Use separate API keys for dev and prod bots so a runaway test instance can't exhaust your production quota
If you're distributing the bot to multiple teams or clients, SubToAPI's per-seat plans (Solo, Team, Scale — see /pricing) let you issue scoped keys per project instead of sharing one Anthropic account across everything you ship.
Questions
Do I need an Anthropic account to connect Claude to Discord? You need access to Claude in some form. You can call Anthropic's API directly with your own key, or use a service like SubToAPI that turns existing Claude access into a standard HTTPS API key you can drop into your bot's environment variables.
Why does my bot's reply arrive all at once instead of streaming? Discord's message edit rate limits mean you can't stream token-by-token like a web UI. The common workaround is buffering chunks and editing the sent message every 500–1000ms until the full response arrives.
How do I keep conversation context across multiple Discord messages? Store a short history (last N messages) per channel or per user, and send it as the messages array on each request. Trim or summarize older turns to avoid exceeding context limits and to keep latency low.