$ lanes link connect discord --profile personal --workspace localIt 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_messageposts 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_webhooktakesusernameandavatar_urlon 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.
| Capability | What it does |
|---|---|
discord.get_my_user | Which application this token is |
discord.list_my_guilds | The servers the bot was added to. An empty list means the invite was missed |
discord.get_guild | One server, with optional member counts |
discord.list_guild_channels | How a channel name becomes the id every other call needs |
discord.get_channel | One channel: name, type, topic, category |
discord.list_messages | The triage read, and the only one. Pages on before and after |
discord.get_message | One message in full, with reactions and embeds |
discord.list_pins | What has been marked |
discord.list_message_reactions_by_emoji | Who reacted with one emoji |
discord.get_active_guild_threads | Every open thread in a server at once |
discord.create_message | Post as the application |
discord.update_message | Edit its own message. Discord shows an "edited" marker regardless |
discord.delete_message | The retraction, one message by id. There is deliberately no bulk delete |
discord.crosspost_message | Publish an announcement post to the servers that follow it |
discord.add_my_message_reaction | Mark a message seen or triaged. Notifies nobody |
discord.create_pin | The heavier mark. Needs Manage Messages, and 50 per channel is the cap |
discord.create_thread_from_message | Turn a post into a discussion |
discord.list_channel_webhooks | Find an existing webhook before making another |
discord.create_webhook | One per channel, once |
discord.execute_webhook | Post 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:
$ lanes link policy deny discord.delete_message discord.create_webhook --profile personal --workspace localThere 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
- Open https://discord.com/developers/applications and choose New Application. The name you give it is the name on every post.
- 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.
- Open the Bot tab and set the username.
- 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
PRESENCEandSERVER MEMBERSoff; nothing here uses them. - 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:
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:
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:
discord.list_channel_webhookson the channel. If one is already there, use it. A channel holds at most 15.discord.create_webhookif not. Keep theidandtokenit returns.discord.execute_webhookwithusernameandavatar_urlset 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:
$ 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 localRe-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 see | What it is |
|---|---|
401 on everything, get_my_user included | The 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 empty | The MESSAGE CONTENT intent is off. Bot tab, Privileged Gateway Intents. No error mentions it |
403 on one channel, fine elsewhere | The bot is in the server but not that channel. A private channel does not inherit |
crosspost_message fails | It only works on an announcement channel (type: 5) and on a message already posted there |
create_pin fails with 403 | Pinning needs Manage Messages, and a channel holds at most 50 pins |
A webhook post returns 400 | username 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.