CEEJAYDocs

Send a message

Post a message or embed to a channel in your Discord server.

POST /v1/messages/send · requires messages:send

Body parameters

Prop

Type

At least one of content or embeds must be present. List channels gives you each channel's channelId.

Returns

Prop

Type

Example

Request
curl -X POST https://ceejay.communitystudios.xyz/api/v1/messages/send \
  -H "Authorization: Bearer $CEEJAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "channelId": "718230049827100384",
    "content": "Tournament results are in. Congratulations to everyone who played."
  }'

The TypeScript example uses ceejay.messages.send from the TypeScript SDK. It returns the data of the response below.

200 OK
{
  "success": true,
  "data": {
    "messageId": "1178230049827100384",
    "channelId": "718230049827100384",
    "duplicate": false
  }
}

How it works

CEEJAY first checks that the channel belongs to your server, then posts the message as the bot. A channel in another server, a channel that does not exist and a channel the bot cannot see all return the same 404.

The text of a message is not filtered or changed, so read the next section before you pass on text written by players. CEEJAY does not store a copy of the message.

Embeds

embeds must be a list of Discord embed objects, and only the first 10 are sent.

  • With no content, the embeds are the whole message: a value that is not a list returns 400 with the message embeds must be a list.
  • With content, a value that is not a list is left out and the text is posted.

Embeds go to Discord unchanged, so Discord decides whether they are valid. An embed Discord does not accept is returned with Discord's own status and the code discord_error.

Mentions

Every message is sent so that only mentions of members notify anyone.

In contentNotifies
A member mention, <@409876543210987654>That member, always
A role mention, <@&718230049827100399>The role, only when its ID is in allowedRoleIds. Otherwise nobody
@everyone or @hereNobody. The API can never ping everyone

To ping a role, name it in allowedRoleIds and mention it in content:

Request body
{
  "channelId": "718230049827100384",
  "content": "Sign-ups are open, <@&718230049827100399>.",
  "allowedRoleIds": ["718230049827100399"]
}

A role in allowedRoleIds that content does not mention pings nobody. Discord's own rules still apply: the bot can only ping a role that is set to be mentionable, unless the bot has the Mention @everyone, @here, and All Roles permission.

Your server's own ID is also the ID of its @everyone role, so allowedRoleIds cannot contain it. A list that does is rejected with 400.

Retrying safely

Send an idempotencyKey with every call you might need to retry, such as after a timeout. It is a string of 1 to 128 characters that you choose, sent in the JSON body: an Idempotency-Key header is not read.

  • Retrying with the same key returns the original response. When the same request is sent again with its key, CEEJAY returns the original response, with duplicate: true and the header Idempotent-Replayed: true.
  • Each key is tied to one request payload. The same request means the same channel, text, embeds and allowedRoleIds, as they would be posted. Two requests for one event need two keys, for example match-9:coins and match-9:xp. A key that was used for a different request, here or on any other request of the API, returns 409 with the code idempotency_key_reused, and nothing is changed.
  • A rejected request does not use up the key. A call that was rejected can be retried with the same key once the cause is fixed.
  • A key belongs to the API key that sent it. Another API key of your community can send the same text, and it is a different key.
  • Keys are retained for 30 days. Use a value you can regenerate after a crash, such as an order ID or a match ID.
  • On an instance that does not support idempotency keys yet, a call that sends one returns 503 with the code idempotency_unavailable, and nothing is changed. Community details reports whether keys are supported, under features.retryKeys. idempotency_key is not accepted on this endpoint; use idempotencyKey.
  • Without a key, a retried call is a new call.

CEEJAY cannot undo a posted message, so it never posts twice with one key:

  • A rejection from Discord (any 4xx, its rate limit included) does not use up the key. The same key can be reused.
  • After a timeout, a dropped connection or a 5xx from Discord, the message may or may not have been posted. The same key then returns 409 with the code idempotency_in_progress for a minute, and idempotency_outcome_unknown after that. Check the channel, then use a new key if the message has to be sent again.
  • With a key, embeds nested more than 32 levels deep are rejected with 400.

Without a key, sending the same request twice posts the message twice.

Errors

StatuscodeerrorWhen
400bad_requestThe request body must be a JSON objectThe body is null, text, a number or a list. A request with no Content-Type header is read as text, so it gets this error too
400bad_requestchannelId must be a valid Discord snowflake (17-20 digits)channelId is missing or is not a string of 17 to 20 digits
400bad_requestcontent must be a stringcontent was sent and is not a string. A number is rejected, not converted to text
400bad_requestcontent must be valid Unicode textcontent contains an unpaired surrogate, such as the JSON "\ud800". Nothing is sent to Discord
400bad_requestembeds must be valid Unicode textThe embeds contain an unpaired surrogate anywhere in their text or their keys. Nothing is sent to Discord
400bad_requestEither content or embeds is requiredThe body has no content, and embeds is missing or an empty list
400bad_requestembeds must be a listThere is no content, and embeds is not a list
400bad_requestallowedRoleIds must be a list of up to 20 valid Discord snowflakes (17-20 digits)allowedRoleIds is not a list, has more than 20 entries, contains something that is not a role ID, or contains your server's own ID
400bad_requestidempotencyKey must be a string of 1-128 charactersThe key is empty, longer than 128 characters or not text, or contains an unpaired surrogate, such as the JSON "\ud800"
400bad_requestidempotencyKey cannot contain a null characterThe key contains a null character (U+0000)
400bad_requestembeds is nested too deeplyYou sent an idempotencyKey, and the embeds nest lists and objects more than 32 levels deep
400discord_errorDiscord API error: 400Discord rejected the message, for example an embed it does not accept. details contains Discord's response
403bot_missing_permissionBot lacks permission to send messages in this channelThe bot has no Send Messages permission in this channel
404channel_not_foundChannel not found or bot has no accessThe channel does not exist, is in another server, or the bot cannot see it
409idempotency_key_reusedThis idempotencyKey was already used for a different requestYour API key already used this idempotencyKey for a different request: a request of another kind, or this one with other values. Nothing was changed
409idempotency_in_progressThe first request with this idempotencyKey is still running. Try again in a few seconds.The first request with this key is still being processed. Wait retryAfter seconds and retry
409idempotency_outcome_unknownAn earlier request with this idempotencyKey may or may not have been applied. It was not sent again. Use a new key to send it again.A first request with this key was started more than a minute ago and never finished. CEEJAY cannot know whether it took effect, and does not do it again. Check what happened, then use a new key if it has to be sent again
429discord_rate_limitedDiscord API rate limited, try again laterDiscord is rate limiting the bot. retryAfter and the Retry-After header contain the wait Discord requested, in whole seconds, if it gave one. Do not retry in a tight loop
500discord_not_configuredThis instance cannot reach Discord yetThis instance has no connection to Discord yet
503discord_unavailableDiscord did not answer in time, so the channel could not be checked and nothing was postedDiscord did not respond to the channel check within 10 seconds. Nothing was posted, and no idempotency key is used up: retry after a pause
503discord_unavailableDiscord did not answer in time, so the message may or may not have been postedDiscord did not respond to the post within 10 seconds. The message may or may not be in the channel, and the idempotency key stays reserved: see Retrying safely before you retry
503idempotency_unavailableidempotencyKey cannot be used on this community yet, so nothing was changedYou sent a key, and this instance does not support idempotency keys yet. Nothing was changed. Community details reports it as features.retryKeys
Discord'sdiscord_errorDiscord API error: 503Any other status Discord returns is passed on, with Discord's own status. details contains Discord's response. After a 5xx the message may or may not have been posted

Discord's own rate limit returns 429 with the code discord_rate_limited. A limit Discord sets for one channel stops calls for that channel only, and the response does not mean your key's own limit is used up: X-RateLimit-Remaining still shows what the key has left.

Every error body also includes requestId, and a 400 with the code bad_request identifies the invalid field in field. Match on code; message text can change.

The errors every request shares (401, 403, 405, 429, 500) are on the Errors page.

Bot permissions

The bot needs View Channel and Send Messages in the target channel, and Embed Links for embeds to show. Without Send Messages the call returns 403; a channel the bot cannot see returns 404.

On this page

Community StudiosPrivacyTerms