# Discord API: posting reference

> Index of all OmniSocials docs pages: https://docs.omnisocials.com/llms.txt

OmniSocials posts to one Discord **channel** per workspace through an **incoming webhook** of that channel. No token expires. The workspace connects the channel once in the OmniSocials dashboard (Channels, then Add a channel, then Discord), in one of two ways:

- **Continue with Discord** (recommended). Discord's own screen asks for the server and the channel; pick a **text** channel (Discord lists voice channels there too, and a post would land in the voice channel's chat). The person who connects needs the **Manage Server** permission in that server. OmniSocials keeps the webhook Discord creates, and the server name and icon. Discord gives a webhook no channel name: where the sign-in also adds the **OmniSocials bot** (with no permissions), the bot reads the channel's name and type and the server's member count, and OmniSocials says so right after connecting when the channel is a voice channel. Without the bot, OmniSocials asks you which channel you picked. The bot never posts and never reads messages; posts always go through the webhook.
- **Paste a webhook URL.** Make a webhook in Discord (Server Settings, then Integrations, then Webhooks, then New Webhook), pick the channel, press **Copy Webhook URL** and paste it in OmniSocials. Discord tells a webhook URL no server or channel name, so the account shows the webhook's name, and OmniSocials asks you which channel it is (unless the OmniSocials bot is already in that server).

A post shows the **webhook's name and picture** as the sender. Change them in Discord, in the webhook's settings. Disconnecting the channel in OmniSocials never deletes the webhook in Discord.

**Channel ID:** `discord`

## Supported content types

| Type | Supported |
|------|-----------|
| Text post | ✅ |
| Images, GIFs and videos (up to 10, mixed) | ✅ |
| Native poll | ✅ |
| Forum or media channel post | ✅ (with `thread_name`) |
| Reel | ✅ (sent as one video message) |
| Story | - |

## Minimal example

```bash
curl -X POST https://api.omnisocials.com/v1/posts/create \
  -H "Authorization: Bearer $OMNISOCIALS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": { "default": "The October menu is live." },
    "channels": ["discord"],
    "media_urls": ["https://example.com/menu.jpg"],
    "discord": { "silent": true }
  }'
```

## Platform-specific options

| Field | Type | Description |
|-------|------|-------------|
| `discord.silent` | boolean | Send without a push or desktop notification (Discord's `@silent`). |
| `discord.link_preview` | boolean | `false` hides the previews of the links in the text. The previews show by default. |
| `discord.allow_mentions` | boolean | Let `@everyone`, `@here`, role and user mentions in the text notify people. Off by default. See [Mentions](#mentions). |
| `discord.spoiler` | boolean | Blur every image and video until someone clicks it. |
| `discord.thread_name` | string | The post title in a forum or media channel, 1 to 100 characters. See [Forum and media channels](#forum-and-media-channels). |
| `discord.buttons` | array | Up to 5 link buttons under the message. See [Buttons](#buttons). |
| `discord.poll` | object | A native Discord poll; the post text is the question. See [Polls](#polls). |

On `PATCH /posts/:id`, every key merges into the stored options, and a key set to `null` removes it (for example `"discord": { "poll": null }`). `"discord": null` removes every option.

## Mentions

Mentions in the text are off by default: `@everyone`, `@here`, a role or a user in the text shows as text and notifies nobody, so a scheduled post never pings a whole server by accident. Set `discord.allow_mentions: true` to let them ping. A role mention is written `<@&role_id>` and a user mention `<@user_id>` (in Discord, turn on Developer Mode and copy the id).

## Buttons

```json
{
  "channels": ["discord"],
  "content": { "discord": "Early access opens today." },
  "media_urls": ["https://example.com/launch.jpg"],
  "discord": {
    "buttons": [
      { "text": "Get early access", "url": "https://example.com/early" },
      { "text": "Read the story", "url": "https://example.com/blog/launch" }
    ]
  }
}
```

**Rules:**

- At most 5 buttons, in one row under the message.
- `text` is 1 to 80 characters. `url` starts with `https://` or `http://`.
- A poll carries no buttons: a request with both is refused with `400 validation_error`.

## Polls

```json
{
  "channels": ["discord"],
  "content": { "discord": "Which day suits the live Q&A?" },
  "discord": {
    "poll": {
      "options": ["Tuesday", "Thursday", "Saturday"],
      "duration_hours": 48,
      "multiple": false
    }
  }
}
```

| Field | Type | Description |
|-------|------|-------------|
| `options` | array of strings | 2 to 10 answers of up to 55 characters. The answers must differ from each other. |
| `duration_hours` | integer | Hours the poll stays open, 1 to 768 (32 days). Default 24. |
| `multiple` | boolean | Let people pick several answers (default `false`). |

The question is the post text, 1 to 300 characters. A poll carries no media and no buttons: a request with either is refused with `400 validation_error`. The votes come back in analytics under `metrics.poll`.

## Forum and media channels

A forum or media channel holds posts, each with its own title. Discord requires a title there: set `discord.thread_name` (1 to 100 characters), and the message opens a new post with that title. `GET /posts/:id` then returns the post's id in `discord.result.thread_id`.

Leave `thread_name` out for a text or announcement channel: Discord refuses a title there, so OmniSocials sends the post without it and adds the warning `thread_name_ignored` to `discord.result.warnings`. A forum or media channel without a title fails with a message that says what to change.

## Media requirements

| Media | Requirement |
|-------|-------------|
| Image | JPEG, PNG, WebP and GIF, up to 40 MB. Discord takes files of up to 20 MB from a webhook; OmniSocials re-encodes a larger image to a JPEG that fits. |
| Video | MP4, MOV or WebM, up to 1 GB. No duration cap. A video of up to 20 MB is attached; a larger one is sent as a link to the file in the message, and Discord shows a player for it. |
| GIF | Up to 20 MB attached; a larger GIF is sent as a link. |

A message holds up to 10 files, and images, GIFs and videos can be mixed. OmniSocials attaches every file from its own servers. Media sent as a link is listed in `discord.result.video_links`. When the links do not fit in the 2,000-character text, they follow in a second message right after the post (`discord.result.link_message_id`).

## Character limit

2,000 characters per message, counted in UTF-16 code units: most emoji count as 2. A poll question allows 300 characters. Discord shows its own Markdown in the text (`**bold**`, `*italic*`, lists, `[masked links](https://example.com)`).

## Published post

`GET /posts/:id` returns under `discord.result`:

| Field | Description |
|-------|-------------|
| `webhook_id` | The webhook the post went through. |
| `guild_id`, `channel_id` | The Discord server and channel. |
| `thread_id` | The forum post the message opened, in a forum or media channel. |
| `message_id` | The id of the message. |
| `url` | The link to the message, which members of the server can open. |
| `sent_at` | When the post went out. |
| `video_links` | Media sent as a link instead of a file. |
| `link_message_id` | The second message that holds the links, when they did not fit in the text. |
| `warnings` | What was sent differently from the request, such as `media_sent_as_links` or `thread_name_ignored` (a post title on a channel that is no forum). Empty when everything went as asked. |

`published_urls.discord` is `https://discord.com/channels/<server>/<channel or forum post>/<message>`.

## Account details

`GET /accounts` returns the Discord account with `platform_details`:

| Field | Description |
|-------|-------------|
| `guild_id`, `guild_name` | The server. The name is known for Continue with Discord, and for a pasted URL in a server the OmniSocials bot is in. |
| `channel_id` | The channel OmniSocials posts to. |
| `channel_name` | The channel name without the #: read by the OmniSocials bot, or given by the person when connecting. `null` when neither is known. |
| `channel_type` | `text`, `voice`, `announcement`, `stage`, `forum` or `media`, as the bot read it; `null` when the bot cannot see the channel. A `forum` or `media` channel needs `discord.thread_name` on every post. |
| `webhook_name` | The name posts show as the sender. |
| `connect_method` | `oauth` (Continue with Discord) or `url` (a pasted webhook URL). |
| `url` | The link to the channel. |

## Analytics

Discord gives webhooks no views. A Discord post carries:

- `likes`: all reactions together
- `reactions`: the count per emoji
- `poll`: the votes per answer, for a poll

When the OmniSocials bot is in the server, the account statistics carry `followers`: the server's member count, read once a day, together with the channel name. Views, reach and replies are not reported.

## Removing a post

A sent message can be removed at any time: "Delete from Discord" on the post in the OmniSocials dashboard removes the message and the message with the links, if there is one. Only the webhook that sent a message can remove it, so after a reconnect with a new webhook, remove older posts in Discord yourself. `DELETE /posts/:id` removes the post from OmniSocials only and never touches the live message.

## Limitations

- One Discord channel per workspace. Connect more channels through more workspaces.
- The sender is the webhook's name and picture; a post cannot set its own.
- A post that was sent cannot be edited through OmniSocials.
- Discord messages are not part of the Social Inbox, and replies are not read. The channel name and the member count come from the OmniSocials bot, so a connection without the bot (a pasted webhook URL) has no member count.
- The OmniSocials bot leaves a server once no workspace posts to it any more. Removing the bot from a server never stops the posts: they go through the webhook.
- Discord is a destination for repost automations, never a source.

## When the connection breaks

When someone deletes the webhook in Discord, or resets its URL, the account shows `needs_reconnect: true` in `GET /accounts` (reason "The webhook was deleted in Discord") and posts to it fail with a message that says so. Reconnect the channel in OmniSocials: Continue with Discord makes a new webhook, or paste the URL of a new one.
