User content collections¶
Recent mentions, saved messages and scheduled messages are private lists that only the account owning them can read. Other routes on this page delete the messages the caller wrote: one form runs straight away with a filter, the other deletes everything and waits a day. Data exports are covered on Data harvests.
All routes here are for user accounts only. A request with a Bot token is rejected with 403 ACCESS_DENIED when the token is valid, or with 401 when it is not (see Authentication). Errors use the standard error response with error, code and message fields.
Saved message object¶
A saved entry is a private bookmark that pairs one message with the channel it was saved from. Saving a message does not notify anyone, including its author.
The entry does not store a copy of the message. TEBHOS reads the message again on every list request, so an entry can carry content on one read and not on the next.
Structure¶
| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the saved entry. Always the same as message_id, because an account can save a message only once |
| message_id | snowflake | The ID of the saved message |
| channel_id | snowflake | The channel given when the message was saved |
| saved_at | ISO8601 timestamp | When the message was saved |
| status | string | The saved message status of the entry |
| content | ?string | The message text, or null when status is missing_permissions |
| author | ?partial user object | The message author, or null when status is missing_permissions. When the author cannot be resolved, this object contains only id |
| created_at | ?ISO8601 timestamp | Always null. The message creation time can be read from the message_id snowflake |
| is_deleted | boolean | Always false |
The entry is a summary of the message, not a full message object. Fetch the message from its channel to get attachments, embeds and reactions.
Saved message statuses¶
| Value | Description |
|---|---|
| available | The caller can still view the channel, and content and author are filled in |
| missing_permissions | The caller can no longer view the channel (lost VIEW_CHANNEL, left the guild, or the channel is gone), so content and author are null. The entry is kept so the caller can still remove it |
Recent mention object¶
A recent mention entry is created automatically when a message mentions the account. There is no route for creating entries. Entries expire from storage 7 days after they are recorded.
Structure¶
| Field | Type | Description |
|---|---|---|
| message_id | snowflake | The ID of the message that mentioned the account |
| channel_id | snowflake | The channel containing the message |
| guild_id | snowflake | The guild of the channel, or "0" for a channel outside a guild |
| mention_everyone | boolean | Whether the entry was recorded as an @everyone or @here mention |
| mention_role | boolean | Whether the entry was recorded as a role mention |
List recent mentions¶
GET /users/@me/mentions
Returns an array of up to 100 recent mention entries for the caller, newest first (by descending message ID).
This route takes no query parameters: there is no limit, before or filter, and the response has no cursor. The entries are the stored records. TEBHOS does not load the messages or check them against the caller's current permissions, so an entry can refer to a message that has since been deleted. Fetch the message from its channel to show it.
Response¶
| Status | Body | Condition |
|---|---|---|
| 200 | array of recent mention objects | The entries were returned |
[
{
"message_id": "1501314428688998182",
"channel_id": "1501300000000000001",
"guild_id": "1501299999999999999",
"mention_everyone": false,
"mention_role": false
}
]
Rate limit¶
40 requests per 10 seconds per user, on the user:mentions:read bucket. See Rate limits.
Mark recent mentions read¶
POST /users/@me/mentions/read
Removes every entry from the caller's recent mention history. It takes no request body and returns 204 with an empty body.
No list of IDs
This route does not accept a message_ids list. It always clears the whole history. To remove a single entry, use Delete recent mention.
Removing entries does not mark the messages as read. The unread mention count on each channel stays the same. Read states explains acknowledgement.
Side effects¶
TEBHOS reads up to 1000 of the newest entries, deletes the whole history, and then sends one RECENT_MENTION_DELETE Gateway event ({"message_id": "..."}) to all of the caller's sessions for each entry it read.
Rate limit¶
60 requests per 10 seconds per user, on the user:mentions:delete bucket. This bucket is shared with Delete recent mention.
Delete recent mention¶
DELETE /users/@me/mentions/{message_id}
Removes one entry from the caller's recent mention history and returns 204 with an empty body.
Path parameters¶
| Field | Type | Description |
|---|---|---|
| message_id | snowflake | The message ID of the recent mention entry |
Response¶
| Status | Body | Condition |
|---|---|---|
| 204 | empty | The entry was removed, or no matching entry was found |
| 400 | error response | message_id is not a valid snowflake (BAD_REQUEST) |
Side effects¶
TEBHOS looks for the entry among the caller's 200 newest entries. If it finds a match, it deletes the entry and sends RECENT_MENTION_DELETE ({"message_id": "..."}) to all of the caller's sessions. If there is no match, including an entry older than the newest 200, nothing is written and no event is sent. The message stays in its channel.
Rate limit¶
60 requests per 10 seconds per user, on the user:mentions:delete bucket. This bucket is shared with Mark recent mentions read.
List saved messages¶
GET /users/@me/saved-messages
Returns the caller's saved messages, newest first (by descending message ID). The response is an object with an items array of saved message objects. It has no cursor, so use before and limit to page.
TEBHOS checks VIEW_CHANNEL once per channel. An entry whose channel the caller can no longer view is returned with status missing_permissions. An entry whose message no longer exists is left out of the response but is not deleted. Because of this, items can contain fewer entries than limit.
Query parameters¶
| Field | Type | Description |
|---|---|---|
| limit? | integer | The maximum number of entries read from the collection. Values outside the range are clamped to 1-100. Default 50 |
| before? | snowflake | Return only entries with a lower message ID. A value that cannot be parsed is ignored |
Response¶
| Status | Body | Condition |
|---|---|---|
| 200 | {"items": [saved message object]} |
The entries were returned |
| 500 | error response | Channel permissions could not be checked. TEBHOS returns an error rather than risk showing content the caller may not be allowed to see |
{
"items": [
{
"id": "1501314428688998182",
"message_id": "1501314428688998182",
"saved_at": "2026-09-20T18:04:33.120+00:00",
"channel_id": "1501300000000000001",
"status": "available",
"content": "Meeting notes are pinned.",
"is_deleted": false,
"created_at": null,
"author": {"id": "1501290000000000042", "username": "aria", "discriminator": "0", "global_name": "Aria", "avatar": null, "avatar_color": null, "flags": 0}
}
]
}
Rate limit¶
40 requests per 10 seconds per user, on the user:saved_messages:read bucket.
Save message¶
POST /users/@me/saved-messages
Adds a message to the caller's saved collection and returns 204 with an empty body. The caller needs VIEW_CHANNEL on a guild channel, or must be a recipient of a DM or group DM.
JSON body¶
| Field | Type | Description |
|---|---|---|
| channel_id | snowflake | The channel containing the message. This value is stored with the entry exactly as given |
| message_id | snowflake | The ID of the message to save |
Collection limit¶
An account can save at most 100 messages. This is a fixed limit, not an instance setting. Saving a new message when the collection is full returns 400 BAD_REQUEST. Saving a message that is already in the collection is always accepted, even when the collection is full.
Response¶
| Status | Body | Condition |
|---|---|---|
| 204 | empty | The message is in the saved collection |
| 400 | error response | An ID is not a valid snowflake, or the collection is full (BAD_REQUEST) |
| 403 | error response | The caller cannot view the channel |
| 404 | error response | The channel is unknown, or the message is not in that channel (NOT_FOUND) |
Side effects¶
Saving a message that is already stored rewrites the entry. On every accepted save, SAVED_MESSAGE_CREATE is sent to all of the caller's sessions. Its payload is the full message object as the caller can see it, with user_id and message_id added. If the message cannot be loaded for the event, the save still succeeds but no event is sent.
Rate limit¶
30 requests per 10 seconds per user, on the user:saved_messages:write bucket. This bucket is shared with Unsave message.
Unsave message¶
DELETE /users/@me/saved-messages/{message_id}
Removes a message from the caller's saved collection and returns 204 with an empty body. The caller does not need current access to the channel.
Path parameters¶
| Field | Type | Description |
|---|---|---|
| message_id | snowflake | The message ID of the saved entry |
Response¶
| Status | Body | Condition |
|---|---|---|
| 204 | empty | The message is no longer in the saved collection |
| 400 | error response | message_id is not a valid snowflake |
Side effects¶
TEBHOS always runs the delete. SAVED_MESSAGE_DELETE ({"user_id": "...", "message_id": "..."}) is sent to all of the caller's sessions on every accepted request, including a request for a message the account never saved. The message stays in its channel.
Rate limit¶
30 requests per 10 seconds per user, on the user:saved_messages:write bucket. This bucket is shared with Save message.
Channel-scoped save routes
POST /channels/{channel_id}/messages/{message_id}/save and DELETE /channels/{channel_id}/messages/{message_id}/save do the same work as the two routes above, with the same permission checks, collection limit and events. They take no body. The POST returns 201 {"ok": true} and the DELETE returns 200 {"ok": true}. These routes have no route-level rate limit.
Scheduled message object¶
A scheduled message is text that TEBHOS posts to a channel for the caller at a later time. A background worker checks for due messages every 30 seconds, so a message is posted up to about 30 seconds after scheduled_at.
Structure¶
| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the scheduled message |
| channel_id | snowflake | The target channel |
| content | string | The text that will be sent |
| file_url | ?string | The file URL given at creation. It is stored, but the posted message does not include it |
| scheduled_at | ISO8601 timestamp | When the message is due |
| created_at | ISO8601 timestamp | When the message was scheduled |
When a scheduled message is posted, it is removed from the list and a normal MESSAGE_CREATE is sent to the channel. If posting fails, the entry is marked invalid and is not retried. Invalid entries are hidden from every route below.
Schedule message¶
POST /scheduled-messages
Schedules a message and returns 201 with the scheduled message object. The caller needs SEND_MESSAGES in the target channel. The same operation is also available as POST /channels/{channel_id}/messages/schedule, which takes the channel from the path (see Messages).
JSON body¶
| Field | Type | Description |
|---|---|---|
| channel_id | snowflake | The target channel |
| content | string | The message text. Must not be blank, and at most 4000 bytes |
| scheduled_at | ISO8601 timestamp | When to send. Must be at least 1 minute and at most 30 days after the request |
| reply_to_id? | snowflake | A message to reply to. A value that cannot be parsed is ignored |
| file_url? | string | Stored with the entry, but not sent |
Limits¶
An account can have at most 50 pending scheduled messages. The 51st returns 400 BAD_REQUEST.
Response¶
| Status | Body | Condition |
|---|---|---|
| 201 | scheduled message object | The message was scheduled |
| 400 | error response | The channel ID, content or time is invalid, or the pending limit has been reached |
| 403 | error response | The caller lacks SEND_MESSAGES in the channel |
List scheduled messages¶
GET /users/@me/scheduled-messages
Also available as GET /scheduled-messages. Returns {"items": [scheduled message object]} with up to 100 pending entries. Entries that have already been sent or are marked invalid are not included. The order of the entries is not defined.
Get scheduled message¶
GET /users/@me/scheduled-messages/{id}
Also available as GET /scheduled-messages/{id}. Returns one pending scheduled message. A message that belongs to another account, has already been sent or has been marked invalid returns 404 NOT_FOUND.
Edit scheduled message¶
PATCH /scheduled-messages/{id}
Changes the text or time of a pending scheduled message and returns 204 with an empty body.
| Field | Type | Description |
|---|---|---|
| content? | string | New text. Must not be blank, and at most 4000 bytes |
| scheduled_at? | ISO8601 timestamp | New time, with the same 1 minute to 30 day window as when scheduling |
Returns 404 NOT_FOUND when the scheduled message does not exist or belongs to another account.
Cancel scheduled message¶
DELETE /users/@me/scheduled-messages/{id}
Also available as DELETE /scheduled-messages/{id}. Cancels a pending scheduled message and returns 204 with an empty body. Returns 404 NOT_FOUND when the message does not exist, belongs to another account or has already been sent.
The scheduled-message routes have no route-level rate limit.
Delete current user's messages¶
POST /users/@me/messages/bulk-delete-mine
Deletes the messages the caller wrote in the channel types chosen by the body. Requires sudo mode and returns 202 with an empty body.
Deletion is permanent
Every selected message is deleted from its channel for every recipient, and no operation can restore it. Get explicit confirmation from the user before you send this request.
Sudo mode¶
This route checks for a sudo grant held by the current session. You get one with POST /auth/sudo, which is valid for 5 minutes (see MFA). Sudo fields in the request body are ignored. Without a valid grant, the route returns 403 with code SUDO_MODE_REQUIRED.
JSON body¶
Every field is optional.
| Field | Type | Description |
|---|---|---|
| include_dms? | boolean | Whether one-to-one DMs are included. This covers every channel outside a guild that is not a group DM. Default true |
| include_group_dms? | boolean | Whether group DMs are included. Default true |
| include_guilds? | boolean | Whether guild channels are included. Default true |
| start_date? | ISO8601 timestamp | Inclusive lower bound on message time. A value that cannot be parsed is ignored |
| end_date? | ISO8601 timestamp | Inclusive upper bound on message time. A value that cannot be parsed is ignored |
Message time comes from the timestamp in the message snowflake, so editing a message does not change which window it falls in.
Accepted but ignored on TEBHOS
The body can also contain scope, include_dms_closed, guild_filter_mode, excluded_guild_ids and included_guild_ids. TEBHOS parses these fields but does not apply them: there is no guild include/exclude filter and no "inaccessible only" mode. TEBHOS does not check the date order, and it does not reject a body with every toggle set to false (such a request deletes nothing).
Behaviour¶
- The deletion runs while the request is open. By the time 202 is returned, the messages have been deleted.
- Only the caller's 10,000 most recent messages are considered. To delete older messages, send the request again.
- For each affected channel, TEBHOS sends one
MESSAGE_DELETE_BULKevent ({"channel_id": "...", "ids": [...]}) to that channel. - No summary direct message is sent when the deletion finishes.
Response¶
| Status | Body | Condition |
|---|---|---|
| 202 | empty | The selected messages were deleted |
| 403 | error response | No valid sudo grant for this session (SUDO_MODE_REQUIRED) |
| 500 | error response | Messages could not be read or deleted. Some messages may already have been deleted |
Rate limit¶
5 requests per 30 minutes per user, on the user:messages:bulk_delete_mine_filtered bucket. The limit is checked before sudo.
Delete current user's messages in a guild¶
POST /users/@me/guilds/{guild_id}/messages/bulk-delete-mine
Deletes every message the caller wrote in any channel of one guild. The caller must be a member of the guild; otherwise the route returns 404 NOT_FOUND. The route requires the same session sudo grant as Delete current user's messages. It takes no body and returns 202 once the deletion has finished. It considers the caller's 10,000 most recent messages and sends one MESSAGE_DELETE_BULK event per channel.
Rate limit: 10 requests per 10 seconds per user and guild, on the guild:leave bucket.
Request bulk message deletion¶
POST /profile/purge-messages
Schedules deletion of every message the caller has sent, starting 24 hours from now. Returns 200 with the schedule. Unlike Delete current user's messages, this route takes no filter and waits a day. Until then, the deletion can be cancelled.
Sending the request again replaces the pending schedule with a new one that starts 24 hours from the new request. An account can have only one pending deletion. The current schedule is shown as pending_bulk_message_deletion on the user object and by Get bulk message deletion status.
Deletion is permanent once it runs
When the schedule runs, it deletes every message the caller wrote at or before the scheduled time, in every channel. Cancel bulk message deletion is the only way to stop it.
JSON body (sudo verification)¶
Unlike the route above, this route checks sudo inline in the request body and ignores session sudo grants. Send exactly one proof:
| Field | Type | Description |
|---|---|---|
| password? | string | The account password. Accepted only when the account has no MFA |
| mfa_code? | string | An authenticator (TOTP) code or an unused backup code |
| sudo_proof? | string | A single-use proof from a passkey assertion (see MFA) |
If more than one field is present, sudo_proof is checked first, then mfa_code, then password.
Response¶
| Status | Body | Condition |
|---|---|---|
| 200 | {"scheduled_at", "message_count", "channel_count"} |
The deletion was scheduled |
| 401 | error response | The password, code or passkey proof is wrong (UNAUTHORIZED) |
| 403 | error response | No proof was given, or only a password was given for an MFA account (SUDO_MODE_REQUIRED) |
message_count and channel_count are counted when the request is made and are not updated while the schedule waits.
Side effects¶
The request itself does not delete anything and sends no Gateway event. A background task runs every 10 minutes. After scheduled_at has passed, it deletes the messages, clears the schedule and removes the pending deletion from the account. These deletions send no MESSAGE_DELETE events, and nothing tells the client that the deletion has finished. Poll Get bulk message deletion status if you need to know.
Rate limit¶
3 requests per hour per user, on the profile:purge bucket. This limit is checked only after sudo verification succeeds. Wrong proofs are limited separately: 10 attempts per 15 minutes for mfa_code and 10 attempts per 5 minutes for password, per user.
Get bulk message deletion status¶
GET /profile/purge-status
Returns the pending bulk deletion for the caller. When nothing is pending, every field is null.
| Field | Type | Description |
|---|---|---|
| scheduled_at | ?ISO8601 timestamp | When the deletion runs |
| message_count | ?integer | Messages counted when the deletion was scheduled |
| channel_count | ?integer | Channels counted when the deletion was scheduled |
Cancel bulk message deletion¶
DELETE /profile/purge-messages
Cancels a pending bulk message deletion and returns 200 {"message": "..."}. This route needs no sudo verification. It returns 200 even when nothing is pending. It sends no Gateway event. After the scheduled deletion has started, there is nothing left to cancel, and deleted messages cannot be restored.