Entrance sounds¶
An entrance sound is a short audio clip an account plays while it is connected to a voice channel. An account uploads a clip once into its own library, assigns it to a scope, and plays it for the other participants of a voice channel. Voice describes where the feature sits.
Every route on this page is user-only, requires no channel permission, and acts on the caller's own library. A bot token is rejected with 403 ACCESS_DENIED.
TEBHOS Pro
Set entrance sound selection and Play entrance sound require TEBHOS Pro and return 403 FORBIDDEN without it. Listing, uploading, renaming, and deleting clips stay available, so an account whose Pro ended can still manage its library. The feature_voice_entrance_sounds limit key in instance discovery tells the TEBHOS clients whether to show the feature.
Clip limits¶
| Bound | Value |
|---|---|
| Clips per account | 8 |
| Decoded clip size | 1,048,576 bytes (1 MiB) |
| Clip duration | 100 through 5,200 milliseconds |
| Display name | 1 through 32 characters after trimming |
Supported containers¶
TEBHOS detects the container from the decoded bytes, so a filename, a data: media type, or any other declared type has no effect. The duration is read from the container itself, and a file whose duration cannot be read is rejected.
| Detected container | Stored extension | Stored content type |
|---|---|---|
| MP3 (with or without an ID3 tag) | mp3 |
audio/mpeg |
| Ogg | ogg |
audio/ogg |
MP4 or M4A (ftyp box) with an audio track |
m4a |
audio/mp4 |
| WAV (RIFF/WAVE) | wav |
audio/wav |
Every other format, including FLAC, WebM, Matroska, and AIFF, is rejected.
The decoded bytes are stored exactly as they arrived, under the extension and content type the table assigns.
Entrance sound object¶
One stored audio clip owned by one account. Every field except name is fixed at upload.
Structure¶
| Field | Type | Description |
|---|---|---|
| id | snowflake | The ID of the sound |
| name | string | The display label (1 through 32 characters) |
| hash | string | A content digest of the stored audio, 16 hexadecimal characters. Clips with identical audio have the same hash |
| extension | string | The stored extension: mp3, ogg, m4a, or wav |
| content_type | string | The MIME type the clip is stored and served with |
| duration_ms | integer | The measured duration in milliseconds |
| size_bytes | integer | The decoded size in bytes |
| url | string | The absolute URL the clip is fetched from, https://cdn.tebhos.com/entrance-sounds/{user_id}/{hash}.{extension} on the public instance. It needs no credential |
| created_at | ISO8601 timestamp | The time the clip was uploaded |
Example¶
{
"id": "1501314428688998182",
"name": "airhorn",
"hash": "9f2c41ab77de0135",
"extension": "mp3",
"content_type": "audio/mpeg",
"duration_ms": 1240,
"size_bytes": 20416,
"url": "https://cdn.tebhos.com/entrance-sounds/1490000000000000000/9f2c41ab77de0135.mp3",
"created_at": "2026-09-20T09:12:04.118+00:00"
}
Entrance sound selection object¶
One scope and the clip assigned to it.
Structure¶
| Field | Type | Description |
|---|---|---|
| scope_id | string | The scope the selection applies to |
| sound_id | snowflake | The clip assigned to that scope |
A scope with no assigned sound has no selection object.
Example¶
Entrance sound scopes¶
The TEBHOS clients use these scope values:
| Value | Description |
|---|---|
global |
Applies wherever no narrower scope is selected |
guilds |
Applies in guild voice channels |
dms |
Applies in direct message and group calls |
guild:{guild_id} |
Applies in one named guild |
TEBHOS stores scope_id as sent and does not validate its format, so use one of these values. Scopes are only a client-side convention: Play entrance sound names the clip explicitly, and the client decides which selection applies to a channel.
List entrance sounds¶
GET /users/@me/entrance-sounds
Returns the current account's complete entrance sound library together with its per-scope selections.
Response body¶
| Field | Type | Description |
|---|---|---|
| sounds | array[entrance sound object] | Every clip the account owns, at most 8 |
| selections | array[entrance sound selection object] | Every scope with a sound assigned |
Both members are always present, and both arrays are empty for an account that has uploaded nothing.
Rate limit¶
30 requests per minute for each user, on the user:entrance_sound:list bucket.
Upload entrance sound¶
POST /users/@me/entrance-sounds
Decodes, validates, and stores one audio clip in the current account's library, and returns 201 with the created entrance sound object.
JSON body¶
| Field | Type | Description |
|---|---|---|
| name | string | The display label, trimmed, 1 through 32 characters |
| audio | base64 string | The audio bytes, base64 encoded. A data:...;base64, prefix is accepted and discarded |
Validation¶
Every failure below returns 400 INVALID_FORM_BODY with a human-readable message. The checks run in this order.
| Failure | Condition |
|---|---|
| Name | The trimmed name is empty or longer than 32 characters |
| Quota | The library already holds 8 clips |
| Encoding | audio is not valid base64, or decodes to nothing |
| Size | The decoded bytes exceed 1,048,576 bytes |
| Format | The bytes match no supported container, or their duration cannot be read |
| Duration | The duration is above 5,200 ms or below 100 ms |
A request body larger than the upload limit (about 1.4 MiB of base64 plus 64 KiB) is rejected before validation.
Response¶
| Status | Body | Condition |
|---|---|---|
| 201 | entrance sound object | The clip was stored |
| 400 | error response | The name or audio fails validation |
Side effects¶
The operation assigns no scope and emits no Gateway Dispatch. Uploading the same bytes twice creates two library entries with the same hash and url. Deleting one leaves the other usable.
Rate limit¶
5 requests per 5 minutes for each user, on the user:entrance_sound:upload bucket.
Rename entrance sound¶
PATCH /users/@me/entrance-sounds/{sound_id}
Changes the display label of one clip and returns the updated entrance sound object.
Path parameters¶
| Field | Type | Description |
|---|---|---|
| sound_id | snowflake | The ID of the sound |
JSON body¶
| Field | Type | Description |
|---|---|---|
| name | string | The replacement label, trimmed, 1 through 32 characters |
Response¶
| Status | Body | Condition |
|---|---|---|
| 200 | entrance sound object | The clip was renamed |
| 400 | error response | sound_id is not a number, or the name is empty or longer than 32 characters (BAD_REQUEST) |
| 404 | error response | The account owns no such clip (NOT_FOUND) |
A clip owned by another account is reported exactly like one that never existed.
Side effects¶
Only the label changes. Selections pointing at the clip keep pointing at it. No Gateway Dispatch is emitted.
Rate limit¶
20 requests per minute for each user, on the user:entrance_sound:mutate bucket, shared with Delete entrance sound and Set entrance sound selection.
Delete entrance sound¶
DELETE /users/@me/entrance-sounds/{sound_id}
Deletes one clip from the current account's library, clears every selection that pointed at it, and returns 204 with an empty body. An unknown clip, or a clip owned by another account, is a successful no-op.
Path parameters¶
| Field | Type | Description |
|---|---|---|
| sound_id | snowflake | The ID of the sound |
Response¶
| Status | Body | Condition |
|---|---|---|
| 204 | empty | The clip was deleted, or no owned clip existed |
| 400 | error response | sound_id is not a number |
Side effects¶
The clip and every selection naming it are removed. The stored audio file is removed too, unless another clip in the library has the same content. No Gateway Dispatch is emitted.
Rate limit¶
20 requests per minute for each user, on the shared user:entrance_sound:mutate bucket.
Set entrance sound selection¶
PUT /users/@me/entrance-sound-selections
Assigns a clip to one scope, or clears that scope, and returns 204 with an empty body. A scope that already has a sound is overwritten. Requires TEBHOS Pro.
JSON body¶
| Field | Type | Description |
|---|---|---|
| scope_id | string | The scope to write |
| sound_id? | ?snowflake | The clip to assign. Null, an omitted field, or a value that is not a number clears the scope |
TEBHOS does not check that sound_id names a clip in the caller's library. A client SHOULD send only IDs returned by List entrance sounds.
Response¶
| Status | Body | Condition |
|---|---|---|
| 204 | empty | The selection was written or cleared |
| 403 | error response | The account does not have TEBHOS Pro |
Side effects¶
One clip can be selected in any number of scopes. No Gateway Dispatch is emitted, so the caller's other sessions learn about the change only by listing the library again.
Rate limit¶
20 requests per minute for each user, on the shared user:entrance_sound:mutate bucket.
Play entrance sound¶
POST /voice/channels/{channel_id}/entrance-sound
Tells every other account connected to a voice channel to play the caller's clip, and returns 204 with an empty body. The caller must hold a voice connection in that channel and must own the clip. Requires TEBHOS Pro. Emits ENTRANCE_SOUND_PLAY.
Recipients fetch and play the clip locally from the URL in the event.
Path parameters¶
| Field | Type | Description |
|---|---|---|
| channel_id | snowflake | The ID of the voice channel or private call channel |
JSON body¶
| Field | Type | Description |
|---|---|---|
| sound_id | snowflake | The clip to play, from the caller's own library |
Response¶
| Status | Body | Condition |
|---|---|---|
| 204 | empty | TEBHOS attempted a Dispatch to every other connected account |
| 400 | error response | channel_id or sound_id is not a number, the caller holds no voice connection in the channel, or the account owns no such clip (BAD_REQUEST) |
| 403 | error response | The account does not have TEBHOS Pro |
| 404 | error response | The channel does not exist |
Connection is the only authorisation
TEBHOS evaluates no channel permission and checks no guild membership. The connection check runs before the clip is looked up.
Side effects¶
TEBHOS sends one ENTRANCE_SOUND_PLAY Dispatch to every other account holding a voice state in the channel, at most once for each account, and never to the caller. The Dispatch is addressed to the account, so every session that account holds receives it, including sessions that are not in the channel. A client filters on channel_id.
Rate limit¶
3 requests per 30 seconds for each user and channel, on the voice:entrance_sound:play bucket.