Connect Discord
Connect Discord to Minion with a bot token for channels or a custom OAuth app for your identity, and see what Minion can't receive yet.
Connecting Discord lets your agents and packages post to channels, read messages and check who you are through the Discord API at discord.com, without the token ever reaching their code.
Minion has no built-in Discord preset. Discord splits its API between bot tokens and user OAuth tokens, so pick the option that fits the job:
- A bot token saved as a secret. Pick this to read or post in a server. This is what most automations need. Bot tokens don't expire until you reset them.
- A custom OAuth app with a user token. Pick this only to read your own identity and server list. A user token can't read or post in channels.
Minion can't receive anything from Discord yet. See "What isn't supported" below.
Before you start
- You need to be an owner or admin of your Minion organization to save a secret or add an OAuth app. Members can use them once they exist.
- You need a Discord account and an application in the Discord developer portal . Click New Application to create one. Both options below start from it.
- To add a bot to a server, you need the Manage Server permission in that server.
Option A: bot token
Create the bot on Discord
- On your application's Bot tab, reset the token to reveal it and copy it.
- If the bot must read message text, turn on the Message Content Intent on the same tab.
- Add the bot to your server. Open this URL with your application's client ID from the OAuth2 tab, pick the server and approve:
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=68608 68608 grants View Channels, Send Messages and Read Message History. Change it if the bot needs more.
Add it in Minion
- Open app.minion.codes/app/secrets .
- Set Name to
discordBotToken. Secret names use letters, digits and underscores only. - Paste the token into Value.
- Set Approved hosts to
discord.com. - Click Save secret.
Never paste the token into chat. See /docs/secrets .
Check it works
Run this with execute. It only reads the bot's own profile. Bot tokens use the Bot prefix, not Bearer.
export default async function main() {
const res = await fetch('https://discord.com/api/v10/users/@me', {
headers: { Authorization: 'Bot {{secret:discordBotToken}}' },
})
if (!res.ok) throw new Error(`Discord returned ${res.status}: ${await res.text()}`)
const bot = await res.json()
return { username: bot.username, id: bot.id }
} Use it in a package
export default async function main({ channelId, content }) {
const res = await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, {
method: 'POST',
headers: {
Authorization: 'Bot {{secret:discordBotToken}}',
'Content-Type': 'application/json',
},
body: JSON.stringify({ content }),
})
if (!res.ok) throw new Error(`Discord returned ${res.status}: ${await res.text()}`)
const message = await res.json()
return { id: message.id }
} To get a channel ID, turn on Developer Mode in Discord's advanced settings, then right-click the channel and choose Copy Channel ID. Say in the package's README that it needs the discordBotToken secret approved for discord.com.
To read new messages, declare a job in the package that calls GET /api/v10/channels/{channelId}/messages?after=<last id> on a schedule and keeps the last message ID in package storage. Jobs can't run more often than your plan's minimum interval. See /docs/packages .
Option B: custom OAuth app with a user token
A user token answers "who am I" and "which servers am I in". Scopes such as identify, guilds, email, connections and guilds.members.read cover your identity and membership, nothing inside channels.
Set up the app on Discord
- On your application's OAuth2 tab, copy the Client ID, and reset the Client Secret to reveal and copy it.
- Under Redirects, add this URL exactly and save:
https://app.minion.codes/oauth/callback Add it in Minion
- Open app.minion.codes/app/integrations .
- In the OAuth apps card, set Provider to Custom (any OAuth 2 provider).
- Set App name (optional) to
discord. Don't leave it empty: a custom app's name defaults tocustom. - Paste the Client ID and Client secret.
- Set Default scopes (optional) to
identify guilds. - Set API hosts (custom: required) to
discord.com. - Open Custom provider endpoints and fill in:
- Authorization URL:
https://discord.com/oauth2/authorize - Token URL:
https://discord.com/api/oauth2/token - Revocation URL (optional):
https://discord.com/api/oauth2/token/revoke - Client authentication: Secret in the request body
- Keep Use PKCE (S256) checked.
- Authorization URL:
- Click Save app.
Discord access tokens expire after 7 days and the refresh token changes on every refresh. Minion refreshes the token for you and keeps the new refresh token, so you don't have to reconnect weekly.
Connect
- In the Integrations card, choose
discordunder OAuth app. - Leave Name (optional) empty to call the integration
discord. - Leave Extra scopes (optional) empty to use the app's default scopes. On a first connect, anything you type here replaces the defaults, so list every scope you need.
- Click Connect and approve on Discord.
Discord sends you back to the Integrations page with Connected "discord". and status connected. The account column stays empty for custom apps. That is expected.
An agent can start the same flow with minion.integrations.connect({ provider: 'discord' }). An owner or admin opens the returned connectUrl while signed in to Minion.
Check it works
export default async function main() {
const res = await minion.integrations.fetch({ name: 'discord', url: 'https://discord.com/api/v10/users/@me' })
if (!res.ok) throw new Error(`Discord returned ${res.status}: ${await res.text()}`)
const me = await res.json()
return { username: me.username }
} Use it in a package
package.json:
{
"name": "@your-org/discord-tools",
"exports": { "./servers": "./src/servers.ts" }
} src/servers.ts:
import { createAuthenticatedFetch } from 'minion:runtime'
const discord = createAuthenticatedFetch('discord')
export default async function main() {
const res = await discord('https://discord.com/api/v10/users/@me/guilds')
if (!res.ok) throw new Error(`Discord returned ${res.status}`)
const guilds = await res.json()
return guilds.map((g) => ({ id: g.id, name: g.name }))
} Save and publish it with minion.packages.save and publish: true. Publishing needs an owner or admin and a README.md that says the package needs the discord integration. Other code can only import a published version. Then call it from execute:
import servers from 'minion:@your-org/discord-tools/servers'
export default async function main() {
return servers()
} Replace your-org with your organization slug. See /docs/packages for saving and publishing.
What isn't supported
- Slash commands, buttons and other interactions. Discord sends these to an Interactions Endpoint URL and signs each request with Ed25519. Discord checks the endpoint before saving it, and rejects endpoints that accept bad signatures. Minion's webhook signature checks support HMAC only, so a Minion webhook can't be an Interactions Endpoint yet.
- Discord webhook events. These are signed the same way, so the same limit applies.
- Gateway events. Discord delivers live messages over a long-lived gateway connection. Minion runs code on demand and doesn't hold that connection open. Poll with a job instead, as described in Option A.
Troubleshooting
- 401 Unauthorized with the bot token. Check the
Botprefix in the header, and that nobody reset the token in the developer portal. If they did, rotate the secret with the new token. - 403 Missing Access or Missing Permissions. The bot isn't in that server, or its role can't see or post in the channel. Add it with the right permissions, or fix its role.
- Messages come back with empty content. Turn on the Message Content Intent on the Bot tab.
- Discord says the redirect URI is invalid. The redirect on the OAuth2 tab must be exactly
https://app.minion.codes/oauth/callback. No trailing slash. - invalid_grant while connecting. Each authorization code works once. Click Connect again for a fresh one.
- Secret "discordBotToken" isn't approved for discord.com. Open the secret on the Secrets page and approve
discord.com. - An error saying integration "discord" may not send its token to a host. The URL isn't on
discord.com. Check the URL. - The OAuth integration turns error and says it needs a person to reconnect. You removed the app under Authorized Apps in Discord, or the refresh failed. Minion fires an
integration.auth.failedevent with areconnectUrl. Click Connect next to the integration on the Integrations page.
Disconnecting the OAuth integration in Minion revokes the grant at Discord through the Revocation URL, then deletes the token in Minion.
For how OAuth integrations work in general, see /docs/oauth . For secrets, see /docs/secrets .