Skip to content
Fluxer API

Admin voice

A voice region is a group of LiveKit servers, and Fluxer places each new voice session in a region and then on a server inside it. Each API node reloads the configuration shortly after a write, and a write never moves or disconnects a session that is already connected.

Regions and servers have the same eligibility fields, and a session can use a server only when it is active and both the server and its region admit the session.

FieldRule
allowed_user_idsWhen the list is not empty, only the accounts in it are admitted
allowed_guild_idsAdmits only these guilds, or lets them skip vip_only and required_guild_features when those are set
vip_onlyThe guild needs the VIP_VOICE guild feature
required_guild_featuresThe guild needs at least one of the listed features

When allowed_guild_ids, vip_only or required_guild_features is set, calls in direct messages and group direct messages can't use that region or server.

You pick the id when you create the region, and you can't change it later. A channel's rtc_region is this ID.

FieldTypeDescription
idstringThe ID of the region (1-64 characters)
namestringThe name shown for the region in a client (1-100 characters)
emojistringThe emoji shown for the region (1-64 characters)
latitudenumberThe latitude placement measures distance from
longitudenumberThe longitude placement measures distance from
is_default1booleanWhether automatic placement falls back to this region
vip_onlybooleanWhether the guild needs VIP_VOICE
required_guild_featuresarray[string]Guild features that admit a guild (max 100)
allowed_guild_idsarray[snowflake]The guilds admitted without other guild checks (max 1000)
allowed_user_idsarray[snowflake]The only accounts allowed to use the region, when not empty (max 1000)
created_at?ISO8601 timestampWhen the region was created
updated_at?ISO8601 timestampWhen the region last changed
servers?array[Admin voice server object]The servers in the region, when include_servers is true

1 To change the default, turn the flag off on the old default region too.

{
"id": "europe-north",
"name": "Northern Europe",
"emoji": "🇸🇪",
"latitude": 59.33,
"longitude": 18.06,
"is_default": true,
"vip_only": false,
"required_guild_features": [],
"allowed_guild_ids": [],
"allowed_user_ids": [],
"created_at": "2026-02-11T08:14:00.000Z",
"updated_at": "2026-08-02T17:45:31.000Z"
}

A voice server is one LiveKit deployment in one region, identified by its region_id and server_id. To move a server to another region, delete it and create it again. You send api_key and api_secret when you create or update a server, and no read returns them.

FieldTypeDescription
region_idstringThe ID of the region the server belongs to (1-64 characters)
server_idstringThe ID of the server, unique inside its region (1-64 characters)
endpointstringThe LiveKit ws:// or wss:// URL that clients connect to
latitude?numberThe latitude used for this server, or null to use the region's
longitude?numberThe longitude used for this server, or null to use the region's
is_activebooleanWhether new sessions can be placed on the server
soft_connection_limit?integerSee soft connection limits, or null for none (1-2147483647)
notes?stringAdmin notes about the server, or null for none
vip_onlybooleanWhether the guild needs VIP_VOICE
required_guild_featuresarray[string]Guild features that admit a guild (max 100)
allowed_guild_idsarray[snowflake]The guilds admitted without other guild checks (max 1000)
allowed_user_idsarray[snowflake]The only accounts allowed to use the server, when not empty (max 1000)
internal_endpoint?stringThe http:// or https:// URL the API uses for the LiveKit control API and health checks, or null to use endpoint
drainingbooleanWhether new calls go to other servers while calls already on this one keep going
public_ipv4?stringThe public IPv4 address LiveKit gives to clients, or null when none is known
public_ipv6?stringThe public IPv6 address LiveKit gives to clients, or null when none is known
healthhealth objectThe server's health as the API node that answered last saw it
duplicate_api_keybooleanWhether another server uses the same api_key
created_at?ISO8601 timestampWhen the server was created
updated_at?ISO8601 timestampWhen the server last changed
FieldTypeDescription
statusstringup, down, or unknown before the first check
checked_at?ISO8601 timestampWhen the last check ran, or null before the first
error?stringThe status code or short transport error of the last failed check

Every API node checks every active server every FLUXER_VOICE_HEALTH_INTERVAL_MS, 15 seconds by default, with GET on internal_endpoint or endpoint. Two failed checks in a row mark a server down, and one passing check marks it up. Placement sends no new calls to a server that is down, unless every server it could pick is down. A call pinned to a server that is down moves on its next join. A check of a public endpoint that resolves to a private address doesn't run, and the status stays unknown.

A server with draining set gets no new calls. Calls already on it keep going, and people who join those calls still land on it. rexctl sets it while it upgrades or removes a server. Unlike is_active false, draining never splits a call in two.

soft_connection_limit is the connection count at which placement starts to prefer the region's other servers, and it never refuses a session. The count comes from Get voice state counts. Each API node refreshes it every 15 seconds, and a burst of joins between refreshes can go past the limit. The limit is checked when a channel's first session is placed, and later joins to that channel use the same server.

GET/v1/admin/voice/regionsAudit reason

Returns every Admin voice region object, sorted by name. Requires voice:region:list.

FieldTypeDescription
include_servers?booleanWhether to include each region's servers (default false)
FieldTypeDescription
regionsarray[Admin voice region object]Every configured region
StatusBodyCondition
200response bodyThe regions were returned

The rate limit is 200 requests per minute, on the admin:lookup bucket.

GET/v1/admin/voice/regions/{region_id}Audit reason

Returns one Admin voice region object. Requires voice:region:list.

FieldTypeDescription
include_servers?booleanWhether to include the region's servers (default true)
FieldTypeDescription
region?Admin voice region objectThe region, or null
StatusBodyCondition
200response bodyThe region was looked up, and region is null when none has the ID

The rate limit is 200 requests per minute, on the admin:lookup bucket.

POST/v1/admin/voice/regionsAudit reason

Creates a region, and an id already in use overwrites that region. Requires voice:region:create.

FieldTypeDescription
idstringThe ID of the region (1-64 characters)
namestringThe name shown for the region in a client (1-100 characters)
emojistringThe emoji shown for the region (1-64 characters)
latitudenumberThe latitude placement measures distance from
longitudenumberThe longitude placement measures distance from
is_default?booleanWhether automatic placement falls back to this region (default false)
vip_only?booleanWhether the guild needs VIP_VOICE (default false)
required_guild_features?array[string]Guild features that admit a guild (max 100)
allowed_guild_ids?array[snowflake]The guilds admitted without other guild checks (max 1000)
allowed_user_ids?array[snowflake]The only accounts allowed to use the region, when not empty (max 1000)
FieldTypeDescription
regionAdmin voice region objectThe created region
StatusBodyCondition
200response bodyThe region was created

The rate limit is 100 requests per minute, on the admin:guild:modify bucket.

PATCH/v1/admin/voice/regions/{region_id}Audit reason

Updates a region, and an array you send replaces the stored one. Requires voice:region:update.

FieldTypeDescription
name?stringThe name shown for the region in a client (1-100 characters)
emoji?stringThe emoji shown for the region (1-64 characters)
latitude?numberThe latitude placement measures distance from
longitude?numberThe longitude placement measures distance from
is_default?booleanWhether automatic placement falls back to this region
vip_only?booleanWhether the guild needs VIP_VOICE
required_guild_features?array[string]Guild features that admit a guild (max 100)
allowed_guild_ids?array[snowflake]The guilds admitted without other guild checks (max 1000)
allowed_user_ids?array[snowflake]The only accounts allowed to use the region, when not empty (max 1000)
FieldTypeDescription
regionAdmin voice region objectThe updated region
StatusBodyCondition
200response bodyThe region was updated
404error responseUNKNOWN_VOICE_REGION when no region has the ID

The rate limit is 100 requests per minute, on the admin:guild:modify bucket.

DELETE/v1/admin/voice/regions/{region_id}Audit reason

Deletes a region and every server in it. Sessions on those servers stay connected, but Fluxer can't mute or disconnect them. Requires voice:region:delete.

FieldTypeDescription
successbooleanAlways true
StatusBodyCondition
200response bodyThe region was deleted
404error responseUNKNOWN_VOICE_REGION when no region has the ID

The rate limit is 100 requests per minute, on the admin:guild:modify bucket.

GET/v1/admin/voice/regions/{region_id}/serversAudit reason

Returns every Admin voice server object in a region. Requires voice:server:list.

FieldTypeDescription
serversarray[Admin voice server object]The servers in the region, empty when the region doesn't exist
StatusBodyCondition
200response bodyThe servers were returned

The rate limit is 200 requests per minute, on the admin:lookup bucket.

GET/v1/admin/voice/regions/{region_id}/servers/{server_id}Audit reason

Returns one Admin voice server object. Requires voice:server:list.

FieldTypeDescription
server?Admin voice server objectThe server, or null
StatusBodyCondition
200response bodyThe server was looked up, and server is null when none matches

The rate limit is 200 requests per minute, on the admin:lookup bucket.

POST/v1/admin/voice/regions/{region_id}/serversAudit reason

Adds a server to a region. Requires voice:server:create.

FieldTypeDescription
server_idstringThe ID of the server, unique inside the region (1-64 characters)
endpointstringThe LiveKit ws:// or wss:// URL that clients connect to
api_keystringThe LiveKit API key (1-256 characters)
api_secretstringThe LiveKit API secret (1-256 characters)
latitude?1?numberThe latitude used for this server, or null to use the region's
longitude?1?numberThe longitude used for this server, or null to use the region's
is_active?booleanWhether new sessions can be placed on the server (default true)
soft_connection_limit??integerSee soft connection limits, or null for none (1-2147483647)
notes??stringAdmin notes about the server, trimmed, with an empty value stored as null (max 2000 characters)
vip_only?booleanWhether the guild needs VIP_VOICE (default false)
required_guild_features?array[string]Guild features that admit a guild (max 100)
allowed_guild_ids?array[snowflake]The guilds admitted without other guild checks (max 1000)
allowed_user_ids?array[snowflake]The only accounts allowed to use the server, when not empty (max 1000)
internal_endpoint??stringThe http:// or https:// URL the API uses for the control API and health checks (max 512 characters)
draining?booleanWhether new calls go to other servers (default false)
public_ipv4??stringThe public IPv4 address LiveKit gives to clients, for the admin panel
public_ipv6??stringThe public IPv6 address LiveKit gives to clients, for the admin panel

1 Send both coordinates or neither

FieldTypeDescription
serverAdmin voice server objectThe created server
StatusBodyCondition
200response bodyThe server was created
404error responseUNKNOWN_VOICE_REGION when no region has the ID
409error responseVOICE_SERVER_ID_IN_USE for a taken ID, or VOICE_SERVER_API_KEY_IN_USE for a taken key

The rate limit is 100 requests per minute, on the admin:guild:modify bucket.

PATCH/v1/admin/voice/regions/{region_id}/servers/{server_id}Audit reason

Updates a server, and an array you send replaces the stored one. Requires voice:server:update.

FieldTypeDescription
endpoint?stringThe LiveKit ws:// or wss:// URL that clients connect to
api_key?stringThe LiveKit API key (1-256 characters)
api_secret?stringThe LiveKit API secret (1-256 characters)
latitude?1?numberThe latitude used for this server, or null to use the region's
longitude?1?numberThe longitude used for this server, or null to use the region's
is_active?booleanWhether new sessions can be placed on the server
soft_connection_limit??integerSee soft connection limits, or null for none (1-2147483647)
notes??stringAdmin notes about the server, trimmed, with null or an empty value clearing them (max 2000 characters)
vip_only?booleanWhether the guild needs VIP_VOICE
required_guild_features?array[string]Guild features that admit a guild (max 100)
allowed_guild_ids?array[snowflake]The guilds admitted without other guild checks (max 1000)
allowed_user_ids?array[snowflake]The only accounts allowed to use the server, when not empty (max 1000)
internal_endpoint??stringThe http:// or https:// URL the API uses for the control API and health checks, or null to use endpoint
draining?booleanWhether new calls go to other servers
public_ipv4??stringThe public IPv4 address LiveKit gives to clients, or null to clear it
public_ipv6??stringThe public IPv6 address LiveKit gives to clients, or null to clear it

1 Send both coordinates or neither, and send both as null to use the region's coordinates

FieldTypeDescription
serverAdmin voice server objectThe updated server
StatusBodyCondition
200response bodyThe server was updated
404error responseUNKNOWN_VOICE_SERVER when no server matches
409error responseVOICE_SERVER_API_KEY_IN_USE when another server uses the new key

The rate limit is 100 requests per minute, on the admin:guild:modify bucket.

DELETE/v1/admin/voice/regions/{region_id}/servers/{server_id}Audit reason

Deletes a server and its stored credentials. Sessions on it stay connected, but Fluxer can't mute or disconnect them. Requires voice:server:delete.

FieldTypeDescription
successbooleanAlways true
StatusBodyCondition
200response bodyThe server was deleted
404error responseUNKNOWN_VOICE_SERVER when no server matches

The rate limit is 100 requests per minute, on the admin:guild:modify bucket.

POST/v1/admin/voice/regions/{region_id}/servers/{server_id}/test-tokenAudit reason

Signs a LiveKit token for a hidden, audio only participant in a throwaway room whose name starts with rexctl-test-. The token works for 60 seconds. rexctl voice test uses it to test a server from outside its network, and Fluxer ignores webhooks for these rooms. Requires voice:server:update.

FieldTypeDescription
tokenstringThe LiveKit token
endpointstringThe server's endpoint
roomstringThe name of the throwaway room
StatusBodyCondition
200response bodyThe token was signed
404error responseUNKNOWN_VOICE_SERVER when no server matches

The rate limit is 100 requests per minute, on the admin:guild:modify bucket.

A join token lets one machine register one voice server with Join a voice server. It looks like fvj_ followed by its ID, an underscore and a 43 character secret. Fluxer stores only a hash of the secret. A token works once, for 60 seconds to 24 hours, and the list keeps it for 7 days after it expires. Whoever runs the server can record the calls placed on it. A token trusts that machine with calls, and it gives no other access.

FieldTypeDescription
token_idsnowflakeThe ID of the token
statusstringpending, used, expired or revoked
created_atISO8601 timestampWhen the token was created
expires_atISO8601 timestampWhen the token stops working
used_at?ISO8601 timestampWhen a server joined with it
used_server_id?stringThe server that joined with it
revoked_at?ISO8601 timestampWhen it was revoked
region_id?stringThe region preset, or null to let Fluxer pick
region_name?stringThe name of the new region the join creates, or null
server_id?stringThe server ID preset, or null
hostname?stringThe hostname the endpoint must use, or null
notes?stringThe notes for the new server, or null
POST/v1/admin/voice/join-tokensAudit reason

Requires voice:join_token:create. Every preset wins over what the joining machine sends.

FieldTypeDescription
region_id??stringAn existing region, or the ID of a new region with region_name (lowercase letters, digits and dashes, 1-64 characters)
region_name??stringThe name of a new region the join creates (1-100 characters)
region_emoji??stringThe emoji of the new region (1-64 characters)
region_latitude??numberThe latitude of the new region, with region_longitude (-90 to 90)
region_longitude??numberThe longitude of the new region, with region_latitude (-180 to 180)
server_id??stringThe server ID (lowercase letters, digits and dashes, 1-64 characters)
hostname??stringThe hostname the endpoint must use
internal_endpoint??stringThe URL the API uses for the control API and health checks of the new server
soft_connection_limit??integerThe soft connection limit of the new server
notes??stringThe notes of the new server (max 2000 characters)
vip_only?booleanRestricts the new server to VIP_VOICE guilds (default false)
required_guild_features?array[string]Guild features that admit a guild to the new server (max 100)
allowed_guild_ids?array[snowflake]Guilds admitted to the new server (max 1000)
allowed_user_ids?array[snowflake]The only accounts allowed on the new server (max 1000)
expires_in_seconds?integerSeconds until the token stops working (60-86400, default 3600)

A new region from region_name gets region_latitude and region_longitude. Without them, it gets the location of the server that joins.

FieldTypeDescription
token_idsnowflakeThe ID of the token
tokenstringThe token, which no other response returns
expires_atISO8601 timestampWhen the token stops working
commandstringThe install command to run on the voice machine
StatusBodyCondition
200response bodyThe token was created
400error responseregion_latitude or region_longitude comes without the other, or without region_name
404error responseUNKNOWN_VOICE_REGION when no region has the ID in region_id and region_name is empty

The rate limit is 100 requests per minute, on the admin:guild:modify bucket.

GET/v1/admin/voice/join-tokens

Lists the tokens of the last 7 days, newest first, without their secrets. Requires voice:join_token:list.

FieldTypeDescription
tokensarray[voice join token object]The tokens

The rate limit is on the admin:lookup bucket.

DELETE/v1/admin/voice/join-tokens/{token_id}Audit reason

Stops a pending token from working. Requires voice:join_token:revoke.

FieldTypeDescription
successbooleanAlways true
StatusBodyCondition
200{"success": true}The token was revoked
401error responseVOICE_JOIN_TOKEN_INVALID when the token is unknown, used, expired or revoked

These routes are for the voice machine, not for admins. They take Authorization: VoiceJoin followed by a join token, and the rate limit is 10 requests per minute for each address.

POST/v1/voice/join/previewUnauthenticated

Shows what a join would choose, without using the token. The JSON body has public_ipv4, public_ipv6, region_id, cpu_count, memory_bytes and rexctl_version, all optional. Fluxer looks up one address in GeoIP, the first of public_ipv4, public_ipv6 and the address the request comes from. The response has the suggested server_id, server_id_pinned set to true when the token sets the server ID, the region with its id, name, distance_km, whether it's new and its source, the latitude and longitude from GeoIP rounded to 0.1 degrees, the hostname preset, a suggested_hostname, the soft_connection_limit preset, the webhook_url, the api_code_version, the release of the API, expires_at and the instance's turn_servers.

Fluxer picks the token's region first, then an existing region in region_id, then the existing region nearest to the machine when the instance has a GeoIP database, and the default region otherwise. It never creates a region unless the token has a new one. The source says which of these it used, as token, requested, nearest or default.

POST/v1/voice/joinUnauthenticated

Registers the server and uses the token.

FieldTypeDescription
endpointstringThe public wss:// URL of the server. A host that is an IP address must be IPv4. A server with only IPv6 needs a hostname
api_keystringA LiveKit API key no other server uses (1-256 characters)
api_secretstringIts secret (32-256 characters)
server_id?stringThe server ID, ignored when the token has one
region_id?stringAn existing region, ignored when the token has one
latitude?1numberThe server's latitude
longitude?1numberThe server's longitude
soft_connection_limit?integerIgnored when the token has one
public_ipv4?stringThe machine's public IPv4 address, for GeoIP and the admin panel
public_ipv6?stringThe machine's public IPv6 address, for GeoIP and the admin panel

1 Send both coordinates or neither

Fluxer resolves the endpoint's host, refuses it when any address is private unless FLUXER_VOICE_JOIN_ALLOW_PRIVATE_ENDPOINTS is true, and connects to the addresses it checked, one after another until one answers. GET on the endpoint's path must answer OK within 10 seconds with a certificate Fluxer trusts, and the room service must accept the key and secret. Then Fluxer saves the server, active and not draining, and the token stops working. The response has server_id, region_id, endpoint, webhook_url, node_url and turn_servers.

turn_servers is the list in FLUXER_LIVEKIT_TURN_SERVERS on the instance, as objects with host, port, protocol and either username and credential or secret and ttl. It's empty when the setting is. rexctl puts it in the LiveKit configuration of the server.

StatusCodeCondition
400VOICE_JOIN_ENDPOINT_INVALIDThe endpoint isn't wss://, uses an IPv6 address, a private address or another hostname than the token's
400VOICE_JOIN_ENDPOINT_UNREACHABLEThe endpoint didn't answer OK, with the status or error in detail
400VOICE_JOIN_KEY_MISMATCHThe server refused the key and secret
401VOICE_JOIN_TOKEN_INVALIDThe token is unknown, used, expired or revoked
404UNKNOWN_VOICE_REGIONNo region has the ID in region_id
409VOICE_JOIN_TOKEN_BUSYAnother join with the token is running
409VOICE_SERVER_ID_IN_USEAnother server has the ID
409VOICE_SERVER_API_KEY_IN_USEAnother server uses the key

A failed join leaves the token unused. The same machine can try again.

A server that joined manages its own row with GET, PATCH and DELETE on /v1/voice/node. The request takes Authorization: Bearer followed by an HS256 JWT signed with the server's API secret, with these claims:

  • iss set to the server's API key.
  • aud set to fluxer-voice-node.
  • method and path of the request, with path set to /voice/node.
  • exp in the future and at most 5 minutes ahead, and iat when present. Fluxer allows 60 seconds of clock difference either way.
  • sha256 set to the base64 SHA-256 of the body, or an empty string for GET and DELETE.

Fluxer refuses tokens with a video grant. LiveKit participant tokens and webhooks signed with the same secret don't work here.

GET/v1/voice/nodeUnauthenticated

Returns the server's region_id, server_id, endpoint, latitude, longitude, is_active, draining, soft_connection_limit, public_ipv4, public_ipv6, turn_servers, features and health. features lists what the API supports for voice servers. server_addresses means PATCH takes public_ipv4 and public_ipv6.

PATCH/v1/voice/nodeUnauthenticated

Takes endpoint, draining, latitude, longitude, soft_connection_limit, public_ipv4 and public_ipv6, and returns the server like GET. An empty string for public_ipv4 or public_ipv6 clears it. Fluxer checks a new endpoint the same way as a join. When the server has an internal endpoint, Fluxer also tries the new public endpoint and logs a warning when it doesn't answer, without refusing the change. A new endpoint must keep the token's hostname, and a server can't change a soft connection limit the token set. A server can't change its region, its restrictions, internal_endpoint or is_active. A server an admin turned off stays off.

DELETE/v1/voice/nodeUnauthenticated

Removes the server and answers 204.

Every node route can fail with these errors.

StatusCodeCondition
401VOICE_NODE_UNKNOWNNo server has the key in iss, usually because an admin deleted it
401VOICE_NODE_SIGNATURE_INVALIDThe signature or a claim is wrong, or an admin changed the server's secret
401VOICE_NODE_TOKEN_EXPIREDThe signature matches, and exp or iat is outside the allowed time. The clock of the voice machine is usually off