← Blog

Claude API Integration with Discord Bot: A How-To Guide

2026-10-15 · 5 min read · SubToAPI Team

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

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:

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.

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 →