Calls¶
A call is the live voice session of a direct message or a group direct message. Channels defines the channel itself and its recipient set.
Joining and leaving a call is a Gateway operation. A client joins or leaves by sending Voice State Update with a null guild_id, and Call Create, Call Update, and Call Delete publish the resulting call state. The Gateway owns the call's ringing timers and participant roster. Voice describes the model.
Every route on this page is user-only. A bot token is rejected with 403 ACCESS_DENIED.
Access rules¶
Every route requires a direct message or group direct message whose recipients include the caller.
| Condition | Response |
|---|---|
| The channel does not exist | 404 NOT_FOUND |
| The channel exists but is not a direct message or group direct message | 403 FORBIDDEN |
| The caller is not a recipient of the channel | 403 FORBIDDEN |
Voice moderation in a private call¶
A private call has no moderator and no permission overwrites. No account other than the participant itself can change that participant's voice state, and the owner of a group direct message is no exception. The mute, deaf, and suppress fields of a call voice state are therefore always false. Only the participant's own Voice State Update changes self_mute, self_deaf, self_video, or self_stream.
Silencing another participant happens in the client. A client MAY mute a participant or change per-participant volume, and MUST keep each setting local to the listening device.
Call eligibility object¶
TEBHOS computes eligibility for one caller against one private channel at the moment of the read.
Structure¶
| Field | Type | Description |
|---|---|---|
| ringable | boolean | Whether the authenticated user can ring the other side of this channel |
| silent | boolean | Whether a newly started call notifies the other recipient without audible ringing. Only meaningful for a direct message, and false whenever ringable is false |
| reason? | string | A human-readable explanation, present when ringable is false. For display only; do not parse it |
Example¶
Get call eligibility¶
GET /channels/{channel_id}/call
Returns the call eligibility object for a direct message or group direct message. The caller must satisfy the access rules.
Path parameters¶
| Field | Type | Description |
|---|---|---|
| channel_id | snowflake | The ID of the direct message or group direct message channel |
ringable is false when the caller is already connected to the channel's call, as reported by the Gateway. If the Gateway cannot be reached, that check is skipped.
In a group direct message, ringable is otherwise true and silent is false.
In a direct message, the other recipient's incoming call policy decides. A block in either direction is never ringable. Otherwise TEBHOS reads the recipient's incoming call flags from their user settings in this order: the nobody flag rejects every caller; the friends-only flag admits only a friend; a friend is always admitted under any other policy; a caller who shares a friend with the recipient is admitted when the friends-of-friends flag is set; a caller who shares a guild is admitted when the guild-members flag is set; the everyone flag admits every remaining caller; any other caller is rejected. The silent-everyone flag reports silent as true for a caller admitted by the friends-of-friends, guild-members, or everyone branch. When the settings cannot be read, the caller is treated as ringable.
Ring call recipients evaluates the same policy again.
Response¶
| Status | Body | Condition |
|---|---|---|
| 200 | call eligibility object | Eligibility was returned |
| 403 | error response | The channel is not a private channel, or the caller is not a recipient |
| 404 | error response | The channel does not exist |
Rate limit¶
60 requests per 10 seconds for each user and channel, on the channel:call:get bucket.
Ring call recipients¶
POST /channels/{channel_id}/call/ring
Starts a call in a direct message or group direct message, or rings more recipients of an existing call. The caller must satisfy the access rules. The caller does not have to be connected to the call yet.
Path parameters¶
| Field | Type | Description |
|---|---|---|
| channel_id | snowflake | The ID of the direct message or group direct message channel |
JSON body¶
| Field | Type | Description |
|---|---|---|
| recipients? | array[snowflake] | The IDs of the recipients to ring. Omitting the field targets every other recipient. An empty array creates the call and rings nobody |
Each entry is a snowflake string or number. An ID that is not a current recipient other than the caller returns 400 INVALID_FORM_BODY with the validation code USER_NOT_IN_CHANNEL at the recipients path.
A targeted recipient is rung only when the incoming call policy described by Get call eligibility admits the caller audibly. A recipient the policy excludes, and a recipient it admits only silently, are left out of the ringing set while the request still succeeds.
Response body¶
| Field | Type | Description |
|---|---|---|
| ringing | array[snowflake] | The recipients this request rang |
Response¶
| Status | Body | Condition |
|---|---|---|
| 200 | response body | The ring operation completed |
| 400 | error response | A named ID is not a recipient and the request returns INVALID_FORM_BODY with USER_NOT_IN_CHANNEL |
| 403 | error response | The channel is not a private channel, or the caller is not a recipient |
| 404 | error response | The channel does not exist |
Side effects¶
The private channel reopens for the caller and every targeted recipient who had closed it, and those accounts receive Channel Create.
When no call is active, TEBHOS creates one: it writes a call system message into the channel (delivered as Message Create), sends the API form of Call Create to every recipient, and starts the Gateway call process, which sends its own Call Create.
Each rung recipient receives a CALL_RING Dispatch with channel_id, call_id, and the initiator (id, name, image_url), and the Gateway call process adds the recipients to its ringing set and sends Call Update.
Ringing lasts 30 seconds per recipient, and its expiry emits Call Update. A call with no connected participant and nobody ringing ends on the Gateway's 120-second idle timer with Call Delete. When a call ends, the call message is updated with its end time and participants.
Rate limit¶
5 requests per 10 seconds for each user and channel, on the channel:call:ring bucket.
Stop ringing call recipients¶
POST /channels/{channel_id}/call/stop-ringing
Removes recipients from the ringing set of the active call. The caller must satisfy the access rules.
Path parameters¶
| Field | Type | Description |
|---|---|---|
| channel_id | snowflake | The ID of the direct message or group direct message channel |
JSON body¶
| Field | Type | Description |
|---|---|---|
| recipients? | array[snowflake] | The IDs of the recipients to stop ringing. Omitting the field targets the caller alone, which is how a client declines its own incoming call |
Response body¶
| Field | Type | Description |
|---|---|---|
| stopped | array[snowflake] | The recipients the request targeted |
Response¶
| Status | Body | Condition |
|---|---|---|
| 200 | response body | The stop-ringing operation completed |
| 403 | error response | The channel is not a private channel, or the caller is not a recipient |
| 404 | error response | The channel does not exist, or no call is active in it |
Side effects¶
Each targeted recipient receives a CALL_STOP_RING Dispatch with channel_id, call_id, stopped_by, and recipients, so the recipient's other devices stop ringing too. The Gateway call process removes them from its ringing set and sends Call Update.
When that leaves the call with no participant and nobody ringing, the call ends: every recipient receives Call Delete (including an API form with reason: "empty") and the call message is marked ended. Otherwise the call continues, so a caller can keep waiting alone.
Rate limit¶
20 requests per 10 seconds for each user and channel, on the channel:call:stop_ringing bucket.
Modify call region¶
PATCH /channels/{channel_id}/call
Changes the voice region of the active call. The caller must satisfy the access rules and does not have to be connected to the call.
Path parameters¶
| Field | Type | Description |
|---|---|---|
| channel_id | snowflake | The ID of the direct message or group direct message channel |
JSON body¶
| Field | Type | Description |
|---|---|---|
| region? | ?string | The ID of the RTC region to select. An empty string, automatic, and auto select automatic routing. Omitting the field or sending null changes nothing |
A region the caller cannot use returns 400 INVALID_OR_RESTRICTED_RTC_REGION.
Response body¶
| Field | Type | Description |
|---|---|---|
| id | string | The API's call ID |
| channel_id | snowflake | The channel |
| initiator_id | snowflake | The user who started the call |
| livekit_room_name | string | The media room, dm_{channel_id} |
| started_at | ISO8601 timestamp | When the call started |
| region? | string | The selected region, automatic for automatic routing |
| voice_states | array[object] | Always empty; the Gateway holds the live roster |
| ringing | array[snowflake] | Recipients the API recorded as ringing |
Response¶
| Status | Body | Condition |
|---|---|---|
| 200 | response body | The region was updated, or no change was requested |
| 400 | error response | The region is unknown or restricted: INVALID_OR_RESTRICTED_RTC_REGION |
| 403 | error response | The channel is not a private channel, or the caller is not a recipient |
| 404 | error response | The channel does not exist, or no call is active in it |
Side effects¶
Supplying region records the new region, releases the call's pinned media server, and hands the region to the Gateway call process, which issues each connected participant a fresh Voice Server Update for the new region. Every recipient receives the API form of Call Update.
Rate limit¶
10 requests per 10 seconds for each user and channel, on the channel:call:update bucket, shared with End call.
End call¶
POST /channels/{channel_id}/call/end
DELETE /channels/{channel_id}/call
Both routes run the same operation and return 204 with an empty body. They mark the active call as ended in the API. The caller must satisfy the access rules and must be the user who started the call.
Path parameters¶
| Field | Type | Description |
|---|---|---|
| channel_id | snowflake | The ID of the direct message or group direct message channel |
Response¶
| Status | Body | Condition |
|---|---|---|
| 204 | empty | The call was ended |
| 403 | error response | The channel is not a private channel, the caller is not a recipient, or the caller did not start the call |
| 404 | error response | The channel does not exist, or no call is active in it |
Side effects¶
The call record and its ringing set are cleared, the call message is marked ended, and every recipient receives the API form of Call Delete with channel_id, call_id, and ended_by.
Leaving is a Gateway operation
Ending the call over HTTP does not disconnect anyone's voice connection. A participant leaves by sending Voice State Update with a null channel and the connection_id to drop. The Gateway ends the call on its own when the last participant leaves and nobody is ringing.
Rate limit¶
10 requests per 10 seconds for each user and channel, on the channel:call:update bucket.
Start call (legacy)¶
POST /channels/{channel_id}/call
Starts a call and returns a media credential directly, without the Gateway. It is kept for older TEBHOS clients. New clients join with Voice State Update and start ringing with Ring call recipients.
The caller must satisfy the access rules. A direct message recipient whose privacy settings do not accept calls from the caller returns 403 MISSING_ACCESS. When a call is already active, the route returns that call with a fresh credential.
Response body¶
| Field | Type | Description |
|---|---|---|
| call_id | string | The API's call ID |
| channel_id | snowflake | The channel |
| livekit_room_name | string | The media room, dm_{channel_id} |
| livekit_url | string | The LiveKit server URL |
| access_token | string | A LiveKit access token for the caller |
| started_at | ISO8601 timestamp | When the call started |
| message_id? | snowflake | The call system message |
| unavailable? | boolean | Present and true when every other recipient is currently unavailable |
| unavailable_recipients? | array[snowflake] | Recipients that are currently unavailable |
A new call writes the call system message and sends the API form of Call Create to every recipient. This route rings nobody.