Docs/Lanes Link/Providers/Discord

Discord

Post announcements and read channels, as a bot application you own

console
$ lanes link connect discord --profile personal --workspace local

It asks for one thing, a bot token from Discord's developer portal. Everything else is choices you make in that portal, plus an invite link you open once per server.

Read this part first

Discord has no API for acting as your own account. Automating a user token is self-botting, their terms forbid it, and accounts get terminated for it. Every legitimate integration acts as an application, and an application's messages carry an APP badge next to the name. No setting, permission, or endpoint removes it.

What you can control is the name and the avatar, two ways:

  • As the application. create_message posts under the application's own name and icon, which you set once in the portal. Set them to your name and photo and posts read as you, with the badge.
  • As anything, per message. execute_webhook takes username and avatar_url on each call, so one channel can carry posts under different names. This is the closer match to "post as myself". See Posting under your own name.

Reading has one gate that will waste an afternoon if you miss it: the Message Content intent. Without it every message comes back with an empty content, the call still returns 200, and nothing says why.

What your agent can do

Twenty operations, out of 242 in Discord's API.

CapabilityWhat it does
discord.get_my_userWhich application this token is
discord.list_my_guildsThe servers the bot was added to. An empty list means the invite was missed
discord.get_guildOne server, with optional member counts
discord.list_guild_channelsHow a channel name becomes the id every other call needs
discord.get_channelOne channel: name, type, topic, category
discord.list_messagesThe triage read, and the only one. Pages on before and after
discord.get_messageOne message in full, with reactions and embeds
discord.list_pinsWhat has been marked
discord.list_message_reactions_by_emojiWho reacted with one emoji
discord.get_active_guild_threadsEvery open thread in a server at once
discord.create_messagePost as the application
discord.update_messageEdit its own message. Discord shows an "edited" marker regardless
discord.delete_messageThe retraction, one message by id. There is deliberately no bulk delete
discord.crosspost_messagePublish an announcement post to the servers that follow it
discord.add_my_message_reactionMark a message seen or triaged. Notifies nobody
discord.create_pinThe heavier mark. Needs Manage Messages, and 50 per channel is the cap
discord.create_thread_from_messageTurn a post into a discussion
discord.list_channel_webhooksFind an existing webhook before making another
discord.create_webhookOne per channel, once
discord.execute_webhookPost with username and avatar_url set per message

That list is the boundary, not a starting point. connect grants discord.*, and policy has no pattern between a whole provider and one exact name, so those twenty are everything an agent can reach. Excluded on purpose: bulk_delete_messages, every moderation endpoint, and everything under roles, invites, and guild settings.

To go narrower, deny what you do not want. A deny beats an allow regardless of order:

console
$ lanes link policy deny discord.delete_message discord.create_webhook --profile personal --workspace local

There is no message search. Discord does not offer one to applications. Finding something means paging list_messages per channel and filtering yourself. Attachments are not available either: Discord takes files as multipart/form-data, which this connector does not encode.

Setting up the application

  1. Open https://discord.com/developers/applications and choose New Application. The name you give it is the name on every post.
  2. On General Information, set the icon. That is the avatar on every post. Copy the Application ID while you are here, because the invite link needs it.
  3. Open the Bot tab and set the username.
  4. Still on Bot, under Privileged Gateway Intents, switch on MESSAGE CONTENT. An application in fewer than 10,000 servers can just toggle it, with no review. Leave PRESENCE and SERVER MEMBERS off; nothing here uses them.
  5. Turn Public Bot off, unless you want other people able to add it to their servers.

The token

On the Bot tab, choose Reset Token and copy what it shows you. Discord shows it once.

Paste it with the word Bot and a space in front:

Code
Bot MTIzNDU2Nzg5MDEyMzQ1Njc4.GhIjKl.mNoPqRsTuVwXyZ

That prefix is Discord's authentication scheme, the way Bearer is most other vendors'. The stored value goes into the Authorization header exactly as you type it, so a token pasted bare produces a 401 on every call with nothing in it to say what is wrong. If something is refusing to authenticate, check this first.

The token is stored encrypted at discord/<connection> in the credential store, never in config.

Inviting it to a server

Take the Application ID from step 2 and open:

Code
https://discord.com/oauth2/authorize?client_id=<application-id>&scope=bot&permissions=309774593088

Pick a server you own and authorise. Repeat per server.

Those permission bits are exactly what the twenty operations need, and no more: View Channels, Send Messages, Send Messages in Threads, Read Message History, Add Reactions, Manage Messages (Discord puts pinning behind it), Create Public Threads, and Manage Webhooks. There is no kick, ban, timeout, role, or channel-management bit in there.

A private channel needs the bot added to it separately. Server-wide permissions do not reach a channel the bot cannot see, so add it under that channel's own permission settings.

Posting under your own name

create_message posts as the application. To post as you:

  1. discord.list_channel_webhooks on the channel. If one is already there, use it. A channel holds at most 15.
  2. discord.create_webhook if not. Keep the id and token it returns.
  3. discord.execute_webhook with username and avatar_url set to whatever the post should wear.

A webhook token is a credential. Anybody holding it can post to that channel with no other authentication, and steps 1 and 2 both return it in their response, which means it reaches the agent and whatever the agent is talking to. That is recorded as a NOT-GUARANTEED row in the security model rather than glossed over. It is withheld from the audit log, and it is bounded: one channel, no read access, nothing else.

To revoke one, go to the channel's Integrations → Webhooks settings in Discord itself. Deleting the webhook there invalidates the token immediately.

Connecting without a terminal to answer

The connection is named by a label you type, so a scripted run has to supply one:

console
$ printf 'Bot %s' "$DISCORD_BOT_TOKEN" | lanes link secrets set discord/main --profile personal
$ lanes link connect discord --display-name "Announcer" --non-interactive --profile personal --workspace local

Re-running connect with the same --display-name repairs the existing connection. A different label makes a second one, which is how you end up with announcer2.

Troubleshooting

What you seeWhat it is
401 on everything, get_my_user includedThe Bot prefix is missing, nine times out of ten. Otherwise the token was invalidated by a later Reset Token. Re-run connect --replace
Reads work, every content is emptyThe MESSAGE CONTENT intent is off. Bot tab, Privileged Gateway Intents. No error mentions it
403 on one channel, fine elsewhereThe bot is in the server but not that channel. A private channel does not inherit
crosspost_message failsIt only works on an announcement channel (type: 5) and on a message already posted there
create_pin fails with 403Pinning needs Manage Messages, and a channel holds at most 50 pins
A webhook post returns 400username has to be 1 to 80 characters, and not "Clyde" or "Discord"

The empty-content case can look intermittent, because content is always available for messages that mention the application, messages in DMs with it, and its own messages.

What is recorded

Guild, channel, message, and webhook ids are kept, along with cursors, allowed_mentions, and execute_webhook's username. Message content, embeds, components, attachments, polls, thread names, and webhook_token are withheld.

allowed_mentions is kept on purpose. It is not content, it is blast radius. Whether a post was permitted to ping @everyone is exactly the thing you want in the log, and it is unrecoverable once the message is edited. See the audit log.


Next: every provider, or Add it to your agent.