Overview
For a LiveKit SDK to successfully connect to the server, it must pass an access token with the request. This token encodes the identity of a participant, name of the room, capabilities (for example, publishing audio, video, or data), and permissions (for example, permission to moderate a room). Access tokens are JWT-based and signed with your API secret to prevent forgery.
Access tokens also carry an expiration time, after which the server rejects connections with that token. Note: expiration time only impacts the initial connection, and not subsequent reconnects. To learn more, see Token refresh.
Tokens can either be generated by LiveKit on your application's behalf or manually on your own backend server. Choose a token generation approach that fits your needs:
Sandbox token generation
Delegate token generation to LiveKit Cloud so you can get started quickly.
Endpoint token generation
Implement your own token generation endpoint once you're ready to go to production.
Custom token generation
Adapt a fully custom token generation scheme using LiveKit's abstractions
Token structure
Access tokens are JWTs that contain participant identity, room information, and permissions. When decoded, a token's payload includes standard JWT fields and LiveKit-specific grants.
The following example shows the decoded body of a join token:
{"exp": 1621657263,"iss": "APIMmxiL8rquKztZEoZJV9Fb","sub": "myidentity","nbf": 1619065263,"video": {"room": "myroom","roomJoin": true},"metadata": ""}
| field | description |
|---|---|
exp | Expiration time of token |
iss | API key used to issue this token |
sub | Unique identity for the participant |
nbf | Start time that the token becomes valid |
video | Video grant, including room permissions (see below) |
metadata | Participant metadata |
attributes | Participant attributes (key/value pairs of strings) |
sip | SIP grant |
Token creation
Use these SDK methods to create tokens programmatically when building your own token generation endpoint or custom TokenSource.
These methods aren't required when using the sandbox because LiveKit Cloud handles token generation so you don't need to create them manually.
lk token create \--api-key <KEY> \--api-secret <SECRET> \--identity <NAME> \--room <ROOM_NAME> \--join \--valid-for 1h
import { AccessToken, VideoGrant } from 'livekit-server-sdk';const roomName = 'name-of-room';const participantName = 'user-name';const at = new AccessToken('api-key', 'secret-key', {identity: participantName,});const videoGrant: VideoGrant = {room: roomName,roomJoin: true,canPublish: true,canSubscribe: true,};at.addGrant(videoGrant);const token = await at.toJwt();console.log('access token', token);
import ("time""github.com/livekit/protocol/auth")func getJoinToken(apiKey, apiSecret, room, identity string) (string, error) {canPublish := truecanSubscribe := trueat := auth.NewAccessToken(apiKey, apiSecret)grant := &auth.VideoGrant{RoomJoin: true,Room: room,CanPublish: &canPublish,CanSubscribe: &canSubscribe,}at.SetVideoGrant(grant).SetIdentity(identity).SetValidFor(time.Hour)return at.ToJWT()}
require 'livekit'token = LiveKit::AccessToken.new(api_key: 'yourkey', api_secret: 'yoursecret')token.identity = 'participant-identity'token.name = 'participant-name'token.video_grant=(LiveKit::VideoGrant.from_hash(roomJoin: true,room: 'room-name'))puts token.to_jwt
import io.livekit.server.*;public String createToken() {AccessToken token = new AccessToken("apiKey", "secret");token.setName("participant-name");token.setIdentity("participant-identity");token.setMetadata("metadata");token.addGrants(new RoomJoin(true), new Room("room-name"));return token.toJwt();}
from livekit import apiimport ostoken = api.AccessToken(os.getenv('LIVEKIT_API_KEY'), os.getenv('LIVEKIT_API_SECRET')) \.with_identity("identity") \.with_name("name") \.with_grants(api.VideoGrants(room_join=True,room="my-room",)).to_jwt()
use livekit_api::access_token;use std::env;fn create_token() -> Result<String, AccessTokenError> {let api_key = env::var("LIVEKIT_API_KEY").expect("LIVEKIT_API_KEY is not set");let api_secret = env::var("LIVEKIT_API_SECRET").expect("LIVEKIT_API_SECRET is not set");let token = access_token::AccessToken::with_api_key(&api_key, &api_secret).with_identity("identity").with_name("name").with_grants(access_token::VideoGrants {room_join: true,room: "my-room".to_string(),..Default::default()}).to_jwt();return token}
For other platforms, you can either implement token generation yourself or use the lk command.
Token signing is fairly straightforward, see JS implementation as a reference.
LiveKit CLI is available at https://github.com/livekit/livekit-cli
Grants and permissions
Grants define what a participant can do in a room or with LiveKit services. Tokens can include video grants, SIP grants, and room configurations.
Video grant
Room permissions are specified in the video field of a decoded join token.
This field may contain one or more of the following properties:
| field | type | description |
|---|---|---|
roomCreate | boolean | Permission to create or delete rooms |
roomList | boolean | Permission to list available rooms |
roomJoin | boolean | Permission to join a room |
roomAdmin | boolean | Permission to moderate a room |
roomRecord | boolean | Permissions to use Egress service |
ingressAdmin | boolean | Permissions to use Ingress service |
room | string | Name of the room, required if join or admin is set |
canPublish | boolean | Allow participant to publish tracks |
canPublishData | boolean | Allow participant to publish data to the room |
canPublishSources | string | Requires canPublish to be true. When set, only listed sources can be published. (camera, microphone, screen_share, screen_share_audio) |
canSubscribe | bool | Allow participant to subscribe to tracks |
canUpdateOwnMetadata | bool | Allow participant to update its own metadata |
hidden | boolean | Hide participant from others in the room |
kind | string | Type of participant (standard, ingress, egress, sip, or agent). This field is typically set by LiveKit internals. |
destinationRoom | string | Name of the room a participant can be forwarded to. |
Creating a subscribe-only token
This example shows how to create a token where the participant can only subscribe (and not publish) into the room:
{..."video": {"room": "myroom","roomJoin": true,"canSubscribe": true,"canPublish": false,"canPublishData": false}}
Creating a camera-only token
This example shows how to create a token where the participant can publish camera tracks, but disallow other sources:
{..."video": {"room": "myroom","roomJoin": true,"canSubscribe": true,"canPublish": true,"canPublishSources": ["camera"]}}
SIP grant
To interact with the SIP service, permission must be granted in the sip field of the JWT.
This field may contain the following properties:
| field | type | description |
|---|---|---|
admin | boolean | Permission to manage SIP trunks and dispatch rules. |
call | bool | Permission to make SIP calls via CreateSIPParticipant. |
Creating a token with SIP grants
This example shows how to create a token where the participant can manage SIP trunks and dispatch rules, and make SIP calls:
import { AccessToken, SIPGrant, VideoGrant } from 'livekit-server-sdk';const roomName = 'name-of-room';const participantName = 'user-name';const at = new AccessToken('api-key', 'secret-key', {identity: participantName,});const sipGrant: SIPGrant = {admin: true,call: true,};const videoGrant: VideoGrant = {room: roomName,roomJoin: true,};at.addGrant(sipGrant);at.addGrant(videoGrant);const token = await at.toJwt();console.log('access token', token);
import ("time""github.com/livekit/protocol/auth")func getJoinToken(apiKey, apiSecret, room, identity string) (string, error) {at := auth.NewAccessToken(apiKey, apiSecret)videoGrant := &auth.VideoGrant{RoomJoin: true,Room: room,}sipGrant := &auth.SIPGrant{Admin: true,Call: true,}at.SetSIPGrant(sipGrant).SetVideoGrant(videoGrant).SetIdentity(identity).SetValidFor(time.Hour)return at.ToJWT()}
require 'livekit'token = LiveKit::AccessToken.new(api_key: 'yourkey', api_secret: 'yoursecret')token.identity = 'participant-identity'token.name = 'participant-name'token.video_grant=(LiveKit::VideoGrant.from_hash(roomJoin: true,room: 'room-name'))token.sip_grant=(LiveKit::SIPGrant.from_hash(admin: true, call: true))puts token.to_jwt
import io.livekit.server.*;public String createToken() {AccessToken token = new AccessToken("apiKey", "secret");// Fill in token information.token.setName("participant-name");token.setIdentity("participant-identity");token.setMetadata("metadata");// Add room and SIP privileges.token.addGrants(new RoomJoin(true), new RoomName("room-name"));token.addSIPGrants(new SIPAdmin(true), new SIPCall(true));return token.toJwt();}
from livekit import apiimport ostoken = api.AccessToken(os.getenv('LIVEKIT_API_KEY'),os.getenv('LIVEKIT_API_SECRET')) \.with_identity("identity") \.with_name("name") \.with_grants(api.VideoGrants(room_join=True,room="my-room")) \.with_sip_grants(api.SIPGrants(admin=True,call=True)).to_jwt()
use livekit_api::access_token;use std::env;fn create_token() -> Result<String, access_token::AccessTokenError> {let api_key = env::var("LIVEKIT_API_KEY").expect("LIVEKIT_API_KEY is not set");let api_secret = env::var("LIVEKIT_API_SECRET").expect("LIVEKIT_API_SECRET is not set");let token = access_token::AccessToken::with_api_key(&api_key, &api_secret).with_identity("rust-bot").with_name("Rust Bot").with_grants(access_token::VideoGrants {room_join: true,room: "my-room".to_string(),..Default::default()}).with_sip_grants(access_token::SIPGrants {admin: true,call: true}).to_jwt();return token}
Room configuration
You can create an access token for a user that includes room configuration options. When a room is created for a user, it uses the configuration that is stored in the token. This is useful for explicitly dispatching an agent when a user joins a room.
For the full list of RoomConfiguration fields, see RoomConfiguration.
Creating a token with room configuration
For a full example of explicit agent dispatch, see the example in GitHub.
import { AccessToken, SIPGrant, VideoGrant } from 'livekit-server-sdk';import { RoomAgentDispatch, RoomConfiguration } from '@livekit/protocol';const roomName = 'name-of-room';const participantName = 'user-name';const agentName = 'my-agent';const at = new AccessToken('api-key', 'secret-key', {identity: participantName,});const videoGrant: VideoGrant = {room: roomName,roomJoin: true,};at.addGrant(videoGrant);at.roomConfig = new RoomConfiguration (agents: [new RoomAgentDispatch({agentName: "test-agent",metadata: "test-metadata"})]);const token = await at.toJwt();console.log('access token', token);
import ("time""github.com/livekit/protocol/auth""github.com/livekit/protocol/livekit")func getJoinToken(apiKey, apiSecret, room, identity string) (string, error) {at := auth.NewAccessToken(apiKey, apiSecret)videoGrant := &auth.VideoGrant{RoomJoin: true,Room: room,}roomConfig := &livekit.RoomConfiguration{Agents: []*livekit.RoomAgentDispatch{{AgentName: "test-agent",Metadata: "test-metadata",}},}at.SetVideoGrant(videoGrant).SetRoomConfig(roomConfig).SetIdentity(identity).SetValidFor(time.Hour)return at.ToJWT()}
require 'livekit'token = LiveKit::AccessToken.new(api_key: 'yourkey', api_secret: 'yoursecret')token.identity = 'participant-identity'token.name = 'participant-name'token.video_grant=(LiveKit::VideoGrant.new(roomJoin: true,room: 'room-name'))token.room_config=(LiveKit::Proto::RoomConfiguration.new(max_participants: 10agents: [LiveKit::Proto::RoomAgentDispatch.new(agent_name: "test-agent",metadata: "test-metadata",)]))puts token.to_jwt
For a full example of explicit agent dispatch, see the example in GitHub.
from livekit import apiimport ostoken = api.AccessToken(os.getenv('LIVEKIT_API_KEY'),os.getenv('LIVEKIT_API_SECRET')) \.with_identity("identity") \.with_name("name") \.with_grants(api.VideoGrants(room_join=True,room="my-room")) \.with_room_config(api.RoomConfiguration(agents=[api.RoomAgentDispatch(agent_name="test-agent", metadata="test-metadata")],),).to_jwt()
use livekit_api::access_token;use std::env;fn create_token() -> Result<String, access_token::AccessTokenError> {let api_key = env::var("LIVEKIT_API_KEY").expect("LIVEKIT_API_KEY is not set");let api_secret = env::var("LIVEKIT_API_SECRET").expect("LIVEKIT_API_SECRET is not set");let token = access_token::AccessToken::with_api_key(&api_key, &api_secret).with_identity("rust-bot").with_name("Rust Bot").with_grants(access_token::VideoGrants {room_join: true,room: "my-room".to_string(),..Default::default()}).with_room_config(livekit::RoomConfiguration {agents: [livekit::AgentDispatch{name: "my-agent"}]}).to_jwt();return token}
Token lifecycle
Tokens have a lifecycle that includes refresh and permission updates during a session.
Token refresh
LiveKit server proactively issues refreshed tokens to connected clients, ensuring they can reconnect if disconnected. These refreshed access tokens have a 10-minute expiration.
Tokens are also automatically refreshed when there are changes to a participant's name, permissions, or metadata.
Updating permissions
A participant's permissions can be updated at any time, even after they've already connected. This is useful in applications where the participant's role could change during the session, such as in a participatory livestream.
It's possible to issue a token with canPublish: false initially, and then update it to canPublish: true during the session. Permissions can be changed with the UpdateParticipant server API.