Read and post in Discord servers with a bot token, and read identity and guilds with user OAuth.
- Integrations
- discord
- bot
- oauth
- messages
- threads
- guilds
- multi-account
- License
- MIT
- Published
- August 22, 2026
- Pinned commit
d73fea9- Rating
- No ratings yet
- Forks
- 0
- Stars
- 0
- Adaptation effort
- —
README
@kody/discord
Intent
Provide reusable, account-agnostic Discord helpers for Kody agents: bot token for reading and posting in servers (messages, reactions, threads), and user OAuth for identity plus the authorizing user's guild list. This is not a personal notify package, not a gateway/chat bot, and not official-server admin.
Auth lanes
Discord splits its API. Pick a lane before you call:
| Lane | Credential | Can do | Cannot do |
|---|---|---|---|
| Bot token | Secret discordBotToken (Bot prefix) | Read/post channel messages, react, create/list threads | List the human user's guilds as that user |
| User OAuth | Saved integration discord (Bearer) | users/@me, users/@me/guilds | Read or post channel messages |
OAuth scopes such as identify and guilds never grant channel content. Reading message text additionally needs the privileged Message Content intent on the bot.
Multi-account: pass integration (OAuth, default discord) or secretName (bot, default discordBotToken). Additional connections use names like discord-work / discordBotTokenWork — do not hard-code personal aliases.
Required setup
There is no built-in Discord OAuth app. Both lanes are bring-your-own.
Lane A — user OAuth (identity and guilds)
- Create an application at discord.com/developers/applications.
- On OAuth2, copy the client id and client secret.
- Add redirect URI exactly:
https://kody.codes/connect/oauth. - Open this prefilled connect URL while signed in to Kody:
https://kody.codes/connect/oauth?provider=discord&authorizeUrl=https%3A%2F%2Fdiscord.com%2Foauth2%2Fauthorize&tokenUrl=https%3A%2F%2Fdiscord.com%2Fapi%2Foauth2%2Ftoken&flow=confidential&scopes=identify%20guildsDecoded: authorize https://discord.com/oauth2/authorize, token https://discord.com/api/oauth2/token, flow=confidential, scopes identify guilds. Discord's token host discord.com is also the API host, so no extra allowedHosts is required. Access tokens expire after 7 days; Kody's createAuthenticatedFetch refreshes them.
For a second Discord user account, change provider= to a new name such as discord-work and pass integration: 'discord-work' on OAuth exports.
Lane B — bot token (read and post in servers)
- On the same application's Bot tab, create the bot and copy the token. Never paste it into chat.
- If the bot must read message text, enable the privileged Message Content intent.
- Install the bot into the target server (Manage Server required):
https://discord.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&scope=bot&permissions=6867268672 = view channel + send messages + read history + add reactions. Add Create Public Threads / Send Messages in Threads in the developer portal if you use ./create-thread.
- Save the token:
https://kody.codes/account/secrets/new?name=discordBotToken&description=Discord%20bot%20token&allowedHosts=discord.com&scope=userApprove host discord.com after saving. Additional bots: a different name= (for example discordBotTokenWork) and pass secretName on bot exports.
Hosts and scopes
- Required host:
discord.com(APIhttps://discord.com/api/v10). - OAuth scopes (Lane A):
identify(who am I) andguilds(server list). Fuller optional:email,connections,guilds.members.read. None of these read channel messages. - Bot capabilities (Lane B): install permissions integer, bot roles, and privileged intents — not OAuth scopes.
Exports
kody:@kody/discord— package overview and setup URLskody:@kody/discord/smoke-test—GET /users/@meforlane: 'bot'(default) orlane: 'oauth'kody:@kody/discord/get-me— same identity readkody:@kody/discord/list-guilds— OAuth guild list (guildsscope)kody:@kody/discord/fetch-channel— bot: one channel by idkody:@kody/discord/fetch-channel-messages— bot: recent messages (limit1–100)kody:@kody/discord/post-message— bot: post content/embeds/files;dryRunorconfirmkody:@kody/discord/edit-message— bot: edit a bot-authored message;dryRunorconfirmkody:@kody/discord/add-reaction— bot: add one reaction;dryRunorconfirmkody:@kody/discord/create-thread— bot: public thread from a message;dryRunorconfirmkody:@kody/discord/list-channel-threads— bot: active/archived threads (channelId+guildIdrequired)
Smoke test
Bot lane (reads the approved discordBotToken secret):
import { packages } from 'kody:runtime'
export default async function main() {
return packages.invoke({
kodyId: 'discord',
exportName: './smoke-test',
params: { lane: 'bot' },
})
}OAuth lane:
import { packages } from 'kody:runtime'
export default async function main() {
return packages.invoke({
kodyId: 'discord',
exportName: './smoke-test',
params: { lane: 'oauth', integration: 'discord' },
})
}Use packages.invoke (not a static import) so the package runtime resolves secret mounts and placeholders.
dryRun
Mutations accept dryRun: true and return the Discord payload without calling the API. A live mutation requires confirm: true after the user approved the exact channel and content.
import postMessage from 'kody:@kody/discord/post-message'
export default async function main() {
return postMessage({
channelId: '1234567890',
content: 'Hello from Kody',
dryRun: true,
})
}403 / insufficient scope
Helpers throw DiscordApiError with missingScope and nextStep:
- OAuth 401/403: names the missing scope (
identify,guilds, …) and points at/connect/oauth?provider=…&scopes=…. Reconnect; do not cache raw tokens (they expire in 7 days). - Bot 401: rotate/save
discordBotTokenat the secrets URL. Bot tokens need theBotprefix (this package always sends it). - Bot 403 code 50001 Missing Access: bot is not in the server or cannot see the channel. Reinstall with
scope=bot. - Bot 403 code 50013 Missing Permissions: grant the missing channel permission / privileged Message Content intent, then retry.
- Empty message
contenton an otherwise successful read: enable Message Content on the Bot tab.
Examples
OAuth identity:
import getMe from 'kody:@kody/discord/get-me'
export default async function main() {
return getMe({ lane: 'oauth', integration: 'discord' })
}Read a channel as the bot:
import fetchMessages from 'kody:@kody/discord/fetch-channel-messages'
export default async function main() {
return fetchMessages({ channelId: '1234567890', limit: 5 })
}Report this listing
Log in to report this listing.