Discord
Send messages, DMs, embeds, polls, forum posts, threads, schedule events, and manage roles in Discord servers with Zernio API - No bot hosting, no gateway, no intents approval
Quick Reference
| Property | Value |
|---|---|
| Content limit | 2,000 characters (message content) |
| Embeds per message | 10 (max 6,000 chars total) |
| Images per message | Up to 10 attachments |
| Image formats | JPEG, PNG, GIF, WebP |
| Image max size | 25 MB (higher on boosted servers) |
| Video formats | MP4, MOV, WebM |
| Video max size | 25 MB (up to 500 MB on boosted servers) |
| Post types | Messages, Embeds, Polls, Forum Posts, Threads |
| Scheduling | Yes |
| Direct Messages | Outbound only |
| Role management | Yes (list / assign / remove) |
| Member listing | Yes (cursor pagination) |
| Pinned messages | Yes (list / pin / unpin) |
| Scheduled Events | Yes (create / list / update / cancel / delete) |
| Inbox (DM replies) | No (Discord requires a Gateway connection) |
| Inbox (Comments) | No |
| Analytics | No (Discord Bot API limit) |
Before You Start
Discord integration uses a centralized bot token model. You do NOT need to create a Discord Application, request privileged intents, or host a gateway connection. You add the Zernio bot to your server via OAuth and POST to our REST API.
Key points:
- No bot hosting required - we manage the bot infrastructure
- No intents approval - MESSAGE_CONTENT and other privileged intents are handled at the bot level
- No gateway/sharding - you never connect a WebSocket; everything is REST
- The bot needs: Send Messages, Embed Links, Attach Files, Send Messages in Threads, Create Public Threads, Manage Messages (for pin/unpin), Manage Roles (for role assignment), and Manage Events (for scheduled events) permissions - all granted at install time on new connections.
- Already-connected servers: existing Zernio installs predate the Manage Roles and Manage Events permissions. If those endpoints return 502, ask a server admin to either re-invite the bot (picks up new permissions automatically) or manually grant the missing permission on the bot's role in Server Settings → Roles.
Note on the Server Members Intent: the GET /v1/discord/guilds/{guildId}/members endpoint requires the "Server Members Intent" enabled on the Zernio Discord app. This is on by default for new bot installs. If listing members returns an empty array with no error, this intent is the likely cause.
OAuth Scopes
Discord posting works through a bot installation rather than user-token publishing, so only two OAuth scopes are involved:
| Scope | What it enables |
|---|---|
bot | Install the Zernio bot into your server; all posting happens through the bot |
guilds | List the servers you manage so you can pick where to post |
Zernio requests all scopes in a single OAuth flow when the account is connected (see Connecting Accounts); scopes cannot be requested individually. To check what a connected account can currently do, call Account Health: it reports posting and analytics capability derived from the scopes the user actually granted.
Quick Start
Send a message to a Discord channel:
const { post } = await zernio.posts.createPost({
content: 'Hello from Zernio API!',
platforms: [
{
platform: 'discord',
accountId: 'YOUR_ACCOUNT_ID',
platformSpecificData: {
channelId: '1234567890'
}
}
],
publishNow: true
});
console.log('Posted to Discord!', post._id);result = client.posts.create_post(
content="Hello from Zernio API!",
platforms=[
{
"platform": "discord",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"channelId": "1234567890"
}
}
],
publish_now=True,
)
print(f"Posted to Discord! {result.post['_id']}")curl -X POST https://zernio.com/api/v1/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Hello from Zernio API!",
"platforms": [
{
"platform": "discord",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"channelId": "1234567890"
}
}
],
"publishNow": true
}'Connection
Discord uses OAuth2 with our centralized bot:
- Call
GET /v1/connect/discord?profileId=YOUR_PROFILE_IDto get the bot invite URL - The user authorizes the bot to join their server
- After authorization, the user selects which channel to post to via
/connect/discord/select-channel - The connected account appears in
GET /v1/accounts
Each connected account represents one Discord server. The channelId in platformSpecificData determines which channel receives the message.
Webhook Identity (Bot Customization)
By default, messages are posted as "Zernio" with the bot's avatar. You can customize the display name and avatar at two levels:
Account-level default (applies to every post from this account):
curl -X PATCH https://zernio.com/api/v1/connect/discord \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "YOUR_ACCOUNT_ID",
"webhookUsername": "My Brand",
"webhookAvatarUrl": "https://example.com/logo.png"
}'Per-post override (overrides account default for a single post):
{
"platforms": [{
"platform": "discord",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"channelId": "1234567890",
"webhookUsername": "Special Announcement",
"webhookAvatarUrl": "https://example.com/special-logo.png"
}
}],
"publishNow": true
}Webhook usernames must be 1-80 characters and cannot contain "clyde" or "discord". Send an empty string to reset to the default ("Zernio").
Embeds
Send rich embedded content alongside or instead of plain text:
{
"content": "New release shipping today!",
"platforms": [{
"platform": "discord",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"channelId": "1234567890",
"embeds": [{
"title": "v2.3.0 Release Notes",
"description": "Dark mode, new API endpoints, faster uploads.",
"color": 5814783,
"url": "https://example.com/changelog",
"footer": { "text": "Shipped today" },
"fields": [
{ "name": "New Features", "value": "3", "inline": true },
{ "name": "Bug Fixes", "value": "12", "inline": true }
]
}]
}
}],
"publishNow": true
}Each message supports up to 10 embeds with a combined character limit of 6,000 across all embed fields.
Native Polls
Create Discord-native polls (same UX as the Discord client):
{
"platforms": [{
"platform": "discord",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"channelId": "1234567890",
"poll": {
"question": { "text": "Ship it now or wait until Monday?" },
"answers": [
{ "poll_media": { "text": "Ship now" } },
{ "poll_media": { "text": "Wait for Monday" } }
],
"duration": 24,
"allow_multiselect": false
}
}
}],
"publishNow": true
}| Poll Property | Value |
|---|---|
| Max answers | 10 |
| Duration range | 1 - 768 hours (32 days) |
| Default duration | 24 hours |
| Multi-select | Optional (default: false) |
Polls are standalone messages. You cannot combine a poll with media attachments in the same message (Discord API limitation).
Forum Posts
Post starter messages to forum channels (type 15):
{
"content": "Community call notes for January 15",
"platforms": [{
"platform": "discord",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"channelId": "9999999999",
"forumThreadName": "Community Call - Jan 15",
"forumAppliedTags": ["11111111", "22222222"]
}
}],
"publishNow": true
}forumThreadName(required for forum channels) becomes the thread titleforumAppliedTagsare snowflake IDs of existing forum tags (max 5 tags per post)- Use
listForumTags(channelId)to discover available tags
Threads
Create a follow-up thread under any published message:
{
"content": "Main announcement here",
"platforms": [{
"platform": "discord",
"accountId": "YOUR_ACCOUNT_ID",
"platformSpecificData": {
"channelId": "1234567890",
"threadFromMessage": {
"name": "Discussion Thread",
"autoArchiveDuration": 1440,
"rateLimitPerUser": 0
}
}
}],
"publishNow": true
}autoArchiveDuration options: 60, 1440 (1 day), 4320 (3 days), or 10080 (7 days) minutes. rateLimitPerUser sets slow-mode in seconds (0-21600).
Announcement Crosspost
Auto-crosspost messages from announcement channels (type 5) so followers in other servers receive them:
{
"platformSpecificData": {
"channelId": "ANNOUNCEMENT_CHANNEL_ID",
"crosspost": true
}
}This is a no-op for regular text channels.
Edit & Delete
- Edit:
PUT /v1/posts/{postId}updates the Discord message content and embeds - Delete:
DELETE /v1/posts/{postId}deletes the Discord message or cancels a scheduled one
Rate Limits
Discord applies per-channel, per-guild, and global rate limits. Zernio handles 429 responses with exponential backoff and respects the X-RateLimit-Reset-After header automatically. You never need to implement retry logic.
platformSpecificData Reference
| Field | Type | Required | Description |
|---|---|---|---|
channelId | string | Yes | Target channel snowflake ID |
embeds | array | No | Up to 10 Discord embed objects (6,000 chars total) |
poll | object | No | Native poll (question, answers, duration). Cannot combine with media |
crosspost | boolean | No | Auto-crosspost in announcement channels |
forumThreadName | string | Forum channels | Thread title for forum starter message |
forumAppliedTags | string[] | No | Tag snowflake IDs for forum posts (max 5) |
threadFromMessage | object | No | Create a thread under the published message |
threadFromMessage.rateLimitPerUser | number | No | Slow-mode seconds for thread (0-21600) |
tts | boolean | No | Text-to-speech message |
webhookUsername | string | No | Override display name for this post (1-80 chars) |
webhookAvatarUrl | string | No | Override avatar URL for this post |
Channel Management
List available channels in the connected guild:
const { channels } = await zernio.discord.getDiscordChannels({
path: { accountId: 'YOUR_ACCOUNT_ID' },
});res = client.discord.get_discord_channels(account_id="YOUR_ACCOUNT_ID")curl https://zernio.com/api/v1/accounts/YOUR_ACCOUNT_ID/discord-channels \
-H "Authorization: Bearer YOUR_API_KEY"Switch the connected channel:
curl -X PATCH https://zernio.com/api/v1/connect/discord \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "YOUR_ACCOUNT_ID",
"channelId": "NEW_CHANNEL_ID"
}'Only text (0), announcement (5), and forum (15) channels are supported. A new webhook is automatically created in the target channel.
Direct Messages
Send a 1:1 message from the bot to a Discord user. Useful for operational messages outside the channel post flow — onboarding, billing reminders, password resets, support pings.
curl -X POST https://zernio.com/api/v1/discord/dms \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "YOUR_ACCOUNT_ID",
"userId": "1234567890123456789",
"content": "Welcome to Acme! Reply STOP to opt out."
}'Same payload shape as channel posts — also accepts embeds, attachments,
and tts. At least one of content, embeds, or attachments is required.
Discord constraints to know about:
- The bot can only DM users it shares at least one guild with.
- If the recipient has DMs disabled for non-friends, Discord returns 403 (surfaces as a 502 platform error).
- Recipient must be identified by Discord snowflake ID (not username).
No public API exists to resolve a username to a snowflake without the
privileged
guild_members.readscope.
Role & Member Management
Four endpoints for community-ops automation: list roles, list members (paginated), and assign / remove roles per member. Path shape mirrors Discord's own API so you can map mentally with zero translation.
List all roles in a guild (id, name, color, position, permissions, flags):
curl "https://zernio.com/api/v1/discord/guilds/GUILD_ID/roles?accountId=YOUR_ACCOUNT_ID" \
-H "Authorization: Bearer YOUR_API_KEY"List members (cursor-paginated, default 100/page, max 1,000):
curl "https://zernio.com/api/v1/discord/guilds/GUILD_ID/members?accountId=YOUR_ACCOUNT_ID&limit=100" \
-H "Authorization: Bearer YOUR_API_KEY"Response:
{
"data": [
{
"user": { "id": "...", "username": "...", "global_name": "..." },
"nick": null,
"roles": ["ROLE_ID_A", "ROLE_ID_B"],
"joined_at": "2024-01-15T10:30:00.000Z"
}
],
"pagination": { "nextCursor": "USER_ID", "hasMore": true }
}Pass &after=NEXT_CURSOR on the next call until hasMore: false.
Assign a role to a member (idempotent):
curl -X PUT "https://zernio.com/api/v1/discord/guilds/GUILD_ID/members/USER_ID/roles/ROLE_ID?accountId=YOUR_ACCOUNT_ID" \
-H "Authorization: Bearer YOUR_API_KEY"Remove a role from a member (idempotent):
curl -X DELETE "https://zernio.com/api/v1/discord/guilds/GUILD_ID/members/USER_ID/roles/ROLE_ID?accountId=YOUR_ACCOUNT_ID" \
-H "Authorization: Bearer YOUR_API_KEY"Discord hierarchy rule: the bot's highest role must be above the
target role. If the bot is demoted in the guild's role hierarchy, it loses
the ability to assign higher roles even with the Manage Roles permission.
The @everyone role (where roleId == guildId) cannot be manipulated.
Pin / Unpin Messages
Pin messages in a channel for community-ops automation: announcement of the week, rules channel updates, auto-pin freshly published posts.
List pinned messages (max 50 per channel, sorted most-recent first):
curl "https://zernio.com/api/v1/discord/channels/CHANNEL_ID/pins?accountId=YOUR_ACCOUNT_ID" \
-H "Authorization: Bearer YOUR_API_KEY"Pin a message (idempotent):
curl -X PUT "https://zernio.com/api/v1/discord/channels/CHANNEL_ID/pins/MESSAGE_ID?accountId=YOUR_ACCOUNT_ID" \
-H "Authorization: Bearer YOUR_API_KEY"Unpin a message (idempotent):
curl -X DELETE "https://zernio.com/api/v1/discord/channels/CHANNEL_ID/pins/MESSAGE_ID?accountId=YOUR_ACCOUNT_ID" \
-H "Authorization: Bearer YOUR_API_KEY"Discord caps a channel at 50 pinned messages. Attempting to pin a 51st returns 400 — unpin one first.
Scheduled Events
Create Discord-native scheduled events — distinct from messages. They show up in the server's Events panel and Discord auto-notifies interested members ahead of start time. Perfect for AMAs, game tournaments, office hours, livestreams, web3 community events.
Three event types via discriminated union on entity.type:
External event (Zoom, IRL, off-platform livestream):
curl -X POST https://zernio.com/api/v1/discord/guilds/GUILD_ID/events \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "YOUR_ACCOUNT_ID",
"name": "Weekly AMA",
"description": "Bring your questions about the roadmap.",
"startsAt": "2026-06-15T18:00:00Z",
"entity": {
"type": "external",
"location": "https://zoom.us/j/123456789",
"endsAt": "2026-06-15T19:00:00Z"
}
}'For external events, both location and endsAt are required.
Voice channel event:
{
"accountId": "YOUR_ACCOUNT_ID",
"name": "Game Night",
"startsAt": "2026-06-20T20:00:00Z",
"entity": { "type": "voice", "channelId": "VOICE_CHANNEL_ID" }
}Stage channel event: same shape as voice, with "type": "stage" and a
stage channel id.
List events (optionally with RSVP count):
curl "https://zernio.com/api/v1/discord/guilds/GUILD_ID/events?accountId=YOUR_ACCOUNT_ID&withUserCount=true" \
-H "Authorization: Bearer YOUR_API_KEY"Get / Update / Delete one event:
# Fetch
curl "https://zernio.com/api/v1/discord/guilds/GUILD_ID/events/EVENT_ID?accountId=YOUR_ACCOUNT_ID" \
-H "Authorization: Bearer YOUR_API_KEY"
# Update (any subset of fields)
curl -X PATCH "https://zernio.com/api/v1/discord/guilds/GUILD_ID/events/EVENT_ID" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "accountId": "YOUR_ACCOUNT_ID", "name": "Updated title" }'
# Cancel (status transition - preserves the event in guild history)
curl -X PATCH "https://zernio.com/api/v1/discord/guilds/GUILD_ID/events/EVENT_ID" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "accountId": "YOUR_ACCOUNT_ID", "status": "cancelled" }'
# Hard delete (no history retained)
curl -X DELETE "https://zernio.com/api/v1/discord/guilds/GUILD_ID/events/EVENT_ID?accountId=YOUR_ACCOUNT_ID" \
-H "Authorization: Bearer YOUR_API_KEY"Discord doesn't have a dedicated "cancel" endpoint — cancellation is a
status transition via PATCH (status: 'cancelled'). Use PATCH if you want
the event preserved in the guild's event history; use DELETE for a hard
delete with no record.
| Event Property | Value |
|---|---|
| Name | Max 100 chars |
| Description | Max 1,000 chars |
| Privacy level | Always GUILD_ONLY (Discord deprecated PUBLIC events) |
| External events | Require location (max 100 chars) + endsAt |
| Voice/stage events | Require channelId; endsAt optional |
| Cover image | Optional base64 data URI (PNG / JPEG / GIF) |
| Start time | Must be in the future |
Related Endpoints
Connection & posting
- Connect Discord Account - OAuth flow and bot installation
- Create Post - Post creation and scheduling
- Discord Settings - Update webhook identity and switch channels
- Discord Channels - List guild channels
Direct Messages
POST /v1/discord/dms- Send a 1:1 DM from the bot to a Discord user
Role & Member management
GET /v1/discord/guilds/{guildId}/roles- List all roles in a guildGET /v1/discord/guilds/{guildId}/members- Paginated member listPUT /v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}- Assign a roleDELETE /v1/discord/guilds/{guildId}/members/{userId}/roles/{roleId}- Remove a role
Pinned messages
GET /v1/discord/channels/{channelId}/pins- List pinned messagesPUT /v1/discord/channels/{channelId}/pins/{messageId}- Pin a messageDELETE /v1/discord/channels/{channelId}/pins/{messageId}- Unpin a message
Scheduled Events
POST /v1/discord/guilds/{guildId}/events- Create an event (external / voice / stage)GET /v1/discord/guilds/{guildId}/events- List events (optionally with RSVP count)GET /v1/discord/guilds/{guildId}/events/{eventId}- Fetch one eventPATCH /v1/discord/guilds/{guildId}/events/{eventId}- Update or cancel (status: 'cancelled')DELETE /v1/discord/guilds/{guildId}/events/{eventId}- Hard delete