LiveKit docs › Reference › Tokens & grants

---

# Tokens & grants

> Reference documentation for access tokens, grants, and permissions.

## 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. Expiration time only impacts the initial connection, and not subsequent reconnects. To learn more, see [Token refresh](#token-refresh).

For guidance on generating tokens in your frontend or backend, see:

- **[Authentication overview](https://docs.livekit.io/frontends/build/authentication.md)**: Learn about `TokenSource` and the recommended authentication workflows.

- **[Custom token generation](https://docs.livekit.io/frontends/build/authentication/custom.md)**: Create tokens programmatically with server SDKs.

## 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:

```json
{
  "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 |

## 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 | Permission to use Egress service |
| `ingressAdmin` | boolean | Permission 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` | boolean | Allow participant to subscribe to tracks |
| `canUpdateOwnMetadata` | boolean | Allow participant to update its own metadata |
| `hidden` | boolean | Hide participant from others in the room |
| `kind` | string | [Type of participant](https://docs.livekit.io/intro/basics/rooms-participants-tracks/participants.md#types-of-participants) (standard, ingress, egress, sip, agent, or connector). This field is typically set by LiveKit internals. |
| `destinationRoom` | string | Name of the room a participant can be [forwarded](https://docs.livekit.io/intro/basics/rooms-participants-tracks/participants.md#forwardparticipant) 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:

```json
{
  ...
  "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:

```json
{
  ...
  "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` | boolean | 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:

**Node.js**:

```typescript
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);

```

---

**Go**:

```go
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()
}

```

---

**Ruby**:

```ruby
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

```

---

**Python**:

```python
from livekit import api
import os

token = api.AccessToken(os.environ['LIVEKIT_API_KEY'],
                        os.environ['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()

```

---

**Rust**:

```rust
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. The configuration is applied only when the room is first created. If the room already exists, LiveKit ignores the configuration in the token. This is useful for [explicitly dispatching an agent](https://docs.livekit.io/agents/server/agent-dispatch.md) when the room is created.

For the full list of `RoomConfiguration` fields, see [RoomConfiguration](https://docs.livekit.io/reference/server/server-apis.md#roomconfiguration).

#### Creating a token with room configuration

**Node.js**:

For a full example of explicit agent dispatch, see the [example](https://github.com/livekit/node-sdks/blob/main/examples/agent-dispatch/index.ts) in GitHub.

```typescript
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);

```

---

**Go**:

```go
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()
}

```

---

**Ruby**:

```ruby
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: 10
    agents: [LiveKit::Proto::RoomAgentDispatch.new(
      agent_name: "test-agent",
      metadata: "test-metadata",
    )]
  )
)

puts token.to_jwt

```

---

**Python**:

For a full example of explicit agent dispatch, see the [example](https://github.com/livekit/python-sdks/blob/main/examples/agent_dispatch.py) in GitHub.

```python
from livekit import api
import os

token = api.AccessToken(os.environ['LIVEKIT_API_KEY'],
                        os.environ['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()

```

---

**Rust**:

```rust
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.

### Token revocation

When a participant's permissions are updated or they are removed from a room, their existing token is automatically revoked. This prevents the participant from using an old, cached token to reconnect with outdated permissions.

> ℹ️ **Cloud-only feature**
> 
> Token revocation is only available on LiveKit Cloud. For [self-hosted deployments](#self-hosted), see the following section.

Token revocation works by tracking the token's `nbf` (not before) timestamp. When permissions change or a participant is removed, the server records the current time. Any subsequent connection attempts with tokens issued before this time are rejected.

This security feature ensures:

- Removed participants cannot immediately rejoin with their existing token.
- Permission revocations take effect immediately, even for reconnections.
- Participants must request a new token to reconnect.

#### Self-hosted deployments

For self-hosted deployments, removing a participant or updating their permissions doesn't invalidate the participant's existing token. To prevent a participant from rejoining the same room or using a token with outdated permissions, generate access tokens with a short Time-To-Live (TTL).

When you remove a participant, do not generate a new token for the same participant via your application's backend.

### 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`](https://docs.livekit.io/intro/basics/rooms-participants-tracks/participants.md#updating-participant-permissions) server API.

---

This document was rendered at 2026-06-07T11:35:38.925Z.
For the latest version of this document, see [https://docs.livekit.io/frontends/reference/tokens-grants.md](https://docs.livekit.io/frontends/reference/tokens-grants.md).

To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).