# Source: https://docs.livekit.io/frontends/authentication/tokens/endpoint.md

LiveKit docs › Authentication › Tokens › Endpoint token generation

---

# Endpoint token generation

> Implement a LiveKit standardized token endpoint.

## Overview

For a frontend or mobile app to connect to a LiveKit room, it needs a server URL pointing to your LiveKit Cloud project or self-hosted SFU instance, and a token generated by your backend server.

Endpoint token generation is an alternative to [sandbox token generation](https://docs.livekit.io/frontends/authentication/tokens/sandbox-token-server.md). Use it when you can't use a sandbox token server or when you're ready to deploy your app to production. By following the standard endpoint format, you can use an endpoint-type `TokenSource` to integrate this same token generation endpoint into all of your applications.

### Endpoint schema

Request format:

| Type | Name | Value/Description |
| Method | `POST` |  |
| Headers | `Content-Type` | `application/json` |
| Optional Body | `room_name` | (Optional room name) |
|  | `participant_identity` | (Optional participant identity) |
|  | `participant_name` | (Optional participant name) |
|  | `participant_metadata` | (Optional participant metadata) |
|  | `participant_attributes` | (Optional participant attributes) |
|  | `room_config` | (Optional room config) |

Your endpoint should be able to accept any of these optional body fields and generate a token encoding the relevant token attributes. If a given field shouldn't be configurable, return a corresponding 4xx status code from the endpoint.

Response format:

| Type | Name | Value/Description |
| Status Code | 201 (Created) |  |
| Headers | `Content-Type` | `application/json` |
| Response Body | `server_url` | Room connection URL |
|  | `participant_token` | Room connection token |

## Use an endpoint-based TokenSource

This guide walks you through setting up a server to generate room connection credentials.

1. Install the LiveKit Server SDK:

**Go**:

```shell
go get github.com/livekit/server-sdk-go/v2

```

---

**Node.js**:

```shell
# yarn:
yarn add livekit-server-sdk

# npm:
npm install livekit-server-sdk --save

```

---

**Ruby**:

```ruby
# Add to your Gemfile
gem 'livekit-server-sdk'

```

---

**Python**:

```shell
uv add livekit-api

```

---

**Rust**:

```toml
# Cargo.toml
[package]
name = "example_server"
version = "0.1.0"
edition = "2021"

[dependencies]
livekit-api = "0.2.0"
# Remaining deps are for the example server
warp = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }

```

---

**PHP**:

```shell
composer require agence104/livekit-server-sdk

```
2. Create a new file named `development.env` with your connection URL, API key and secret:

```shell
export LIVEKIT_URL=%{wsURL}%
export LIVEKIT_API_KEY=%{apiKey}%
export LIVEKIT_API_SECRET=%{apiSecret}%

```
3. Create a server to host an endpoint at `/getToken`, following the token endpoint specification:

**Go**:

```go
 // server.go
 package main

 import (
   "encoding/json"
   "log"
   "net/http"
   "os"

   "github.com/livekit/protocol/auth"
   "github.com/livekit/protocol/livekit"
 )

 type TokenSourceRequest struct {
   RoomName              string                     `json:"room_name"`
   ParticipantName       string                     `json:"participant_name"`
   ParticipantIdentity   string                     `json:"participant_identity"`
   ParticipantMetadata   string                     `json:"participant_metadata"`
   ParticipantAttributes map[string]string          `json:"participant_attributes"`
   RoomConfig            *livekit.RoomConfiguration `json:"room_config"`
 }

 type TokenSourceResponse struct {
   ServerURL        string `json:"server_url"`
   ParticipantToken string `json:"participant_token"`
 }

 func getJoinToken(body TokenSourceRequest) string {
   at := auth.NewAccessToken(os.Getenv("LIVEKIT_API_KEY"), os.Getenv("LIVEKIT_API_SECRET"))

   // If this room doesn't exist, it'll be automatically created when
   // the first participant joins
   roomName := body.RoomName
   if roomName == "" {
     roomName = "quickstart-room"
   }
   grant := &auth.VideoGrant{
     RoomJoin: true,
     Room:     room,
   }
   at.AddGrant(grant)

   if body.RoomConfig != nil {
     at.SetRoomConfig(body.RoomConfig)
   }

   // Participant related fields.
   // `participantIdentity` will be available as LocalParticipant.identity
   // within the livekit-client SDK
   if body.ParticipantIdentity != "" {
     at.SetIdentity(body.ParticipantIdentity)
   } else {
     at.SetIdentity("quickstart-identity")
   }
   if body.ParticipantName != "" {
     at.SetName(body.ParticipantName)
   } else {
     at.SetName("quickstart-username")
   }
   if len(body.ParticipantMetadata) > 0 {
     at.SetMetadata(body.ParticipantMetadata)
   }
   if len(body.ParticipantAttributes) > 0 {
     at.SetAttributes(body.ParticipantAttributes)
   }

   token, _ := at.ToJWT()
   return token
 }

 func main() {
   http.HandleFunc("/getToken", func(w http.ResponseWriter, r *http.Request) {
     defer r.Body.Close()

     // Declare a new Person struct to hold the decoded data
     var body TokenSourceRequest

     // Create a JSON decoder and decode the request body into the struct
     err := json.NewDecoder(r.Body).Decode(&body)
     if err != nil {
       http.Error(w, err.Error(), http.StatusBadRequest)
       return
     }

     b, _ := json.Marshal(TokenSourceResponse{
       ServerURL:        os.Getenv("LIVEKIT_URL"),
       ParticipantToken: getJoinToken(body),
     })
     w.Write(b)
   })

   log.Fatal(http.ListenAndServe(":3000", nil))
 }

```

---

**Node.js**:

```js
// server.js
import express from 'express';
import { AccessToken } from 'livekit-server-sdk';

const app = express();
const port = 3000;

app.post('/getToken', async (req, res) => {
  const body = req.body;

  // If this room doesn't exist, it'll be automatically created when
  // the first participant joins
  const roomName = body.room_name ?? 'quickstart-room';
  const roomConfig = body.room_config ?? {};

  // Participant related fields. 
  // `participantIdentity` will be available as LocalParticipant.identity
  // within the livekit-client SDK
  const participantIdentity = body.participant_identity ?? 'quickstart-identity';
  const participantName = body.participant_name ?? 'quickstart-username';
  const participantMetadata = body.participant_metadata ?? '';
  const participantAttributes = body.participant_attributes ?? {};

  const at = new AccessToken(process.env.LIVEKIT_API_KEY, process.env.LIVEKIT_API_SECRET, {
    identity: participantIdentity,
    name: participantName,
    metadata: participantMetadata,
    attributes: participantAttributes,

    // Token to expire after 10 minutes
    ttl: '10m',
  });
  at.addGrant({ roomJoin: true, room: roomName });
  at.roomConfig = roomConfig;

  const participantToken = await at.toJwt();

  res.send({ serverURL: process.env.LIVEKIT_URL, participantToken });
});

app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

```

---

**Ruby**:

```ruby
# server.rb
require 'livekit'
require 'sinatra'

set :port, 3000

def create_token(body)
  token = LiveKit::AccessToken.new(api_key: ENV['LIVEKIT_API_KEY'], api_secret: ENV['LIVEKIT_API_SECRET'])

  // If this room doesn't exist, it'll be automatically created when
  // the first participant joins
  room_name = body["room_name"] || 'quickstart-room';
  token.add_grant(roomJoin: true, room: room_name)

  token.room_config = body["room_config"] || {};

  // Participant related fields. 
  // `participantIdentity` will be available as LocalParticipant.identity
  // within the livekit-client SDK
  token.identity = body["participant_identity"] || "quickstart-identity";
  token.name = body["participant_name"] || "quickstart-username";
  if body["participant_metadata"] do
    token.metadata = body["participant_metadata"]
  end
  if body["participant_attributes"] do
    token.attributes = body["participant_attributes"]
  end

  token.to_jwt
end

get '/getToken' do
  request.body.rewind # in case someone already read it
  body = JSON.parse(request.body.read)

  json { "server_url" => ENV['LIVEKIT_URL'], "participant_token" => create_token(body) }
end

```

---

**Python**:

```python
# server.py
import os
from livekit import api
from flask import Flask

app = Flask(__name__)

@app.route('/getToken', method=['POST'])
def getToken():
    body = request.get_json()
    
    token = api.AccessToken(os.getenv('LIVEKIT_API_KEY'), os.getenv('LIVEKIT_API_SECRET'))
    
    # If this room doesn't exist, it'll be automatically created when
    # the first participant joins
    room_name = body['room_name'] || 'quickstart-room'
    token.add_grant(room_join=True, room: room_name)
    
    if body.get('room_config'):
        token = token.with_room_config(body['room_config'])
    
    # Participant related fields. 
    # `participantIdentity` will be available as LocalParticipant.identity
    # within the livekit-client SDK
    token = token
        .with_identity(body['participant_identity'] || 'quickstart-identity')
        .with_name(body['participant_name'] || 'quickstart-username')
    if body.get('participant_metadata'):
        token = token.with_metadata(body['participant_metadata'])
    if body.get('participant_attributes'):
        token = token.with_attributes(body['participant_attributes'])
    
    return { server_url: os.getenv('LIVEKIT_URL'), participant_token: token.to_jwt() }

if __name__ == '__main__':
    app.run(port=3000)

```

---

**Rust**:

```rust
// src/main.rs

use livekit_api::access_token;
use warp::Filter;
use serde::{Serialize, Deserialize};
use std::env;

#[derive(Deserialize)]
struct TokenSourceRequest {
  #[serde(default)]
  room_name: Option<String>,
  #[serde(default)]
  participant_name: Option<String>,
  #[serde(default)]
  participant_identity: Option<String>,
  #[serde(default)]
  participant_metadata: Option<String>,
  #[serde(default)]
  participant_attributes: HashMap<String, String>,
  #[serde(default)]
  room_config: Option<livekit_protocol::RoomConfiguration>,
}

#[derive(Serialize)]
struct TokenSourceResponse {
  server_url: String,
  participant_token String,
}

#[tokio::main]
async fn main() {
    let server_url = env::var("LIVEKIT_URL").expect("LIVEKIT_URL is not set");

    // Define the route
    let create_token_route = warp::path("create-token")
        .and(warp::body::json())
        .map(|body: TokenSourceRequest| {
            let participant_token = create_token(body).unwrap();
            warp::reply::json(&TokenSourceResponse { server_url, participant_token })
        });

    // Start the server
    warp::serve(create_token_route).run(([127, 0, 0, 1], 3000)).await;
}

// Token creation function
fn create_token(body: TokenSourceRequest) -> 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 mut token = access_token::AccessToken::with_api_key(&api_key, &api_secret);

  // If this room doesn't exist, it'll be automatically created when
  // the first participant joins
  let room_name = body.get('room_name').unwrap_or("quickstart-room");
  token = token.with_grants(access_token::VideoGrants {
    room_join: true,
    room: room_name,
    ..Default::default()
  });

  if let Some(room_config) = body.get('room_config') {
    token = token.with_room_config(room_config)
  };

  // Participant related fields. 
  // `participantIdentity` will be available as LocalParticipant.identity
  // within the livekit-client SDK
  token = token
      .with_identity(body.get("participant_identity").unwrap_or("quickstart-identity"))
      .with_name(body.get("participant_name").unwrap_or("quickstart-username"));
  if let Some(participant_metadata) = body.get('participant_metadata') {
      token = token.with_metadata(participant_metadata)
  };
  if let Some(participant_attributes) = body.get('participant_attributes') {
      token = token.with_attributes(participant_attributes)
  };

  token.to_jwt()
}

```

---

**PHP**:

```php
// Left as an exercise to the reader: Make sure this is running on port 3000.

// Get the incoming JSON request body
$rawBody = file_get_contents('php://input');
$body = json_decode($rawBody, true);

// Validate that we have valid JSON
if (json_last_error() !== JSON_ERROR_NONE) {
  http_response_code(400);
  echo json_encode(['error' => 'Invalid JSON in request body']);
  exit;
}

// Define the token options.
$tokenOptions = (new AccessTokenOptions())
  // Participant related fields. 
  // `participantIdentity` will be available as LocalParticipant.identity
  // within the livekit-client SDK
  ->setIdentity($body['participant_identity'] ?? 'quickstart-identity')
  ->setName($body['participant_name'] ?? 'quickstart-username');

if (!empty($body["participant_metadata"])) {
  $tokenOptions = $tokenOptions->setMetadata($body["participant_metadata"]);
}
if (!empty($body["participant_attributes"])) {
  $tokenOptions = $tokenOptions->setAttributes($body["participant_attributes"]);
}

// Define the video grants.
$roomName = $body['room_name'] ?? 'quickstart-room';
$videoGrant = (new VideoGrant())
  ->setRoomJoin()
  // If this room doesn't exist, it'll be automatically created when
  // the first participant joins
  ->setRoomName($roomName);


$token = (new AccessToken(getenv('LIVEKIT_API_KEY'), getenv('LIVEKIT_API_SECRET')))
  ->init($tokenOptions)
  ->setGrant($videoGrant)

if (!empty($body["room_config"])) {
  $token = $token->setRoomConfig($body["room_config"]);
}

echo json_encode([ 'server_url' => os.getenv('LIVEKIT_URL'), 'participant_token' => token->toJwt() ]);

```
4. Load the environment variables and run the server:

**Go**:

```shell
$ source development.env
$ go run server.go

```

---

**Node.js**:

```shell
$ source development.env
$ node server.js

```

---

**Ruby**:

```shell
$ source development.env
$ ruby server.rb

```

---

**Python**:

```shell
$ source development.env
$ python server.py

```

---

**Rust**:

```shell
$ source development.env
$ cargo r src/main.rs

```

---

**PHP**:

```shell
$ source development.env
$ php server.php

```

> ℹ️ **Note**
> 
> See the [Tokens overview](https://docs.livekit.io/frontends/authentication/tokens.md) page for more information on how to generate tokens with custom permissions.
5. Consume your endpoint with a `TokenSource`:

**JavaScript**:

```typescript
import { Room, TokenSource } from 'livekit-client';

// Create the TokenSource
const tokenSource = TokenSource.endpoint("http://localhost:3000/getToken");

// Generate a new token
const { serverUrl, participantToken } = await tokenSource.fetch({ roomName: "room name to join" });

// Use the generated token to connect to a room
const room = new Room();
room.connect(serverUrl, participantToken);

```

---

**React**:

```typescript
import { TokenSource } from 'livekit-client';
import { useSession, SessionProvider } from '@livekit/components-react';

// Create the TokenSource
const tokenSource = TokenSource.endpoint("http://localhost:3000/getToken");

export const MyPage = () => {
  const session = useSession(tokenSource, { roomName: "room name to join" });

  // Start the session when the component mounts, and end the session when the component unmounts
  useEffect(() => {
    session.start();
    return () => {
      session.end();
    };
  }, []);

  return (
    <SessionProvider session={session}>
      <MyComponent />
    </SessionProvider>
  )
}

export const MyComponent = () => {
  // Access the session available via the context to build your app
  // ie, show a list of all camera tracks:
  const cameraTracks = useTracks([Track.Source.Camera], {onlySubscribed: true});
  return (
    <>
      {cameraTracks.map((trackReference) => {
        return (
          <VideoTrack {...trackReference} />
        )
      })}
    </>
  )
}

```

---

**Swift**:

```swift
import LiveKitComponents

@main
struct SessionApp: App {
    let session = Session(tokenSource: EndpointTokenSource(url: "http://localhost:3000/getToken"))

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(session)
                .alert(session.error?.localizedDescription ?? "Error", isPresented: .constant(session.error != nil)) {
                    Button(action: session.dismissError) { Text("OK") }
                }
                .alert(session.agent.error?.localizedDescription ?? "Error", isPresented: .constant(session.agent.error != nil)) {
                    AsyncButton(action: session.end) { Text("OK") }
                }
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var session: Session
    @State var message = ""
    
    var body: some View {
        if session.isConnected {
            AsyncButton(action: session.end) {
                Text("Disconnect")
            }
            
            Text(String(describing: session.agent.agentState))
        } else {
            AsyncButton(action: session.start) {
                Text("Connect")
            }
        }
    }
}

```

---

**Android**:

```kotlin
val tokenRequestOptions = remember { TokenRequestOptions(roomName = "customRoom") }
val tokenSource = remember {
    TokenSource.fromEndpoint(URL("http://localhost:3000/getToken")).cached()
}
val session = rememberSession(
    tokenSource = tokenSource,
    options = SessionOptions(
        tokenRequestOptions = tokenRequestOptions
    )
)

Column {
    SessionScope(session = session) { session ->
        val coroutineScope = rememberCoroutineScope()
        var shouldConnect by remember { mutableStateOf(false) }

        LaunchedEffect(shouldConnect) {
            if (shouldConnect) {

                val result = session.start()

                // Handle if the session fails to connect.
                if (result.isFailure) {
                    Toast.makeText(context, "Error connecting to the session.", Toast.LENGTH_SHORT).show()
                    shouldConnect = false
                }
            } else {
                session.end()
            }
        }
        Button(onClick = { shouldConnect = !shouldConnect }) {
            Text(
                if (shouldConnect) {
                    "Disconnect"
                } else {
                    "Connect"
                }
            )
        }
    }
}

```

---

**Flutter**:

```dart
import 'package:livekit_client/livekit_client.dart' as sdk;

final tokenSource = sdk.EndpointTokenSource(url: "http://localhost:3000/getToken");
final session = sdk.Session.fromConfigurableTokenSource(
  tokenSource,
   const TokenRequestOptions()
);
 
/* ... */
 
await session.start();
 
// Use session to further build out your application.

```

---

**React Native**:

```typescript
import { TokenSource } from 'livekit-client';
import { useSession, SessionProvider } from '@livekit/components-react';

// Create the TokenSource
const tokenSource = TokenSource.endpoint("http://localhost:3000/getToken");

export const MyPage = () => {
  const session = useSession(tokenSource, { roomName: "room name to join" });

  // Start the session when the component mounts, and end the session when the component unmounts
  useEffect(() => {
    session.start();
    return () => {
      session.end();
    };
  }, []);

  return (
    <SessionProvider session={session}>
      {/* render the rest of your application here */}
    </SessionProvider>
  )
}

```

---

This document was rendered at 2026-02-03T03:25:09.496Z.
For the latest version of this document, see [https://docs.livekit.io/frontends/authentication/tokens/endpoint.md](https://docs.livekit.io/frontends/authentication/tokens/endpoint.md).

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