Voice¶
TEBHOS runs voice over LiveKit. The Gateway places a session into a voice channel and hands it one credential. The client presents that credential to a LiveKit media server, and every track goes over the connection it opens there. Microphone, camera, and screen share are track sources on that one connection, so going live opens nothing new.
Client commands and Gateway events define the placement protocol.
Voice surfaces¶
| Surface | What it is | Reference |
|---|---|---|
| Guild voice channel | A guild channel of the voice type, which also has messages, pins, and slowmode | Channels |
| Private call | The direct message and group direct message counterpart of a voice channel | Calls |
| Go Live stream | A screen share published as an extra track on a voice connection the member already holds. It opens no second connection and issues no second credential | Streams |
| Entrance sound | A short clip announced to everyone already connected to a voice channel. The one surface that publishes no media track | Entrance sounds |
| Voice activity sharing | Whether a friend is told which voice channel the account is in | User settings |
TEBHOS also runs public live rooms, a separate first-party feature built on LiveKit. They are not covered by this reference.
Media transport¶
Media never crosses the HTTP API. No route returns an audio or video track, and the placement credential is delivered only by the Gateway.
TEBHOS publishes no signalling protocol of its own. There is no voice WebSocket, no voice opcode set, no UDP discovery step, and no separate encryption handshake. A client connects to the endpoint that the Voice Server Update grant names, presents the grant token, and speaks the LiveKit protocol from there.
| Concept | Value |
|---|---|
| Room name, guild voice channel | {channel_id} |
| Room name, private call | dm_{channel_id} |
| Participant identity | user_{user_id}_{connection_id} |
| Grant lifetime | 6 hours |
One room is one voice channel, and one participant is one voice connection. A member holding several connections in the same channel is several participants in the same room, which is how one account is present from more than one device.
The grant names the track sources the connection may publish. SPEAK allows the microphone source, and STREAM allows the camera, screen_share, and screen_share_audio sources. A server-deafened connection may neither publish nor subscribe. In a private call every participant may publish every source.
Deployment feature state¶
A deployment can be configured without voice. The instance discovery document reports that state as features.voice_enabled, which is true when a LiveKit server is configured. Where it is false, no media credential can be issued and every placement fails without a grant.
Placement¶
Voice State Update declares where a session wants to be. It carries the target guild_id, channel_id, and connection_id together with self_mute, self_deaf, self_video, and self_stream. A null channel_id leaves, and a call placement always has a null guild_id.
| Command shape | Result |
|---|---|
channel_id and no connection_id |
Opens a new connection |
channel_id and a connection_id |
Updates or moves that connection |
Null channel_id and a connection_id, in a guild |
Drops that connection |
Null channel_id and no connection_id, in a guild |
Refused with VOICE_MISSING_CONNECTION_ID |
Null channel_id and a connection_id, in a call |
Drops that call connection |
Null guild_id, null channel_id, and no connection_id |
Drops every voice membership the session holds |
The server answers with Voice State Update for the resulting membership, delivered to every session that can view the channel, and with Voice Server Update for the requesting session alone. That grant carries the token, endpoint, channel_id, and connection_id of the media server the deployment selected. guild_id is present for a guild voice channel and omitted for a call. It also carries an e2ee_key when the channel is end-to-end encrypted.
A grant is issued when a connection opens, when it moves to another channel, and when the region changes. An update that stays in the same channel reissues nothing, so toggling self_mute, self_video, or self_stream produces one Voice State Update and no new grant.
The grant token is consumed by the media connection alone. No HTTP route accepts it.
A refused placement produces no Dispatch. A client observes it only as the absence of a grant.
Minimal client flow¶
import { Room } from "livekit-client";
// 1. Ask the Gateway to place this session in a voice channel.
gateway.send(JSON.stringify({
op: 4,
d: {
guild_id: "1189375284394692608",
channel_id: "1189375284394692610",
self_mute: false,
self_deaf: false,
},
}));
// 2. Wait for the grant and connect to LiveKit with it.
gateway.on("VOICE_SERVER_UPDATE", async (grant) => {
const room = new Room();
await room.connect(grant.endpoint, grant.token);
await room.localParticipant.setMicrophoneEnabled(true);
});
// 3. Leave: null channel with the connection_id from the grant.
function leave(guildId, connectionId) {
gateway.send(JSON.stringify({
op: 4,
d: { guild_id: guildId, channel_id: null, connection_id: connectionId },
}));
}
Guild voice channels¶
A guild voice channel stores its bitrate, user_limit, voice_connection_limit, and rtc_region on the channel object. It also has ordinary messages, pins, and slowmode, read and written through the Messages resource.
A new voice channel stores a bitrate of 64000 bits per second (64 kbps); the bitrate is always stored and returned in bits per second. The accepted range starts at 8 kbps. The ceiling is 96 kbps, 128 kbps for a member with TEBHOS Pro, and 128 kbps or 384 kbps in a guild at boost level 1 or 2; the higher of the Pro and boost ceilings applies. See Channels for the exact bitrate rules.
Permissions¶
| Permission | Effect on voice |
|---|---|
| VIEW_CHANNEL and CONNECT | Both are required to hold a voice connection in the channel. A member missing either is refused with VOICE_PERMISSION_DENIED |
| SPEAK | Publish audio. A member without it is admitted with suppress true |
| STREAM | Publish camera video and Go Live media. One bit gates both |
| MUTE_MEMBERS | Apply and clear a moderator mute or deafen |
| MOVE_MEMBERS | Move a member to another guild voice channel, or disconnect it |
| UPDATE_RTC_REGION | Change the channel's rtc_region |
ADMINISTRATOR satisfies every row of that table. Two cases skip the VIEW_CHANNEL and CONNECT check: a member the guild is already moving, and a member holding virtual access to the channel. The guild grants virtual access to a connected member that loses VIEW_CHANNEL or that a moderator moves into a channel it cannot see.
The Gateway checks these permissions when it issues a grant, and the guild checks them again for connections that are already open. Joining, moving, a region change, a role edit, an overwrite edit, and a member role change each recompute SPEAK and STREAM. The guild applies the new result to the live connection without a new Voice Server Update: the media server mutes a track the member may no longer publish and drops a connection that fails the VIEW_CHANNEL and CONNECT check.
Moderation is an HTTP operation on the guild membership. Modify guild member accepts mute, deaf, channel_id, and connection_id:
| Field | Effect | Required permission |
|---|---|---|
| mute | Moderator mute on the member's voice connections | MUTE_MEMBERS |
| deaf | Moderator deafen on the member's voice connections | MUTE_MEMBERS |
| channel_id | Move to another guild voice channel, or null to disconnect |
MOVE_MEMBERS |
| connection_id | Limits a move or disconnect to one of the member's connections | None |
Acting on another member also requires a higher role than theirs, and the guild owner cannot be moderated. A mute or deafen reaches the media server without a new credential.
Capacity¶
| Bound | Refusal |
|---|---|
user_limit |
VOICE_CHANNEL_FULL |
voice_connection_limit |
VOICE_CONNECTION_LIMIT_REACHED |
| 25 members with a camera on | VOICE_CAMERA_USER_LIMIT |
A stored user_limit of 0 means no limit. While any member in the channel has a camera on, the effective occupancy limit becomes the lower of user_limit and 25.
voice_connection_limit is the ceiling on simultaneous connections one member may hold in the channel. A channel that stores no value is evaluated at 5, and values are limited to 1 through 100. A private call applies a fixed ceiling of 5 connections for each member.
A member whose timeout is still running is refused with VOICE_MEMBER_TIMED_OUT before any permission or capacity check. A session that did not identify with e2ee_capable is refused with VOICE_E2EE_REQUIRED while the channel requires end-to-end encryption. A bot is exempt from that one.
Regions¶
List RTC regions returns the RTC region objects the caller may select for a voice channel. A region is returned only when the caller passes every restriction configured for it. The array can be empty.
rtc_region is written by Modify channel and requires UPDATE_RTC_REGION. A null value, an empty string, and the values automatic and auto select automatic routing, and so does a stored value the placing account cannot reach.
Automatic routing selects an available server, using the client's latitude and longitude when possible. Participants in the same channel share a server. Always use the endpoint returned in Voice Server Update.
Private calls¶
A private call has no moderator, no permission overwrites, and no moderator mute, deafen, or disconnect. Joining and leaving use the same Voice State Update with a null guild_id.
The Calls resource owns the HTTP surface: it reads whether the caller may ring, rings recipients, stops ringing them, changes the region of an active call, and ends a call. Call Create, Call Update, and Call Delete publish the call state.
A recipient's incoming call settings decide whether it is rung. They can admit nobody, friends only, friends of friends, guild members, or everyone, and they can admit everyone silently. A blocked relationship in either direction never rings.
Go Live streams¶
Going live publishes a screen share track, and its screen share audio track, on the LiveKit participant the member already holds in the channel. There is no second connection, no second participant, and no second connection_id. A member that held STREAM at placement already has both screen share sources in its grant, so no new credential is issued.
The publisher advertises the stream by setting self_stream on that connection with Voice State Update, naming the connection's own connection_id. The server increments the voice state version and rebroadcasts the state.
The voice state of a connection without STREAM in its channel has self_stream and self_video false, whatever the client sent.
A viewer declares which streams it watches with viewer_stream_keys on its own voice state, and a channel move resets that list.
The publisher's connection_id is the last segment of the stream key, which is {guild_id}:{channel_id}:{connection_id} for a guild voice channel and dm:{channel_id}:{connection_id} for a private call. The Streams resource records a region preference and manages the JPEG preview image addressed by that key.
Entrance sounds¶
An entrance sound is a short clip an account plays for everyone already connected to a voice channel. An account keeps a library of at most eight clips, each 100 through 5,200 milliseconds and at most 1,048,576 decoded bytes, stored as mp3, ogg, m4a, or wav, and assigns clips to scopes. Assigning and playing clips require TEBHOS Pro.
Play entrance sound names the clip explicitly, so the client decides which selection applies to a given channel. Playback requires a voice connection in the target channel and no channel permission. A successful call sends one ENTRANCE_SOUND_PLAY Dispatch to every other account with a voice state in the channel, and each recipient fetches the clip from its URL and plays it locally.
Voice activity sharing¶
An account chooses whether friends are told which voice channel it is in. PUT /users/@me/settings/voice-activity-sharing with {"share_voice_activity": true | false} writes the account's default and rewrites the caller's side of every existing friendship to the same value, then emits Relationship Update to both sides of each rewritten friendship. A second change within 24 hours is refused with 429 and a retry_after.
The stored result is share_voice_activity on the caller's own relationship object.
The Gateway does not enforce voice activity sharing
A voice state reaches every session that can view the channel whatever share_voice_activity holds. A client MUST NOT present the flag as concealment.