Skip to content

Messages

A message is one post in a channel, together with the text, attachments, embeds, stickers, and reactions stored with it. Channel metadata and membership are defined by the Channels resource.

A text-bearing channel is a channel type that stores messages: every type except a guild category and a guild link channel. The message history cutoff is a time stored on the guild; a member without READ_MESSAGE_HISTORY reads nothing created before it.

Unless a route says user-only, it accepts both a user session (Authorization: Bearer <token>) and a bot token (Authorization: Bot <token>). Error bodies follow Errors.

Channel resolution

TEBHOS resolves the channel before it applies any per-route authorisation:

  • A channel that does not exist, a guild the caller is not a member of, and a private channel the caller is not a recipient of all return 404 NOT_FOUND.
  • A guild member without VIEW_CHANNEL in the channel receives 403 FORBIDDEN.
  • A guild caller without READ_MESSAGE_HISTORY sees only messages created at or after the guild's message history cutoff, and nothing when the guild has no cutoff. Older messages behave as if they do not exist (an empty list or 404), not as a 403.
  • A timed-out member cannot send, type, react, or upload in that guild (403 FORBIDDEN).

Permission bits are evaluated only in guild channels.

Message object

Structure

Field Type Description
id snowflake The ID of the message
channel_id snowflake The ID of the channel
author partial user object The user credited with the message
webhook_id? snowflake The originating webhook ID, present only for a webhook-authored message
type integer Message type
flags integer Message flags
content string The text of the message, empty when the message has only media
timestamp ISO8601 timestamp Creation time derived from the message snowflake
edited_timestamp ?ISO8601 timestamp Most recent edit time, or null when the message has never been edited
pinned boolean Whether the message is pinned
mention_everyone boolean Whether the content contains @everyone or @here
tts boolean Whether the message was sent as text-to-speech
mentions array[partial user object] The users mentioned in the content
mention_roles array[snowflake] The IDs of the roles mentioned in the content
mention_channels? array[channel mention object] The channels the content links by ID
embeds array[embed object] The link previews and rich embeds on the message
attachments array[message attachment object] The files attached to the message
stickers array[sticker item object] The stickers sent with the message
reactions? array[reaction object] Reaction summaries, omitted when the message has none
message_reference? message reference object Reply or forward reference
message_snapshots? array[message snapshot object] The copies captured for a forward
nonce? string The caller-supplied nonce, echoed only in the create response and its Gateway event
call? message call object Call state attached to a CALL message
referenced_message? ?message object The resolved reply target, without its own referenced_message

mentions, mention_roles, embeds, attachments and stickers are always present and can be empty. A field marked ? is omitted when it has no value.

referenced_message has three states: the key is absent when the message is not a reply, present and null when the reply target no longer exists, and present with the message when it does. A client must tell absent apart from null by key presence.

A webhook-authored message has no user author. TEBHOS builds its author from the webhook: id is the webhook ID, username is the name the message was posted under, discriminator is "0", avatar is the webhook avatar hash, and bot is true. A message whose author account can no longer be resolved carries an author object with only a real id.

A forward has immutable copies in message_snapshots

A reply resolves into referenced_message; a forward has none. TEBHOS captures the snapshot at forward time and it never follows later edits to the source.

Example

{
  "id": "1501320000000000000",
  "channel_id": "1501314428688998182",
  "author": {
    "id": "1501314428688990000",
    "username": "ada",
    "discriminator": "0",
    "global_name": "Ada",
    "avatar": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
    "avatar_color": null,
    "flags": 0
  },
  "type": 0,
  "flags": 0,
  "content": "shipping it",
  "timestamp": "2026-08-31T09:14:02.000Z",
  "edited_timestamp": null,
  "pinned": false,
  "mention_everyone": false,
  "tts": false,
  "mentions": [],
  "mention_roles": [],
  "embeds": [],
  "attachments": [],
  "stickers": []
}

Message types

Value Name Description
0 DEFAULT Ordinary message (also used for forwards and webhook messages)
1 RECIPIENT_ADD Group direct message recipient-add system message
2 RECIPIENT_REMOVE Group direct message recipient-remove system message
3 CALL Private call system message
4 CHANNEL_NAME_CHANGE Group direct message name-change system message
5 CHANNEL_ICON_CHANGE Group direct message icon-change system message
6 CHANNEL_PINNED_MESSAGE Pin system message, referencing the pinned message
7 USER_JOIN Guild join system message
19 REPLY Message with a reply reference

Only DEFAULT and REPLY messages can be pinned or unpinned; any other type returns 400 BAD_REQUEST.

Message flags

Value Name Description
1 << 2 SUPPRESS_EMBEDS Suppress rendering of embeds (link previews are still resolved but not broadcast)
1 << 12 SUPPRESS_NOTIFICATIONS Do not generate push or desktop notifications
1 << 13 VOICE_MESSAGE The message is a voice message
1 << 17 COMPACT_ATTACHMENTS Render attachments in a compact layout

These bits are the complete sendable set. A create keeps only these bits of the supplied value, and a modify replaces only these bits and keeps every other stored bit. Any other bit is discarded without an error.

Message attachment object

An attachment is one file stored against a message and served from the TEBHOS CDN.

Structure

Field Type Description
id snowflake The ID of the attachment
filename string The stored filename
title ?string Display title, or null
description ?string Alternative text, or null
content_type ?string The media type recorded at upload
content_hash ?string SHA-256 hash of the stored bytes, or null when it was never computed
size integer The stored size in bytes
url ?string The public file URL: https://cdn.tebhos.com/attachments/{channel_id}/{attachment_id}/{filename}
proxy_url ?string For images (except SVG): a resizable URL of the form https://tebhos.com/api/v1/files/attachments/{channel_id}/{attachment_id}/{filename}. null for other files
width? integer Pixel width for visual media
height? integer Pixel height for visual media
placeholder? string Encoded media placeholder
flags integer Attachment flags
nsfw? boolean Set by TEBHOS media safety scanning; true means the file was flagged and clients hide it behind a click-through
duration? integer Audio or video duration in seconds
waveform? string Base64-encoded voice waveform supplied by the uploader
expires_at ?ISO8601 timestamp The recorded storage expiry of the attachment, or null
expired? boolean Whether expires_at has passed

Attachment URLs are not signed and do not expire on their own. proxy_url accepts width, height and format query parameters; see Media proxy.

Example

{
  "id": "1501320000000000001",
  "filename": "diagram.png",
  "title": null,
  "description": "Sequence diagram",
  "content_type": "image/png",
  "content_hash": null,
  "size": 18422,
  "url": "https://cdn.tebhos.com/attachments/1501314428688998182/1501320000000000001/diagram.png",
  "proxy_url": "https://tebhos.com/api/v1/files/attachments/1501314428688998182/1501320000000000001/diagram.png",
  "width": 1280,
  "height": 720,
  "flags": 0,
  "expires_at": null
}

Attachment flags

Value Name Description
1 << 3 IS_SPOILER Attachment is presented as a spoiler
1 << 4 CONTAINS_EXPLICIT_MEDIA Media inspection marked the attachment explicit
1 << 5 IS_ANIMATED Attachment is animated

Emoji object

Field Type Description
id ?snowflake The ID of a custom emoji, or null for a Unicode emoji
name string The custom emoji name, or the Unicode emoji itself
animated? boolean Whether a custom emoji is animated

Reaction object

A reaction object summarises one emoji group on a message.

Field Type Description
emoji emoji object The emoji this group counts
count integer The number of users who added this emoji
me boolean Whether the caller added this emoji

Groups are listed in the order their first reaction was added. TEBHOS applies no per-message ceiling on reaction groups or users.

Example

{
  "emoji": {"id": "1501314428688991234", "name": "party_parrot", "animated": true},
  "count": 7,
  "me": true
}

Message reference object

Field Type Description
channel_id snowflake The ID of the referenced channel
message_id snowflake The ID of the referenced message
guild_id? snowflake The ID of the referenced guild
type integer Message reference type

Message reference types

Value Name Description
0 DEFAULT Reply reference (the default when type is omitted)
1 FORWARD Forward reference represented by message_snapshots

Message snapshot object

A snapshot is a flat, immutable copy of one forwarded message. It has no message ID, channel ID, or author.

Field Type Description
content? string Snapshotted text
timestamp ISO8601 timestamp Original creation time
edited_timestamp? ISO8601 timestamp Original edit time
mentions? array[snowflake] Snapshotted mentioned user IDs
mention_roles? array[snowflake] Snapshotted mentioned role IDs
embeds? array[embed object] Snapshotted embeds (omitted when the source had SUPPRESS_EMBEDS)
attachments? array[message attachment object] Snapshotted attachments. They are copied into the destination channel, so deleting the source leaves them intact
stickers? array[sticker item object] Snapshotted stickers
type integer Original message type
flags integer Original message flags

Message call object

Field Type Description
participants array[snowflake] Participant user IDs
ended_timestamp ?ISO8601 timestamp Call end time, or null while the call is active

Channel mention object

Field Type Description
id snowflake The ID of the mentioned channel
name string The channel name
type integer Channel type

Sticker item object

Field Type Description
id snowflake The ID of the sticker
name string The sticker name at send time
animated boolean Whether the sticker is animated
image_url? string The current image URL of the sticker, omitted when the sticker no longer exists
guild_id? snowflake The ID of the guild that owns the sticker
legacy_id? string An older identifier of the sticker, present on some stickers

Embed object

An embed is a rendered preview on a message: either resolved from a link in the content or supplied as a rich embed input.

Structure

Field Type Description
type string Embed type
title? string Embed title
description? string Embed description
url? string Embed destination URL
timestamp? ISO8601 timestamp Embed timestamp
color? integer Embed colour as a 24-bit RGB integer
author? embed author object Embed author
provider? embed provider object External provider (link previews only)
thumbnail? embed media object Thumbnail image
image? embed media object Main image
video? embed media object Video media (link previews only)
audio? embed media object Audio media (link previews only)
footer? embed footer object Embed footer
fields? array[embed field object] Embed fields
html? string Sanitised oEmbed markup (link previews only)
children? array[embed object] Nested link-preview embeds

Every optional field is omitted when it has no value.

Example

{
  "type": "rich",
  "title": "Release 2.4.0",
  "description": "Voice reconnects are now instant.",
  "url": "https://example.com/releases/2.4.0",
  "color": 5793266,
  "footer": {"text": "example.com"},
  "fields": [{"name": "Platform", "value": "Desktop", "inline": true}]
}

Embed types

Value Description
rich A rich embed supplied by a bot, user or webhook
link A generic link preview, and the type of a preview that has not been resolved yet
image An image preview resolved from a link
video A video preview resolved from a link
gifv An animated looping video preview (for example a KLIPY GIF link)
article An encyclopaedia article preview
social A social-network post preview

Embed author object

Field Type Description
name string Author name
url? string Author link
icon_url? string Author icon URL

Embed media object

Field Type Description
url string Media URL
proxy_url string Always equal to url
width? integer Pixel width
height? integer Pixel height
duration? integer Duration in seconds
description? string Media description
content_type? string Media type
placeholder? string Encoded media placeholder
flags? integer Media flags using CONTAINS_EXPLICIT_MEDIA and IS_ANIMATED from attachment flags
Field Type Description
text string Footer text
icon_url? string Footer icon URL

Embed field object

Field Type Description
name string Field name
value string Field value
inline boolean Whether the field can render inline

Embed provider object

Field Type Description
name string Provider name
url? string Provider URL

Allowed mentions object

An allowed mentions object selects which mentions written in the message text notify anyone. The mentions still render and are still stored on the message; only notifications are filtered.

Field Type Description
parse? array[string] Mention categories that notify: users, roles, everyone
users? array[snowflake] User IDs allowed to be notified (used instead of parse: ["users"])
roles? array[snowflake] Role IDs allowed to be notified (used instead of parse: ["roles"])
replied_user? boolean Whether the author of the replied-to message is notified (default true)

When the object is omitted, every mention notifies. When it is present, parse defaults to all three categories unless users or roles is given; a category then notifies only when parse names it or its explicit ID list allows the ID. An empty object {} suppresses every notification, including the reply notification. A non-empty parse together with a non-empty users or roles list is rejected with 400 PARSE_AND_USERS_OR_ROLES_CANNOT_BE_USED_TOGETHER.

A reply (outside one-to-one DMs) adds the author of the replied-to message to the message's mentions and notifies them, unless replied_user is false or the object is {}. Replying to your own message never notifies you.

Independently of this object, @everyone and @here notify only when the sender holds MENTION_EVERYONE in a guild channel, and a role mention notifies the role's members only when the role is mentionable or the sender holds MENTION_EVERYONE.

Rich embed input object

The shape a bot or user supplies in embeds on Create message. TEBHOS stores every supplied embed with the rich type.

Field Type Description
url? string Embed destination URL, http or https
title? string Embed title, up to 256 characters
description? string Embed description, up to 4,096 characters
color? integer Embed colour as a 24-bit RGB integer, 0 to 16777215 (0xFFFFFF)
timestamp? ISO8601 timestamp or integer Embed timestamp, as an ISO 8601 string or as milliseconds since the Unix epoch
author? object {name, url?, icon_url?}. name is up to 256 characters. Dropped when name is missing or empty
image? object {url, description?}. Dropped when url is missing or empty. url may be attachment://{filename}, see Embed images from attachments
thumbnail? object {url, description?}. Dropped when url is missing or empty. url may be attachment://{filename}
footer? object {text, icon_url?}. text is up to 2,048 characters. Dropped when text is missing or empty
fields? array[object] Up to 25 {name, value, inline?} entries. name is 1-256 characters and value 0-1,024 characters

Every URL (url, author.url, author.icon_url, image.url, thumbnail.url, footer.icon_url) must use http or https. image.url and thumbnail.url may instead be attachment://{filename}.

Embed limits

Limit Value When exceeded
Embeds supplied per message 10 400 INVALID_FORM_BODY, error code TOO_MANY_EMBEDS at path embeds
Total characters across all supplied embeds 6,000 400 INVALID_FORM_BODY, error code EMBEDS_EXCEED_MAX_CHARACTERS at path embeds
Title 256 characters Schema violation, see below
Description 4,096 characters Schema violation
Fields per embed 25 Schema violation
Field name / value 1-256 / 0-1,024 characters Schema violation
Footer text 2,048 characters Schema violation
Author name 256 characters Schema violation
Colour 0 to 16777215 Schema violation
Link previews resolved from content 3 URLs Later URLs get no preview

The 6,000-character total counts the title, description, footer.text, author.name, and every field name and value of every supplied embed.

Supplied embeds come first, and link previews for URLs in the content are added after them up to 10 embeds in all; previews beyond that are dropped.

How a schema violation is reported depends on the body:

  • JSON body: 400 INVALID_FORM_BODY with one error at path message_data, code INVALID_MESSAGE_DATA. The response does not say which field failed.
  • Multipart body (payload_json): 400 INVALID_FORM_BODY with one error per failing field, for example path embeds.0.title with code STRING_LENGTH_INVALID.

The same limits apply to Modify message and to Execute webhook.

Embed images from attachments

An embed's image.url or thumbnail.url can point at a file sent with the same message as attachment://{filename}, on Create message and Modify message. The reference is resolved when the message is stored:

  • the URL is replaced by the attachment's file URL, and the attachment's width, height, content type and placeholder are copied into the embed media;
  • the referenced attachment is removed from the message's attachments list, so the file appears only inside the embed.

{filename} is the attachment's file name. TEBHOS stores file names with unsafe characters replaced by _ (for example my photo.png becomes my_photo.png); either spelling matches. Only image files (png, jpg, jpeg, webp, gif) can be referenced. The request fails with 400 INVALID_FORM_BODY before anything is stored when:

errors[].code Condition
CANNOT_REFERENCE_ATTACHMENTS_WITHOUT_ATTACHMENTS The message carries no attachments
REFERENCED_ATTACHMENT_NOT_FOUND No attachment of the message has that file name
ATTACHMENT_MUST_BE_IMAGE The referenced file is not an image

Attachment uploads

A message references files that were uploaded first. The flow is:

  1. POST /channels/{channel_id}/attachments with {"attachments": [{"id", "filename", "file_size", "content_type?"}]} (1-10 entries). Each result has upload_filename (the upload key) and upload_mode. A file of at most 10 MiB gets upload_mode: "singlepart" and one upload_url (a presigned PUT URL valid for 300 seconds); a larger file gets upload_mode: "multipart" with upload_id, part_size and a parts list of per-part upload URLs.
  2. PUT each file to its upload_url, or each part of a multipart file to its part URL.
  3. For multipart files, call Complete attachment uploads (POST /channels/{channel_id}/attachments/complete).
  4. Send the message with the upload keys in attachment_ids (or, on the channel-scoped route, in attachments[].upload_filename).

A message can also carry its files directly as multipart/form-data; see Multipart body.

Uploading requires SEND_MESSAGES and ATTACH_FILES in a guild channel. Bot tokens are accepted. The per-file ceiling is 10 MiB, 200 MiB with TEBHOS Pro, or the guild's boost ceiling (50 MiB at level 1, 100 MiB at level 2), whichever is larger. The route allows 10 requests per 10 seconds for each user and channel (channel:attachment:upload). Uploads describes the upload routes in full.

List channel messages

GET /channels/{channel_id}/messages

Returns an array of message objects, newest first. Requires VIEW_CHANNEL.

Query parameters

Field Type Description
limit? integer Number of messages to return (at most 100, default 50)
before? snowflake Return messages before this message ID
after? snowflake Return messages after this message ID
around? snowflake Return a window centred on this message ID, overriding before and after. With limit 50 the window holds 25 newer messages, the target and 24 older messages

before and after can be combined. A page can hold fewer than limit messages when some fall outside the caller's message history cutoff. A caller without READ_MESSAGE_HISTORY in a guild with no cutoff receives an empty array.

Rate limit

100 requests per 10 seconds for each user and channel, on the channel:messages:read bucket.

curl "https://tebhos.com/api/v1/channels/1501314428688998182/messages?limit=20" \
  -H "Authorization: Bot $TEBHOS_BOT_TOKEN"

List messages from multiple channels

POST /channels/messages/bulk

Returns bounded message windows from several channels in one request. User-only.

Each channel is authorised exactly like List channel messages. An inaccessible channel fails the whole request.

JSON body

Field Type Description
requests array[object] 1-25 entries, each {channel_id, limit, before?, after?, around?}. limit must be 1-25, and the limit values must sum to at most 250

Response body

Field Type Description
channels array[object] One {channel_id, messages} entry per request entry, in request order. messages is newest first

Rate limit

20 requests per 10 seconds for each user, on the channel:messages:bulk_read bucket.

Get message

GET /channels/{channel_id}/messages/{message_id}

Returns one message object. Requires VIEW_CHANNEL. A message that does not belong to the channel, does not exist, or lies outside the caller's message history cutoff returns 404 NOT_FOUND.

Rate limit

100 requests per 10 seconds for each user and channel, on the channel:message:read bucket.

Create message

POST /channels/{channel_id}/messages

Creates a message and returns 200 with the created message object. Emits a Message Create Gateway event. The body is JSON or, to send files in the same request, multipart/form-data (see Multipart body).

Limitations

  • The channel must be text-bearing; a link channel returns 400 BAD_REQUEST.
  • A guild channel requires VIEW_CHANNEL and SEND_MESSAGES, and the caller must be a member who is not timed out.
  • A locked channel refuses every sender with 403 FORBIDDEN.
  • Attachments require ATTACH_FILES and embeds require EMBED_LINKS in a guild channel (403 FORBIDDEN otherwise). Without EMBED_LINKS, links in the content get no preview.
  • A custom emoji or sticker from another guild requires USE_EXTERNAL_EMOJIS or USE_EXTERNAL_STICKERS and TEBHOS Pro (403 FORBIDDEN).
  • In a one-to-one direct message, a block in either direction returns 403 FORBIDDEN, and a recipient whose privacy settings refuse the sender returns 400 CANNOT_SEND_MESSAGES_TO_USER.
  • Slowmode applies unless the caller holds BYPASS_SLOWMODE; see Slowmode.

JSON body

Field Type Description
content? string Message text. See content length
embeds? array[rich embed input object] Rich embeds (see embed limits)
attachment_ids? array[string] Upload keys from Attachment uploads, at most 10
attachments? array[object] Metadata for the uploads: {upload_filename, width?, height?, description?, title?, duration?, waveform?, flags?}. On this route an entry's upload_filename also adds the upload to attachment_ids, and a flags value with IS_SPOILER marks it as a spoiler
spoiler_attachment_ids? array[string] Upload keys to mark as spoilers
sticker_ids? array[snowflake] At most 3 sticker IDs
message_reference? object {message_id, channel_id?, guild_id?, type?}. See replies and forwards
reply_to_id? snowflake Shorthand for a reply reference to a message in the same channel
allowed_mentions? allowed mentions object Notification policy
flags? integer Message flags, masked to the sendable set
tts? boolean Text-to-speech request. Forced to false in a guild channel when the caller lacks SEND_TTS_MESSAGES
nonce? string | integer A caller-generated nonce: a string of 1-32 characters or a non-negative integer (turned into a string). It is echoed as nonce on the created message and its Gateway event. Any other value fails with 400 (message_data / INVALID_MESSAGE_DATA)
_optimistic_id? string Older name for nonce, used when nonce is absent

content, sticker_ids and an embed's fields may be null, which counts as absent. A message must have visible content, an attachment, a sticker or an embed. An otherwise empty request fails with 400 CANNOT_SEND_EMPTY_MESSAGE.

The smallest body that works:

{
  "content": "hello"
}

A reused nonce replays the first result

A nonce is remembered for the sender for five minutes. Reusing it in the same channel returns the message the first request created (200) instead of creating a duplicate. Reusing it in a different channel returns 404 NOT_FOUND. A client that retries a send after a timeout should reuse the same nonce.

Multipart body

With Content-Type: multipart/form-data the request carries:

Part Description
payload_json The JSON body described above, as a string
files[n] A file, where n is 0-9. The legacy names file and file{n} are also accepted
content, nonce, tts, flags, sticker_ids Optional plain fields; each overrides the same key of payload_json

An attachments entry whose id equals a file's index supplies that file's filename, description and flags (IS_SPOILER marks a spoiler). Every file passes the same checks as an uploaded one (permissions, size limit, blocked-file list) and the whole body may be at most 100 MiB. Embeds can reference the files with attachment://{filename}.

curl -X POST https://tebhos.com/api/v1/channels/1501314428688998182/messages \
  -H "Authorization: Bot $TEBHOS_BOT_TOKEN" \
  -F 'payload_json={"content":"Chart","embeds":[{"title":"Today","image":{"url":"attachment://chart.png"}}],"attachments":[{"id":0,"filename":"chart.png"}]}' \
  -F "files[0][email protected]"

Content length

Sender Maximum characters
User without TEBHOS Pro 2,000
User with TEBHOS Pro 4,000
Bot 4,000
Webhook (Execute webhook) 4,000

Content above 4,000 characters fails validation with 400 INVALID_FORM_BODY; content between the two limits from a user without TEBHOS Pro fails with 400 BAD_REQUEST.

Replies and forwards

A reply (message_reference.type omitted or 0, or reply_to_id) must target a message in the same channel; otherwise the request fails with 400 BAD_REQUEST. The created message has type REPLY (19) and a resolved referenced_message.

A forward (message_reference.type 1) requires both channel_id and message_id and must not carry content, attachments or stickers (400 BAD_REQUEST). The caller must be able to view the source channel, and a source message that does not exist returns 404. The created message has type DEFAULT and a message_snapshots entry; the source's attachments are copied into the destination channel.

Slowmode

In a guild channel with a non-zero rate_limit_per_user, each member may send one message per interval. A send inside the interval returns 400 SLOWMODE_RATE_LIMITED (not 429), with a Retry-After header in whole seconds and a top-level retry_after member in decimal seconds:

{
  "error": "400 Bad Request",
  "code": "SLOWMODE_RATE_LIMITED",
  "message": "...",
  "retry_after": 4.2
}

The owner, administrators, and members with BYPASS_SLOWMODE are exempt; MANAGE_MESSAGES or MANAGE_CHANNELS alone does not exempt a member. Private channels have no slowmode.

Side effects

The operation stores the message, binds the referenced uploads, advances the sender's read state (except for bots), reopens a closed direct message for its recipients, indexes the message for search, and emits Message Create to every session that can see the channel. Up to 3 links in the content are resolved into previews asynchronously; each resolution emits Message Update unless SUPPRESS_EMBEDS is set.

Rate limit

20 requests per 10 seconds for each user and channel, on the channel:message:create bucket.

curl -X POST https://tebhos.com/api/v1/channels/1501314428688998182/messages \
  -H "Authorization: Bot $TEBHOS_BOT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"content": "Build finished", "embeds": [{"title": "Release 2.4.0", "color": 5793266}]}'
const res = await fetch(
  "https://tebhos.com/api/v1/channels/1501314428688998182/messages",
  {
    method: "POST",
    headers: {
      Authorization: `Bot ${process.env.TEBHOS_BOT_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ content: "Build finished" }),
  },
);
const message = await res.json();
import os, requests

res = requests.post(
    "https://tebhos.com/api/v1/channels/1501314428688998182/messages",
    headers={"Authorization": f"Bot {os.environ['TEBHOS_BOT_TOKEN']}"},
    json={"content": "Build finished"},
)
message = res.json()

Modify message

PATCH /channels/{channel_id}/messages/{message_id}

Modifies a message and returns the updated message object. Emits a Message Update Gateway event. Accepts user sessions and bot tokens. The body is JSON or multipart/form-data (payload_json plus files[n], as in Multipart body).

JSON body

Field Type Description
content? string Replacement text (at most 4,000 characters, and at most 2,000 without TEBHOS Pro). "" together with embeds clears the text. Only the author can change content
flags? integer Replacement message flags; only the sendable bits change. The author, or a member with MANAGE_MESSAGES, can change flags
embeds? array[rich embed input object] Replaces the message's supplied embeds ([] removes them). Only the author; requires EMBED_LINKS in a guild channel. Link previews are kept. Images may use attachment://{filename}
attachments? array[object] The complete new attachment list, see Editing attachments. Omit it to keep the attachments unchanged

At least one field is required, and a blank content needs non-empty embeds, attachments or flags beside it (400 CANNOT_SEND_EMPTY_MESSAGE otherwise). A caller who is not the author and supplies content or embeds receives 403 FORBIDDEN.

A content change advances edited_timestamp, recalculates mentions, replaces the link previews with previews of the links in the new content, and re-indexes the message for search. A flags-only or attachments-only change does not mark the message edited.

Editing attachments

Each entry of attachments is one of:

Entry Effect
{id, title?, description?} Keeps an existing attachment. id is the attachment ID, or its position in the current list. title and description replace the stored values (null clears them)
{upload_filename, filename?, description?, title?, width?, height?, duration?, waveform?, flags?} Adds a file uploaded through Attachment uploads by the caller in this channel. Requires ATTACH_FILES in a guild channel; the file passes the blocked-file check and the size limit

An existing attachment that is not listed is removed from the message; [] removes them all. At most 10 entries. In a multipart request, entries whose id equals a file index become new attachments; when files are sent, the new list is the listed existing attachments plus the files. An entry that names a filename without a matching file fails with 400 INVALID_FORM_BODY (errors[].code NO_FILE_FOR_ATTACHMENT_METADATA).

A member who is not the author may send attachments only in a guild channel and with MANAGE_MESSAGES: they can drop attachments or change title/description, and any other key fails with 400 INVALID_FORM_BODY (errors[].code CANNOT_EDIT_ATTACHMENT_METADATA). Without the permission the request fails with 403 CANNOT_EDIT_OTHER_USER_MESSAGE.

Status Condition
400 INVALID_FORM_BODY (errors[].code FILE_NOT_FOUND) An upload_filename is unknown, already used, or belongs to another account or channel
400 FILE_SIZE_TOO_LARGE A new file exceeds the caller's size limit
403 CONTENT_BLOCKED A new file is on the blocked-file list
403 MISSING_PERMISSIONS New files without ATTACH_FILES

Not supported on TEBHOS

allowed_mentions cannot be changed through this route.

Rate limit

20 requests per 10 seconds for each user and channel, on the channel:message:update bucket.

Delete message

DELETE /channels/{channel_id}/messages/{message_id}

Deletes one message and returns 200 with a JSON body containing a message string. Emits a Message Delete Gateway event.

The author can delete their own message. Deleting another user's message requires MANAGE_MESSAGES (403 FORBIDDEN otherwise).

Side effects

The message is removed and deleted from search. Message Delete carries the channel ID, message ID, the deleted content and the author ID. When the message was pinned, Channel Pins Update is also emitted with the channel's unchanged last-pin timestamp.

Rate limit

20 requests per 10 seconds for each user and channel, on the channel:message:delete bucket.

Delete message attachment

DELETE /channels/{channel_id}/messages/{message_id}/attachments/{attachment_id}

Removes one attachment from the caller's own message and returns 204. Accepts user sessions and bot tokens. Only the author can use this route; MANAGE_MESSAGES is not enough (403 MISSING_PERMISSIONS). The caller must be able to view the channel.

attachment_id is the attachment's id from the message object.

When the attachment is the message's last one and the message has no content, embeds or stickers, the whole message is deleted and Message Delete is emitted. Otherwise the file is deleted from storage (and queued for CDN cache purge), the attachment is removed from the message, edited_timestamp advances, and Message Update is emitted.

Status Condition
204 The attachment (or the whole message) was deleted
403 MISSING_PERMISSIONS The caller is not the author
404 UNKNOWN_MESSAGE The message does not exist in this channel, has no attachments, or has no attachment with this ID
404 / 403 The caller cannot access the channel (see Channel resolution)

DELETE /messages/{message_id}/attachments/{attachment_id} is an older TEBHOS form of the same operation. It resolves the channel from the message and answers 200 with the updated message object, or 200 with a message string when the whole message was deleted.

Rate limit

20 requests per 10 seconds for each user and channel, on the channel:message:delete bucket.

Bulk delete messages

POST /channels/{channel_id}/messages/bulk-delete

Deletes 1-100 messages in one channel and returns 204. Requires MANAGE_MESSAGES. The alias POST /channels/{channel_id}/messages/bulk_delete behaves identically.

JSON body

Field Type Description
message_ids? array[snowflake] Message IDs to delete (1-100)
messages? array[snowflake] Alias for message_ids, used when message_ids is absent

An empty list or more than 100 IDs fails with 400 BAD_REQUEST. IDs that are not valid, or that name no message in the channel, are skipped. Messages of any age can be selected.

Side effects

Every matching message is removed and deleted from search. One Message Delete Bulk with the deleted IDs is emitted to sessions that can see the channel.

Rate limit

10 requests per 10 seconds for each user and channel, on the channel:message:bulk_delete bucket.

Bulk delete own messages

POST /channels/{channel_id}/messages/bulk-delete-mine

Deletes every message the caller authored in one channel and returns 202. Requires VIEW_CHANNEL and sudo mode: a user session must have obtained a sudo grant through POST /auth/sudo within the last few minutes, otherwise the request fails with 403 SUDO_MODE_REQUIRED. A bot token always satisfies sudo mode.

The deletion runs inside the request, so the messages are gone when the response arrives. One Message Delete Bulk is emitted. The same bucket as Bulk delete messages applies.

There is no restore

Every message the caller authored in the channel is deleted, without an age boundary.

Two wider variants exist with the same sudo requirement: POST /users/@me/guilds/{guild_id}/messages/bulk-delete-mine deletes the caller's messages in every channel of a guild (10 requests per 10 seconds per user and guild), and POST /users/@me/messages/bulk-delete-mine deletes across the caller's direct messages, group direct messages and guilds (user-only, 5 requests per 30 minutes per user; the body accepts include_dms, include_group_dms, include_guilds (each default true), and start_date/end_date as ISO8601 timestamps).

Purge personal notes

POST /channels/{channel_id}/messages/purge

Deletes every message in the caller's personal notes channel and returns {"deleted_count": N}. channel_id must equal the caller's user ID; any other channel returns 400 BAD_REQUEST. Emits Message Delete Bulk when anything was deleted.

Rate limit

2 requests per minute for each user and channel, on the channel:message:purge bucket.

List pinned messages

GET /channels/{channel_id}/pins

Returns one page of pinned messages, most recently pinned first. Requires VIEW_CHANNEL. The alias GET /channels/{channel_id}/messages/pins behaves identically.

A guild caller without READ_MESSAGE_HISTORY sees only pins whose message falls on or after the history cutoff.

Query parameters

Field Type Description
limit? integer Number of pins to return (at most 50, default 50)
before? snowflake The ID of the last pinned message of the previous page

Response body

Field Type Description
items array[message object] Pinned messages, most recently pinned first
has_more boolean Whether another page may exist

Rate limit

20 requests per 10 seconds for each user and channel, on the channel:pins bucket.

Pin message

PUT /channels/{channel_id}/pins/{message_id}

Pins a DEFAULT or REPLY message and returns 204. Requires PIN_MESSAGES in a guild channel; any recipient can pin in a private channel. A system message returns 400 BAD_REQUEST, and a message that is not in the channel returns 404.

Side effects

The message's pin time and the channel's last-pin timestamp are set to now, and a CHANNEL_PINNED_MESSAGE (6) system message referencing the pinned message is created. TEBHOS emits Message Update for the message (pinned: true), Channel Pins Update with channel_id, message_id, guild_id and last_pin_timestamp, and Message Create for the system message.

Note

Pinning an already pinned message refreshes its pin time and creates another system message.

Rate limit

20 requests per 10 seconds for each user and channel, on the channel:pins bucket.

Unpin message

DELETE /channels/{channel_id}/pins/{message_id}

Unpins a message and returns 204. Authorisation is the same as Pin message. Emits Message Update (pinned: false) and Channel Pins Update with the channel's unchanged last-pin timestamp. No system message is created, and the original CHANNEL_PINNED_MESSAGE stays. The operation is idempotent. Same bucket as pinning.

Acknowledge pins

POST /channels/{channel_id}/pins/ack

Records the channel's current last-pin timestamp in the caller's read state and returns 204. User-only. The request has no body.

When the channel has never held a pin, nothing is written and nothing is emitted. Otherwise TEBHOS writes the channel's last-pin timestamp and emits Channel Pins ACK with channel_id and last_pin_timestamp to the caller's own sessions.

Rate limit

20 requests per 10 seconds for each user and channel, on the channel:pins bucket.

Reaction emoji path value

The emoji path value is URI-encoded and takes one of these forms:

Form Example Meaning
Unicode emoji %F0%9F%91%8D A standard emoji
name:id party_parrot:1501314428688991234 A custom emoji
a:name:id a:party_parrot:1501314428688991234 An animated custom emoji
<:name:id> / <a:name:id> %3C%3Aparty_parrot%3A1501314428688991234%3E A custom emoji in message-token form

All custom forms address the same stored reaction. The add, remove-own and remove-user routes accept an optional session_id query parameter (1-64 characters) that is echoed in the Gateway event so the originating session can skip its own echo.

List reaction users

GET /channels/{channel_id}/messages/{message_id}/reactions/{emoji}/users

Returns one page of the users who added a reaction. Requires VIEW_CHANNEL; a message outside the caller's history cutoff returns 404.

Query parameters

Field Type Description
limit? integer Number of users to return (1-100, default 100)
after? snowflake Return users with an ID after this user ID

Response body

Field Type Description
items array[object] Users in ascending user ID order, each {id, name, image_url} where image_url is the avatar hash (empty string when none)
has_more boolean Whether another page exists
next_after ?snowflake The cursor for the next page (the last user ID), or null

Rate limit

30 requests per 10 seconds for each user and channel, on the channel:reactions bucket.

List reaction users unpaged

GET /channels/{channel_id}/messages/{message_id}/reactions/{emoji}

Returns {emoji, users}, where emoji is an emoji object and users is every user who added the reaction, in the same {id, name, image_url} shape. Same authorisation and bucket as List reaction users.

Add own reaction

PUT /channels/{channel_id}/messages/{message_id}/reactions/{emoji}/@me

Adds the caller's reaction and returns 204. Emits Message Reaction Add.

  • The message must belong to the channel (404 otherwise), and the caller must be able to view it under the history cutoff.
  • A timed-out member cannot react (403 FORBIDDEN).
  • Creating a new emoji group in a guild channel requires ADD_REACTIONS; joining an existing group does not.
  • A new group with a custom emoji from another guild requires USE_EXTERNAL_EMOJIS and TEBHOS Pro (403 FORBIDDEN).

The operation is idempotent, and every successful call emits the event. Message Reaction Add carries channel_id, message_id, user_id, emoji and session_id when supplied.

Rate limit

30 requests per 10 seconds for each user and channel, on the channel:reactions bucket.

Remove own reaction

DELETE /channels/{channel_id}/messages/{message_id}/reactions/{emoji}/@me

Removes the caller's reaction and returns 204. Emits Message Reaction Remove whether or not the reaction existed. Same bucket as adding.

Remove another user's reaction

DELETE /channels/{channel_id}/messages/{message_id}/reactions/{emoji}/{user_id}

Removes one user's reaction and returns 204. Requires MANAGE_MESSAGES, including when user_id is the caller. Emits Message Reaction Remove with the target user ID. Same bucket as adding.

Remove all reactions for emoji

DELETE /channels/{channel_id}/messages/{message_id}/reactions/{emoji}

Removes every reaction for one emoji and returns 204. Requires MANAGE_MESSAGES. Emits one Message Reaction Remove Emoji. Same bucket as adding.

Remove all reactions

DELETE /channels/{channel_id}/messages/{message_id}/reactions

Removes every reaction from a message and returns 204. Requires MANAGE_MESSAGES. Emits one Message Reaction Remove All. Same bucket as adding.

Indicate typing

POST /channels/{channel_id}/typing

Publishes typing activity and returns 204. Requires VIEW_CHANNEL and SEND_MESSAGES in a guild channel, and the caller must not be timed out; a private channel needs only recipient access.

Emits Typing Start with channel_id, user_id and a Unix timestamp in seconds. Nothing is stored; clients expire the indicator themselves.

Rate limit

20 requests per 10 seconds for each user and channel, on the channel:typing bucket.

Acknowledge message

POST /channels/{channel_id}/messages/{message_id}/ack

Advances the caller's read state in the channel through message_id. User-only. TEBHOS resolves neither the channel nor the message.

The stored mention count is reset to 0, and a message_id below the stored watermark leaves the entry unchanged. There is no request body; use Acknowledge read states to store a mention count or to move the watermark backwards.

Response

200 with {"read_states": [read state], "read_state_proto": string}; see Read state object. Emits Message ACK to the caller's own sessions.

Rate limit

100 requests per 10 seconds for each user and channel, on the channel:message:ack bucket.

Clear channel read state

DELETE /channels/{channel_id}/messages/ack

Deletes the caller's read state entry for one channel and returns 204. User-only. The channel is not resolved, the operation is idempotent, and no Gateway event is emitted.

Rate limit

40 requests per 10 seconds for each user and channel, on the channel:read_state:delete bucket.