Skip to main content

Discord

Everything specific to connecting a Discord bot: what to set up on Discord's side, the two flows a bot can serve, who may invoke it, and why a silent bot is usually silent.

Overview

Discord is bring-your-own-app: you create a Discord application, and connecting it supplies its application_id and its bot token. The token is write-only like every other channel credential — reads report has_credential, never the value — and an application backs exactly one channel, so a second connect for the same application_id is a 409 application_in_use.

Everything a channel does regardless of kind — the default action, the route table, conversations, addresses — is on the Channels page. This page is only the Discord half.

To connect one end to end, follow Connect a Discord channel.

Key Concepts

Discord-side setup

Three things happen on Discord's site, not naturali's API. The third is the one people skip.

  1. Create the application at the Discord Developer Portal and copy its Application ID from General Information.
  2. Create the bot token on the Bot tab (Reset Token). Discord shows it exactly once.
  3. Add the bot to a server. On the OAuth2 tab, use the URL Generator: tick the bot scope, tick the permissions from the table below, then open the generated URL and pick a server.
A bot that is in no server cannot be reached at all

Step 3 is not optional, including for the DM-only flow: Discord lets a person open a DM with a bot only when they share a server with it. Skip it and the bot is unreachable everywhere, with nothing to see on naturali's side — no conversation is created, because no message was ever delivered.

Permissions the bot needs

Permissions are a separate Discord concept from intents, granted when you invite the bot (and overridable per channel in the server's settings). Grant only what the flows you enable actually use:

PermissionNeeded forWhat breaks without it
View Channelmention_threadsThe bot never receives messages in that channel, so a mention does nothing.
Create Public Threadsmention_threadsThe mention is seen, the thread is never opened, and no reply is sent.
Send Messages in Threadsmention_threadsThe thread opens and then stays empty — the reply cannot be posted.

The dms flow needs none of these: a direct message is not in a server, so no server permission applies to it — only the shared-server requirement above.

A per-channel override in the server's settings outranks what the invite granted, so a bot that works in one channel and not another is usually a channel-level permission difference, not a naturali configuration difference.

Two flows, chosen with modes

modes picks which flows the bot serves:

ModeShapeIntent it needs
dmsThe bot converses 1:1 in direct messages — the WhatsApp shape, one conversation per user.DIRECT_MESSAGES, non-privileged.
mention_threadsAn allowed member @mentions the bot in a server; naturali opens a public thread and answers inside it. The thread is the conversation.MESSAGE_CONTENT, privileged.

dms is on and mention_threads is off by default, and the reason is the privileged intent: the @mention itself arrives with its content, but the follow-up messages inside the thread do not, so the thread flow only works once you enable MESSAGE_CONTENT on your own application — and Discord requires bot verification past roughly 100 servers. A channel identifies with only the intents its enabled flows need, so a DM-only bot never asks for the privileged one. The other intents the thread flow uses (GUILDS, GUILD_MESSAGES) are not privileged and need nothing enabled in the portal.

A thread is one conversation shared by several people, which is worth knowing before you enable it: everyone in the thread talks to the same session and its memory, so it is the right shape for a shared support thread and the wrong one for anything personal.

PATCH /v1/projects/{project_id}/channels/{channel_id} rotates the bot token, flips the modes, or disables the channel. Any of those cycles the bot's connection, so a change takes effect within about half a minute rather than instantly.

Turn the thread flow on — after enabling MESSAGE_CONTENT on the application:

naturali update-channel \
--channel-id chan_V1StGXR8Z5jdHi6B \
--modes '{ "dms": true, "mention_threads": true }'

Mentioning the bot: the user, not the role

The thread flow opens on an @mention of the bot user. When an app joins a server Discord also creates a role with the same name, and the two render identically in the message — same text, same colour, same chip. Only the user mention starts a thread; a role mention is ignored, silently and with nothing recorded.

When you type @, pick the entry showing the bot's avatar and the APP badge. To tell them apart after the fact, click the mention in the posted message: the bot user opens a profile card, the role does not.

This applies only to the message that opens the thread. Replies inside a thread the bot already owns need no mention at all — that is exactly what the MESSAGE_CONTENT intent buys, and why a bot can look like it answers in threads but ignores the channel.

Who may talk to a Discord bot

Server messages carry the sending member's roles, so admission is decided before the agent is invoked — an unlisted member costs you nothing. It is configured on the resolved action's config — today that means the channel default's config (a route's own config works identically once you have one):

{
"discord": {
"allowed_role_ids": ["1290000000000000042"],
"allowed_user_ids": ["112233445566778899"]
}
}

A member is admitted by matching either list. Empty or absent lists mean everyone, so the default stays open — set at least one when the server is not already a trusted room.

When the bot does not answer

A Discord bot fails quietly by design: a message that is not the bot's to answer is dropped rather than replied to with an error, so "no reply" is one symptom with several causes. Work down this list — it is ordered by how often each one is the answer.

SymptomLikely cause
Nothing works anywhere, DMs includedThe bot was never added to a server — see Discord-side setup.
A mention in a server channel does nothingThe mention hit the role, not the bot user — see Mentioning the bot.
Mentions do nothing, in every channelmention_threads is off, or MESSAGE_CONTENT is not enabled on the application.
The first mention works, follow-ups in the thread are ignoredMESSAGE_CONTENT is not enabled — the opening mention carries its content, the follow-ups do not.
A thread opens but stays emptyMissing Send Messages in Threads in that channel.
It works in one channel, not anotherA per-channel permission override in the server's settings.
It stopped right after a connect or a modes changeThe worker picks the change up on its next poll — give it about half a minute.
The reply arrives but carries the model's reasoning or JSONThe reply is delivered verbatim, so this is the agent, not the channel — constrain it in the instructions, and see Do not bind an agent that has an output_schema.

One read separates "never arrived" from "arrived and failed": GET /v1/projects/{project_id}/channels/{channel_id}/conversations.

naturali list-channel-conversations --channel-id chan_V1StGXR8Z5jdHi6B

A row whose identifier matches the surface you tried — discord:dm:<user_id> or discord:thread:<guild_id>:<channel_id> — means the message reached naturali and resolved; the problem is after that point, so look at the agent and at the bot's permission to post its reply. No row means the message never got that far: everything above the fold in the table — invite, mention target, modes, intent, View Channel.

Reading the conversation's messages (GET /v1/projects/{project_id}/channels/{channel_id}/conversations/{conversation_id}/messages) narrows it once more: a user message with no assistant message after it puts the failure between the agent and delivery, not on the way in.