# Livekit
> LiveKit docs › Get Started › About LiveKit
---
# Source: https://docs.livekit.io/intro/about.md
LiveKit docs › Get Started › About LiveKit
---
# About LiveKit
> An overview of the LiveKit ecosystem.
## What is LiveKit?
LiveKit is an open source framework and cloud platform for building voice, video, and physical AI agents. It provides the tools you need to build agents that interact with users in realtime over audio, video, and data streams. Agents run on the LiveKit server, which supplies the low-latency infrastructure—including transport, routing, synchronization, and session management—built on a production-grade WebRTC stack. This architecture enables reliable and performant agent workloads.
### About WebRTC
The internet's core protocols weren't designed for realtime media. Hypertext Transfer Protocol (HTTP) is optimized for request-response communication, which is effective for the web's client-server model, but not for continuous audio and video streams. Historically, developers building realtime media applications had to work directly with the complexities of WebRTC.
WebRTC is a browser-native technology for transmitting audio and video in realtime. Unlike general-purpose transports such as websockets, WebRTC is optimized for media delivery, providing efficient codecs and automatically adapting to unreliable network conditions. Because all major browsers support WebRTC, it works consistently across platforms. LiveKit manages the operational and scaling challenges of WebRTC and extends its use to mobile applications, backend services, and telephony integrations.
## Why use LiveKit?
LiveKit differentiates itself through several key advantages:
**Build faster with high-level abstractions:** Use the LiveKit Agents framework to quickly build production-ready AI agents with built-in support for speech processing, turn-taking, multimodal events, and LLM integration. When you need custom behavior, access lower-level WebRTC primitives for complete control.
**Write once, deploy everywhere:** Both human clients and AI agents use the same SDKs and APIs, so you can write agent logic once and deploy it across Web, iOS, Android, Flutter, Unity, and backend environments. Agents and clients interact seamlessly regardless of platform.
**Focus on building, not infrastructure:** LiveKit handles the operational complexity of WebRTC so developers can focus on building agents. Choose between fully managed LiveKit Cloud or self-hosted deployment—both offer identical APIs and core capabilities.
**Connect to any system:** Extend LiveKit with egress, ingress, telephony, and server APIs to build end-to-end workflows that span web, mobile, phone networks, and physical devices.
## What can I build?
LiveKit supports a wide range of applications:
- **AI assistants:** Multimodal AI assistants and avatars that interact through voice, video, and text.
- **Video conferencing:** Secure, private meetings for teams of any size.
- **Interactive livestreaming:** Broadcast to audiences with realtime engagement.
- **Customer service:** Flexible and observable web, mobile, and telephone support options.
- **Healthcare:** HIPAA-compliant telehealth with AI and humans in the loop.
- **Robotics:** Integrate realtime video and powerful AI models into real-world devices.
LiveKit provides the realtime foundation—low latency, scalable performance, and flexible tools—needed to run production-ready AI experiences.
## How does LiveKit work?
LiveKit's architecture consists of several key components that work together.
### LiveKit server
LiveKit server is an open source [WebRTC](#webrtc) Selective Forwarding Unit (SFU) that orchestrates realtime communication between participants and agents. The server handles signaling, network address translation (NAT) traversal, RTP routing, adaptive degradation, and quality-of-service controls. You can use [LiveKit Cloud](https://livekit.io/cloud), a fully managed cloud service, or self-host LiveKit server on your own infrastructure.
### LiveKit Agents framework
The [LiveKit Agents framework](https://docs.livekit.io/agents.md) provides high-level tools for building AI agents, including speech processing, turn-taking, multimodal events, and LLM integration. Agents join rooms as participants and can process incoming media, synthesize output, and interact with users through the same infrastructure that powers all LiveKit applications. For lower-level control over raw media tracks, you can use the SDKs and clients.
### SDKs and clients
Native SDKs for Web, iOS, Android, Flutter, Unity, and backend environments provide a consistent programming model. Both human clients and AI agents use the same SDKs to join rooms, publish and subscribe to media tracks, and exchange data.
### Integration services
LiveKit provides additional services that enable you to connect to any system. LiveKit supports recording and streaming (Egress), external media streams (Ingress), integration with SIP, PSTN, and other communication systems (Telephony), and server APIs for programmatic session management.
## How can I learn more?
This documentation site is organized into several main sections:
- [**Introduction:**](https://docs.livekit.io/intro/basics.md) Start here to understand LiveKit's core concepts and get set up.
- [**Build Agents:**](https://docs.livekit.io/agents.md) Learn how to build AI agents using the LiveKit Agents framework.
- [**Agent Frontends:**](https://docs.livekit.io/frontends.md) Build web, mobile, and hardware interfaces for agents.
- [**Telephony:**](https://docs.livekit.io/telephony.md) Connect agents to phone networks and traditional communication systems.
- [**WebRTC Transport:**](https://docs.livekit.io/transport.md) Deep dive into WebRTC concepts and low-level transport details.
- [**Manage & Deploy:**](https://docs.livekit.io/deploy.md) Deploy and manage LiveKit agents and infrastructure, and learn how to test, evaluate, and observe agent performance.
- [**Reference:**](https://docs.livekit.io/reference.md) API references, SDK documentation, and component libraries.
Use the sidebar navigation to explore topics within each section. Each page includes code examples, guides, and links to related concepts. Start with [Understanding LiveKit overview](https://docs.livekit.io/intro/basics.md) to learn core concepts, then follow the guides that match your use case.
---
This document was rendered at 2026-02-03T03:24:50.873Z.
For the latest version of this document, see [https://docs.livekit.io/intro/about.md](https://docs.livekit.io/intro/about.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/sip/accepting-calls-twilio-voice.md
LiveKit docs › Accepting calls › Inbound calls with Twilio Voice
---
# Inbound calls with Twilio Voice
> How to use LiveKit SIP with TwiML and Twilio conferencing.
## Inbound calls with Twilio programmable voice
Accept inbound calls using Twilio programmable voice. All you need is an inbound trunk and a dispatch rule created using the LiveKit CLI (or SDK) to accept calls and route callers to LiveKit rooms. The following steps guide you through the process.
> ℹ️ **Unsupported features**
>
> This method doesn't support [SIP REFER](https://docs.livekit.io/sip/transfer-cold.md) or outbound calls. To use these features, switch to Elastic SIP Trunking. For details, see the [Configuring Twilio SIP trunks](https://docs.livekit.io/sip/quickstarts/configuring-twilio-trunk.md) quickstart.
### Step 1. Purchase a phone number from Twilio
If you don't already have a phone number, see [How to Search for and Buy a Twilio Phone Number From Console](https://help.twilio.com/articles/223135247-How-to-Search-for-and-Buy-a-Twilio-Phone-Number-from-Console).
### Step 2. Set up a TwiML Bin
> ℹ️ **Other approaches**
>
> This guide uses TwiML Bins, but you can also return TwiML via another mechanism, such as a webhook.
TwiML Bins are a simple way to test TwiML responses. Use a TwiML Bin to redirect an inbound call to LiveKit.
To create a TwiML Bin, follow these steps:
1. Navigate to your [TwiML Bins](https://console.twilio.com/us1/develop/twiml-bins/twiml-bins?frameUrl=/console/twiml-bins) page.
2. Create a TwiML Bin and add the following contents:
```xml
sip:@%{sipHost}%
```
### Step 3. Direct phone number to the TwiML Bin
Configure incoming calls to a specific phone number to use the TwiML Bin you just created:
1. Navigate to the [Manage numbers](https://console.twilio.com/us1/develop/phone-numbers/manage/incoming) page and select the purchased phone number.
2. In the **Voice Configuration** section, edit the **A call comes in** fields. After you select **TwiML Bin**. select the TwiML Bin created in the previous step.
### Step 4. Create a LiveKit inbound trunk
Use the LiveKit CLI to create an [inbound trunk](https://docs.livekit.io/sip/trunk-inbound.md) for the purchased phone number.
1. Create an `inbound-trunk.json` file with the following contents. Replace the phone number and add a `username` and `password` of your choosing:
```json
{
"trunk": {
"name": "My inbound trunk",
"auth_username": "",
"auth_password": ""
}
}
```
> ℹ️ **Note**
>
> Be sure to use the same username and password that's specified in the TwiML Bin.
2. Use the CLI to create an inbound trunk:
```shell
lk sip inbound create inbound-trunk.json
```
### Step 5. Create a dispatch rule to place each caller into their own room.
Use the LiveKit CLI to create a [dispatch rule](https://docs.livekit.io/sip/dispatch-rule.md) that places each caller into individual rooms named with the prefix `call`.
1. Create a `dispatch-rule.json` file with the following contents:
```json
{
"dispatch_rule":
{
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
}
}
}
```
2. Create the dispatch rule using the CLI:
```shell
lk sip dispatch create dispatch-rule.json
```
### Testing with an agent
Follow the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md) to create an agent that responds to incoming calls. Then call the phone number and your agent should pick up the call.
## Connecting to a Twilio phone conference
You can bridge Twilio conferencing to LiveKit via SIP, allowing you to add agents and other LiveKit clients to an existing Twilio conference. This requires the following setup:
- [Twilio conferencing](https://www.twilio.com/docs/voice/conference).
- LiveKit [inbound trunk](https://docs.livekit.io/sip/trunk-inbound.md).
- LiveKit [voice AI agent](https://docs.livekit.io/agents/start/voice-ai.md).
The example in this section uses [Node](https://nodejs.org) and the [Twilio Node SDK](https://www.twilio.com/docs/libraries).
### Step 1. Set Twilio environment variables
You can find these values in your [Twilio Console](https://console.twilio.com/):
```shell
export TWILIO_ACCOUNT_SID=
export TWILIO_AUTH_TOKEN=
```
### Step 2. Bridge a Twilio conference and LiveKit SIP
Create a `bridge.js` file and update the `twilioPhoneNumber`, `conferenceSid`, `sipHost`, and `from` field for the API call in the following code:
> ℹ️ **Note**
>
> If you're signed in to [LiveKit Cloud](https://cloud.livekit.io), your sip host is filled in below.
```typescript
import twilio from 'twilio';
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const twilioClient = twilio(accountSid, authToken);
/**
* Phone number bought from Twilio that is associated with a LiveKit trunk.
* For example, +14155550100.
* See https://docs.livekit.io/sip/quickstarts/configuring-twilio-trunk/
*/
const twilioPhoneNumber = '';
/**
* SIP host is available in your LiveKit Cloud project settings.
* This is your project domain without the leading "sip:".
*/
const sipHost = '%{sipHost}%';
/**
* The conference SID from Twilio that you want to add the agent to. You
* likely want to obtain this from your conference status callback webhook handler.
* The from field must contain the phone number, client identifier, or username
* portion of the SIP address that made this call.
* See https://www.twilio.com/docs/voice/api/conference-participant-resource#request-body-parameters
*/
const conferenceSid = '';
await twilioClient.conferences(conferenceSid).participants.create({
from: '',
to: `sip:${twilioPhoneNumber}@${sipHost}`,
});
```
### Step 3. Execute the file
When you run the file, it bridges the Twilio conference to a new LiveKit session using the previously configured dispatch rule. This allows you to automatically [dispatch an agent](https://docs.livekit.io/agents/server/agent-dispatch.md) to the Twilio conference.
```shell
node bridge.js
```
---
This document was rendered at 2025-11-18T23:55:20.650Z.
For the latest version of this document, see [https://docs.livekit.io/sip/accepting-calls-twilio-voice.md](https://docs.livekit.io/sip/accepting-calls-twilio-voice.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/telephony/accepting-calls.md
LiveKit docs › Accepting calls › Overview
---
# Accepting calls overview
> An overview of accepting inbound calls with LiveKit telephony.
## Overview
Accept inbound calls and route them to LiveKit rooms. Configure inbound trunks, dispatch rules, and workflows to handle incoming calls and connect callers with agents or other participants.
> ℹ️ **Simplified inbound calling**
>
> LiveKit Phone Numbers provide a simple setup process that only requires purchasing a phone number and creating a dispatch rule. To learn more, see [LiveKit Phone Numbers](https://docs.livekit.io/telephony/start/phone-numbers.md).
## Accepting calls components
Set up inbound call handling with trunks, dispatch rules, and provider-specific configurations.
| Component | Description | Use cases |
| **Workflow & setup** | Overview of the inbound call workflow, from receiving an INVITE request to creating SIP participants and routing to rooms. | Understanding call flow, setting up inbound call handling, and learning how dispatch rules route calls to rooms. |
| **Inbound trunk** | Configure inbound trunks to accept incoming calls from SIP providers, with options to restrict calls by IP address or phone number. | Accepting calls from SIP providers, restricting inbound calls to specific sources, and configuring trunk authentication. |
| **Dispatch rule** | Create dispatch rules that control how callers are added as SIP participants and routed to rooms, including agent dispatch configuration. | Routing calls to specific rooms, configuring agent dispatch, and customizing how SIP participants join rooms. |
| **Twilio Voice integration** | Accept inbound calls using Twilio programmable voice with TwiML and Twilio conferencing integration. | Twilio Voice integration, TwiML-based call routing, and Twilio conferencing features. |
## In this section
Read more about accepting calls.
- **[Workflow & setup](https://docs.livekit.io/telephony/accepting-calls/workflow-setup.md)**: Overview of the inbound call workflow and setup process.
- **[Inbound trunk](https://docs.livekit.io/telephony/accepting-calls/inbound-trunk.md)**: Create and configure inbound trunks to accept incoming calls from SIP providers.
- **[Dispatch rule](https://docs.livekit.io/telephony/accepting-calls/dispatch-rule.md)**: Configure dispatch rules to route calls to rooms.
- **[Twilio Voice integration](https://docs.livekit.io/telephony/accepting-calls/inbound-twilio.md)**: Accept inbound calls using Twilio programmable voice.
---
This document was rendered at 2026-02-03T03:25:12.094Z.
For the latest version of this document, see [https://docs.livekit.io/telephony/accepting-calls.md](https://docs.livekit.io/telephony/accepting-calls.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/deploy/admin.md
LiveKit docs › Administration › Overview
---
# Administration overview
> Manage your project regions, firewalls, and quotas.
## Overview
Manage your LiveKit Cloud project with administration tools for configuring access controls, monitoring usage, and managing billing.
## Administration topics
Learn more about managing your LiveKit deployment with these topics.
| Component | Description | Use cases |
| **Regions** | Configure and manage regional traffic and agent deployments for improved latency and redundancy, or to comply with local regulatory restrictions and meet data residency requirements. | Deploying agents in multiple regions, optimizing latency, managing regional deployments, and regulatory compliance. |
| **Sandbox** | Rapidly prototype your apps with hosted components that are integrated with the CLI and ready to work with your LiveKit account. | Prototyping voice assistants, testing ideas, and sharing demos without deployment setup. |
| **Configuring firewalls** | Configure firewall rules to control access to your LiveKit Cloud rooms and restrict connections based on IP addresses or ranges. | Securing rooms, restricting access by location, and implementing IP-based access controls. |
| **Quotas & limits** | Understand LiveKit Cloud quotas, limits, and how usage is calculated across different plans and features. | Planning capacity, understanding billing, and optimizing resource usage. |
| **Billing** | Manage your LiveKit Cloud billing, view usage, update payment methods, and understand how charges are calculated. | Managing subscriptions, viewing usage, and understanding costs. |
| **Analytics API** | Access usage, performance, and quality metrics programmatically through the Analytics API for integration with your own systems. | Building custom dashboards, monitoring usage, and integrating metrics into existing tools. |
## In this section
Manage your LiveKit Cloud project settings and configuration.
- **[Regions](https://docs.livekit.io/deploy/admin/regions.md)**: Configure and manage regional traffic and agent deployments.
- **[Sandbox](https://docs.livekit.io/deploy/admin/sandbox.md)**: Rapidly prototype your apps with hosted components.
- **[Configuring firewalls](https://docs.livekit.io/deploy/admin/firewall.md)**: Configure firewall rules to control access to your rooms.
- **[Quotas & limits](https://docs.livekit.io/deploy/admin/quotas-and-limits.md)**: Understand quotas, limits, and usage calculations.
- **[Billing](https://docs.livekit.io/deploy/admin/billing.md)**: Manage your LiveKit Cloud billing and subscriptions.
- **[Analytics API](https://docs.livekit.io/deploy/admin/analytics-api.md)**: Access usage and performance metrics programmatically.
---
This document was rendered at 2026-02-03T03:25:23.132Z.
For the latest version of this document, see [https://docs.livekit.io/deploy/admin.md](https://docs.livekit.io/deploy/admin.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/media/advanced.md
LiveKit docs › Media › Codecs & more
---
# Codecs and more
> Advanced audio and video topics.
## Video codec support
LiveKit supports multiple video codecs to suit different application needs:
- H.264
- VP8
- VP9 (including SVC)
- AV1 (including SVC)
Scalable Video Coding (SVC) is a feature of newer codecs like VP9 and AV1 that provides the following benefits:
- Improves bitrate efficiency by letting higher quality layers leverage information from lower quality layers.
- Enables instant layer switching without waiting for keyframes.
- Incorporates multiple spatial (resolution) and temporal (frame rate) layers in a single stream.
When using VP9 or AV1, SVC is automatically activated with L3T3_KEY `scalabilityMode` (three spatial and temporal layers).
You can specify which codec to use when connecting to a room. To learn more, see the examples in the following sections.
## Video quality presets
LiveKit provides preset resolutions when creating video tracks. These presets include common resolutions and aspect ratios:
- h720 (1280x720)
- h540 (960x540)
- h360 (640x360)
- h180 (320x180)
The presets also include recommended bitrates and framerates for optimal quality. You can use these presets or define custom parameters based on your needs.
**React**:
```js
const localParticipant = useLocalParticipant();
const audioTrack = await createLocalAudioTrack();
const audioPublication = await localParticipant.publishTrack(audioTrack, {
red: false,
});
```
---
**JavaScript**:
```js
const audioTrack = await createLocalAudioTrack();
const audioPublication = await room.localParticipant.publishTrack(audioTrack, {
red: false,
});
```
## Video track configuration
LiveKit provides extensive control over video track settings through two categories:
- Capture settings: Device selection and capabilities (resolution, framerate, facing mode).
- Publish settings: Encoding parameters (bitrate, framerate, simulcast layers).
Here's how to configure these settings:
**JavaScript**:
```typescript
// Room defaults
const room = new Room({
videoCaptureDefaults: {
deviceId: '',
facingMode: 'user',
resolution: {
width: 1280,
height: 720,
frameRate: 30,
},
},
publishDefaults: {
videoEncoding: {
maxBitrate: 1_500_000,
maxFramerate: 30,
},
videoSimulcastLayers: [
{
width: 640,
height: 360,
encoding: {
maxBitrate: 500_000,
maxFramerate: 20,
},
},
{
width: 320,
height: 180,
encoding: {
maxBitrate: 150_000,
maxFramerate: 15,
},
},
],
},
});
// Individual track settings
const videoTrack = await createLocalVideoTrack({
facingMode: 'user',
resolution: VideoPresets.h720,
});
const publication = await room.localParticipant.publishTrack(videoTrack);
```
---
**Swift**:
```swift
// Room defaults
var room = Room(
delegate: self,
roomOptions: RoomOptions(
defaultCameraCaptureOptions: CameraCaptureOptions(
position: .front,
dimensions: .h720_169,
fps: 30,
),
defaultVideoPublishOptions: VideoPublishOptions(
encoding: VideoEncoding(
maxBitrate: 1_500_000,
maxFps: 30,
),
simulcastLayers: [
VideoParameters.presetH180_169,
VideoParameters.presetH360_169,
]
),
)
)
// Individual track
let videoTrack = try LocalVideoTrack.createCameraTrack(options: CameraCaptureOptions(
position: .front,
dimensions: .h720_169,
fps: 30,
))
let publication = localParticipant.publishVideoTrack(track: videoTrack)
```
## Video simulcast
Simulcast enables publishing multiple versions of the same video track with different bitrate profiles. This allows LiveKit to dynamically forward the most suitable stream based on each recipient's bandwidth and preferred resolution.
LiveKit will automatically select appropriate layers when it detects bandwidth constraints, upgrading to higher resolutions as conditions improve.
Simulcast is enabled by default in all LiveKit SDKs and can be disabled in publish settings if needed.
## Dynacast
Dynamic broadcasting (Dynacast) automatically pauses video layer publication when they aren't being consumed by subscribers. For simulcasted video, if subscribers only use medium and low-resolution layers, the high-resolution publication is paused.
To enable this bandwidth optimization:
**JavaScript**:
```typescript
const room = new Room({
dynacast: true
});
```
---
**Swift**:
```swift
let room = Room(
delegate: self,
roomOptions: RoomOptions(
dynacast: true
)
)
```
---
**Android**:
```kotlin
val options = RoomOptions(
dynacast = true
)
var room = LiveKit.create(
options = options
)
```
---
**Flutter**:
```dart
var room = Room(
roomOptions: RoomOptions(
dynacast: true
),
)
```
With SVC codecs (VP9 and AV1), Dynacast can only pause entire streams, not individual layers, due to SVC encoding characteristics.
## Hi-fi audio
For high-quality audio streaming, LiveKit provides several configuration options to optimize audio quality.
#### Recommended hi-fi settings
For high-quality audio, we provide a preset with our recommended settings:
**React**:
```js
const localParticipant = useLocalParticipant();
const audioTrack = await createLocalAudioTrack({
channelCount: 2,
echoCancellation: false,
noiseSuppression: false,
});
const audioPublication = await localParticipant.publishTrack(audioTrack, {
audioPreset: AudioPresets.musicHighQualityStereo,
dtx: false,
red: false,
});
```
---
**JavaScript**:
```js
const audioTrack = await createLocalAudioTrack({
channelCount: 2,
echoCancellation: false,
noiseSuppression: false,
});
const audioPublication = await room.localParticipant.publishTrack(audioTrack, {
audioPreset: AudioPresets.musicHighQualityStereo,
dtx: false,
red: false,
});
```
#### Maximum quality settings
LiveKit supports audio tracks up to 510kbps stereo - the highest theoretical quality possible. Note that the listener's playback stack may resample the audio, so actual playback quality may be lower than published quality. For comparison, 256kbps AAC-encoded audio is considered high quality for music streaming services like Spotify.
**React**:
```js
const localParticipant = useLocalParticipant();
const audioTrack = await createLocalAudioTrack({
channelCount: 2,
echoCancellation: false,
noiseSuppression: false,
});
const audioPublication = await localParticipant.publishTrack(audioTrack, {
audioBitrate: 510000,
dtx: false,
red: false,
});
```
---
**JavaScript**:
```js
const audioTrack = await createLocalAudioTrack({
channelCount: 2,
echoCancellation: false,
noiseSuppression: false,
});
const audioPublication = await room.localParticipant.publishTrack(audioTrack, {
audioBitrate: 510000,
dtx: false,
red: false,
});
```
If you configure a high bitrate, we recommend testing under real-world conditions to find what settings work best for your use case.
## Audio RED
REDundant Encoding is a technique to improve audio quality by sending multiple copies of the same audio data in different packets. This is useful in lossy networks where packets may be dropped. The receiver can then use the redundant packets to reconstruct the original audio packet.
Redundant encoding increases bandwidth usage in order to achieve higher audio quality. LiveKit recommends enabling this feature because audio glitches are so distracting that the tradeoff is almost always worth it. If your use case prioritizes bandwidth and can tolerate audio glitches, you can disable RED.
#### Disabling Audio RED when publishing
You can disable Audio RED when publishing new audio tracks:
**React**:
```js
const localParticipant = useLocalParticipant();
const audioTrack = await createLocalAudioTrack();
const audioPublication = await localParticipant.publishTrack(audioTrack, {
red: false,
});
```
---
**JavaScript**:
```js
const audioTrack = await createLocalAudioTrack();
const audioPublication = await room.localParticipant.publishTrack(audioTrack, {
red: false,
});
```
---
**Swift**:
```swift
let audioTrack = LocalAudioTrack.createTrack()
let audioPublication = room.localParticipant.publish(audioTrack: audioTrack, options: AudioPublishOptions(red: false))
```
---
**Android**:
```kotlin
val audioTrack = localParticipant.createAudioTrack()
coroutineScope.launch {
val publication = localParticipant.publishAudioTrack(
track = localAudioTrack,
red = false
)
}
```
---
This document was rendered at 2026-02-03T03:25:16.669Z.
For the latest version of this document, see [https://docs.livekit.io/transport/media/advanced.md](https://docs.livekit.io/transport/media/advanced.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/reference/other/agent-cli.md
LiveKit docs › Other › Agent CLI reference
---
# Agent CLI reference
> Reference for the LiveKit Cloud agent deployment commands in the LiveKit CLI.
## Overview
The LiveKit CLI is the primary interface for managing agents [deployed to LiveKit Cloud](https://docs.livekit.io/deploy/agents.md). All agent commands are prefixed with `lk agent`.
For instructions on installing the CLI, see the LiveKit CLI [Getting started](https://docs.livekit.io/intro/basics/cli.md) guide.
```shell
lk agent [command] [command options] [working-dir]
```
> 🔥 **CLI version requirement**
>
> Update the CLI regularly to ensure you have the latest version. You must have an up-to-date CLI to deploy and manage agents. See [Update the CLI](https://docs.livekit.io/intro/basics/cli/start.md#updates) for instructions.
### Working directory
The default working directory for each command is the current directory. You can override the working directory by passing it as the first regular argument.
For example, this command deploys the agent in the current directory:
```shell
lk agent deploy
```
While this command deploys the agent in the named directory:
```shell
lk agent deploy ~/my-agent
```
### Project and agent identification
If a `livekit.toml` file is present in the working directory, the CLI uses the project and agent configuration from that file by default.
If no `livekit.toml` file is present, the CLI uses the [default project](https://docs.livekit.io/intro/basics/cli/projects.md#set-default). You must also specify the agent ID with the `--id` flag for commands that operate on an existing agent.
## Agent subcommands
The following agent subcommands are available:
### Create
Create a new agent using configuration in the working directory and optional secrets. You must not already have a configuration file for the agent (default name is `livekit.toml`). If no `Dockerfile` is present, the CLI creates one for you.
```shell
lk agent create [options] [working-dir]
```
Options for `create`:
- `--region REGION`: [Region code](https://docs.livekit.io/deploy/admin/regions/agent-deployment.md#regions) for the agent deployment. If no value is provided, the CLI prompts you to select a region.
- `--secrets KEY=VALUE [--secrets KEY=VALUE]`: Comma-separated `KEY=VALUE` secrets. Injected as environment variables into the agent. Individual values take precedence over values in `--secrets-file`, in the case of duplicate keys.
- `--secrets-file FILE`: File containing secret `KEY=VALUE` pairs, one per line. Injected as environment variables into the agent.
- `--secret-mount FILE`: Path to a file to load as a [file-mounted secret](https://docs.livekit.io/deploy/agents/secrets.md#file-mounted-secrets) in the agent container.
- `--config FILE`: Name of the configuration file to create for the new deployment. If no value is provided, the default name is `livekit.toml`.
- `--silent`: Do not prompt for interactive confirmation. Default: `false`.
#### Examples
Create and [deploy a new agent](https://docs.livekit.io/agents/ops/deployment.md#create) to `us-east` from the current directory, providing secrets inline and via file:
```shell
lk agent create \
--region us-east \
--secrets OPENAI_API_KEY=sk-xxx,GOOGLE_API_KEY=ya29.xxx \
--secrets-file ./secrets.env \
.
```
### Deploy
[Build and deploy](https://docs.livekit.io/deploy/agents/builds.md) a new agent version based on the working directory. You must have a `livekit.toml` and `Dockerfile` in the working directory.
```shell
lk agent deploy [options] [working-dir]
```
Options for `deploy`:
- `--secrets KEY=VALUE [--secrets KEY=VALUE]`: Comma-separated `KEY=VALUE` secrets. Injected as environment variables into the agent. Takes precedence over `--secrets-file`.
- `--secrets-file FILE`: File containing secret `KEY=VALUE` pairs, one per line. Injected as environment variables into the agent.
- `--secret-mount FILE`: Path to a file to load as a [file-mounted secret](https://docs.livekit.io/deploy/agents/secrets.md#file-mounted-secrets) in the agent container.
#### Examples
Deploy a new version from the current directory:
```shell
lk agent deploy
```
Deploy a new version from the subdirectory `./agent`:
```shell
lk agent deploy ./agent
```
### Status
Show the current status of the specified agent:
```shell
lk agent status [options] [working-dir]
```
Options for `status`:
- `--id AGENT_ID`: Agent ID. By default, uses the ID found in the `livekit.toml` file in the working directory.
#### Examples
Show the status of the agent in the current directory:
```shell
lk agent status
```
Show the status of the agent with the ID `CA_MyAgentId`:
```shell
lk agent status --id CA_MyAgentId
```
Example output:
```shell
Using default project [my-project]
Using agent [CA_MyAgentId]
┌─────────────────┬────────────────┬─────────┬──────────┬────────────┬─────────┬───────────┬──────────────────────┐
│ ID │ Version │ Region │ Status │ CPU │ Mem │ Replicas │ Deployed At │
├─────────────────┼────────────────┼─────────┼──────────┼────────────┼─────────┼───────────┼──────────────────────┤
│ CA_MyAgentId │ 20250809003117 │ us-east │ Sleeping │ 0m / 2000m │ 0 / 4GB │ 1 / 1 / 1 │ 2025-08-09T00:31:48Z │
└─────────────────┴────────────────┴─────────┴──────────┴────────────┴─────────┴───────────┴──────────────────────┘
```
#### Status values
The `status` field indicates the current state of the agent.
##### Normal statuses
These indicate that the agent is running or deploying normally.
| Agent status | Description |
| Setting Up | Agent created; waiting for provisioning. |
| Building | Building images for a new version. |
| Running | Agent is running and serving users. |
| Updating | Agent is pending update. |
| Scheduling | Agent is being deployed. |
| Deleting | Agent is pending delete. |
##### Sleep
Agents on certain plans may be scaled down to zero active instances. See [cold start](https://docs.livekit.io/agents/ops/deployment.md#cold-start) for more info.
| Agent status | Description |
| Sleeping | Agent has been scaled down. |
| Waking | Agent is scaling back up to serve a new user. |
##### Errors
These indicate that the agent is in an error state.
| Agent status | Description |
| Error | Agent program exited with a non-zero error code. |
| CrashLoop | Agent pod is crash looping. |
| Build Failed | Latest build failed. |
| Server Error | LiveKit Cloud Agents infrastructure error (not customer-related). See the live [Status page](https://status.livekit.io) for more info. |
| Deleted | Agent has been deleted. |
| Suspended | Project suspended for suspicious behavior. |
### Update
Update secrets for an existing agent. This command restarts the agent servers, but does not interrupt any active sessions.
```shell
lk agent update [options] [working-dir]
```
Options for `update`:
- `--secrets KEY=VALUE [--secrets KEY=VALUE]`: Comma-separated `KEY=VALUE` secrets. Injected as environment variables into the agent. Takes precedence over `--secrets-file`.
- `--secrets-file FILE`: File containing secret `KEY=VALUE` pairs, one per line. Injected as environment variables into the agent.
- `--secret-mount FILE`: Path to a file to load as a [file-mounted secret](https://docs.livekit.io/deploy/agents/secrets.md#file-mounted-secrets) in the agent container.
- `--id AGENT_ID`: Agent ID. By default, uses the ID found in the `livekit.toml` file in the working directory.
#### Examples
Update secrets and restart the agent:
```shell
lk agent update \
--secrets OPENAI_API_KEY=sk-new
```
### Restart
Restart the agent server pool for the specified agent. This command does not interrupt any active sessions.
```shell
lk agent restart [options] [working-dir]
```
Options for `restart`:
- `--id AGENT_ID`: Agent ID. By default, uses the ID found in the `livekit.toml` file in the working directory.
#### Examples
```shell
lk agent restart --id CA_MyAgentId
```
### Rollback
[Rollback](https://docs.livekit.io/agents/ops/deployment.md#rolling-back) the specified agent to a prior version:
```shell
lk agent rollback [options] [working-dir]
```
Options for `rollback`:
- `--version string`: Version to roll back to. Defaults to the most recent version prior to the current.
- `--id ID`: Agent ID. If unset and `livekit.toml` is present, uses the ID found there.
#### Examples
Roll back to a specific version:
```shell
lk agent rollback --id CA_MyAgentId --version 20250809003117
```
### Logs
Stream [logs](https://docs.livekit.io/deploy/agents/logs.md) for the specified agent and log type. Also available as `tail`.
```shell
lk agent logs [options] [working-dir]
# or
lk agent tail [options] [working-dir]
```
Options for `logs`/`tail`:
- `--id ID`: Agent ID. If unset and `livekit.toml` is present, uses the ID found there.
- `--log-type string`: Log type to retrieve. Valid values: `deploy`, `build`. Default: `deploy`.
#### Examples
Tail deploy logs:
```shell
lk agent logs --id CA_MyAgentId --log-type deploy
```
### Delete
Delete the specified agent. Also available as `destroy`.
```shell
lk agent delete [options] [working-dir]
# or
lk agent destroy [options] [working-dir]
```
Options for `delete`/`destroy`:
- `--id ID`: Agent ID. If unset and `livekit.toml` is present, uses the ID found there.
#### Examples
```shell
lk agent delete --id CA_MyAgentId
```
### Versions
List versions associated with the specified agent, which can be used to [rollback](https://docs.livekit.io/agents/ops/deployment.md#rollback).
```shell
lk agent versions [options] [working-dir]
```
Options for `versions`:
- `--id ID`: Agent ID. If unset and `livekit.toml` is present, uses the ID found there.
#### Examples
```shell
lk agent versions --id CA_MyAgentId
```
Example output:
```shell
Using default project [my-project]
Using agent [CA_MyAgentId]
┌────────────────┬─────────┬──────────────────────┐
│ Version │ Current │ Deployed At │
├────────────────┼─────────┼──────────────────────┤
│ 20250809003117 │ true │ 2025-08-09T00:31:48Z │
└────────────────┴─────────┴──────────────────────┘
```
### List
List all deployed agents in the current project:
```shell
lk agent list [options]
```
Options for `list`:
- `--id IDs [--id IDs]`: Filter to one or more agent IDs. Repeatable.
- `--project PROJECT_NAME`: The project name to list agents for. By default, use the project from the current `livekit.toml` file or the [default project](https://docs.livekit.io/intro/basics/cli/projects.md#set-default).
#### Examples
```shell
lk agent list
```
Example output:
```shell
Using default project [my-project]
┌─────────────────┬─────────┬────────────────┬──────────────────────┐
│ ID │ Regions │ Version │ Deployed At │
├─────────────────┼─────────┼────────────────┼──────────────────────┤
│ CA_MyAgentId │ us-east │ 20250809003117 │ 2025-08-09T00:31:48Z │
└─────────────────┴─────────┴────────────────┴──────────────────────┘
```
### Secrets
Show the current [secret](https://docs.livekit.io/deploy/agents/secrets.md) keys for the specified agent. Does not include secret values.
```shell
lk agent secrets [options] [working-dir]
```
Options for `secrets`:
- `--id AGENT_ID`: Agent ID. By default, uses the ID found in the `livekit.toml` file in the working directory.
#### Examples
```shell
lk agent secrets --id CA_MyAgentId
```
Example output:
```shell
Using default project [my-project]
Using agent [CA_MyAgentId]
┌────────────────┬──────────────────────┬──────────────────────┐
│ Name │ Created At │ Updated At │
├────────────────┼──────────────────────┼──────────────────────┤
│ OPENAI_API_KEY │ 2025-08-08T23:32:29Z │ 2025-08-09T00:31:10Z │
│ GOOGLE_API_KEY │ 2025-08-08T23:32:29Z │ 2025-08-09T00:31:10Z │
│ HEDRA_API_KEY │ 2025-08-08T23:32:29Z │ 2025-08-09T00:31:10Z │
└────────────────┴──────────────────────┴──────────────────────┘
```
### Update secrets
Update secrets for the specified agent. This command restarts the agent:
```shell
lk agent update-secrets [options] [working-dir]
```
Options for `update-secrets`:
- `--secrets KEY=VALUE [--secrets KEY=VALUE]`: Comma-separated `KEY=VALUE` secrets. Injected as environment variables into the agent. Takes precedence over `--secrets-file`.
- `--secrets-file FILE`: File containing secret `KEY=VALUE` pairs, one per line. Injected as environment variables into the agent.
- `--secret-mount FILE`: Path to a file to load as a [file-mounted secret](https://docs.livekit.io/deploy/agents/secrets.md#file-mounted-secrets) in the agent container.
- `--id ID`: Agent ID. If unset and `livekit.toml` is present, uses the ID found there.
- `--overwrite`: Overwrite existing secrets. Default: `false`.
#### Examples
Update secrets without overwriting existing keys:
```shell
lk agent update-secrets --id CA_MyAgentId \
--secrets-file ./secrets.env
```
Overwrite existing keys explicitly:
```shell
lk agent update-secrets --id CA_MyAgentId \
--secrets OPENAI_API_KEY=sk-xxx \
--overwrite
```
Mount a file as a secret:
```shell
lk agent update-secrets --id CA_MyAgentId \
--secret-mount ./google-appplication-credentials.json
```
### Config
Generate a new `livekit.toml` in the working directory for an existing agent:
```shell
lk agent config --id AGENT_ID [options] [working-dir]
```
Options for `config`:
- `--id AGENT_ID`: Agent ID. Uses the provided ID to generate a new `livekit.toml` file.
### Generate Dockerfile
Generate a new `Dockerfile` and `.dockerignore` file in the working directory. To overwrite existing files, use the `--overwrite` flag.
```shell
lk agent dockerfile [options] [working-dir]
```
Options for `dockerfile`:
- `--overwrite`: Overwrite existing files. Default: `false`.
#### Examples
```shell
lk agent dockerfile
```
---
This document was rendered at 2026-02-03T03:25:08.279Z.
For the latest version of this document, see [https://docs.livekit.io/reference/other/agent-cli.md](https://docs.livekit.io/reference/other/agent-cli.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/deploy/admin/regions/agent-deployment.md
LiveKit docs › Administration › Regions › Agent deployment
---
# Agent deployment
> Configure and manage agent deployments across multiple regions.
## Overview
When you deploy agents on LiveKit Cloud, each agent is assigned to a specific region. This region assignment determines where the agent's compute resources run and cannot be changed after creation. By default, users connect to the agent deployment in the region closest to them, minimizing network latency and ensuring responsive interactions.
For global apps, you can deploy the same agent to multiple regions. This provides redundancy and ensures users worldwide experience low latency by connecting to their nearest deployment. You can also control region assignment explicitly using agent dispatch to route users to specific regional deployments based on your app's requirements.
## Deployment regions
Each agent deployment is isolated to a single region, which you must select during the first deployment. The following regions are currently available for agent deployments:
| Region code | Geographic location |
| `us-east` | Ashburn, Virginia, USA |
| `eu-central` | Frankfurt, Germany |
| `ap-south` | Mumbai, India |
Region assignment is immutable, and cannot be changed after agent creation.
## Multi-region deployments
To deploy an agent in multiple regions, use `lk agent create` once per region. To keep track of the deployments, add the region to the configuration filename. For instance, these commands deploy a new agent to both `us-east` and `eu-central` regions:
```shell
lk agent create --region us-east --config livekit.us-east.toml
lk agent create --region eu-central --config livekit.eu-central.toml
```
Now you can deploy the agent to each region as needed by specifying the appropriate configuration file:
```shell
lk agent deploy --config livekit.us-east.toml
lk agent deploy --config livekit.eu-central.toml
```
By default, users connect to the agent in the region closest to them. In some cases, if agents are at capacity, users may connect to an agent in a different region. For fine-grained control over which regions users connect to, set a separate agent name for each region and use [explicit dispatch](https://docs.livekit.io/agents/server/agent-dispatch.md#explicit) to directly assign users to the appropriate agent.
## Moving an agent to a new region
To move an existing agent to a new region, you should follow the preceding steps for [multi-region deployments](#multi-region-deployments) to add a deployment in the new region. Then, you can delete the agent in the old region using `lk agent delete`, specifying the old agent's ID or configuration file.
---
This document was rendered at 2026-02-03T03:25:23.437Z.
For the latest version of this document, see [https://docs.livekit.io/deploy/admin/regions/agent-deployment.md](https://docs.livekit.io/deploy/admin/regions/agent-deployment.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/server/agent-dispatch.md
LiveKit docs › Agent Server › Agent dispatch
---
# Agent dispatch
> Specifying how and when your agents are assigned to rooms.
## Dispatching agents
Dispatch is the process of assigning an agent to a room. LiveKit server manages this process as part of the [Server lifecycle](https://docs.livekit.io/agents/server/lifecycle.md). LiveKit optimizes dispatch for high concurrency and low latency, typically supporting hundred of thousands of new connections per second with a max dispatch time under 150 ms.
## Automatic agent dispatch
By default, an agent is automatically dispatched to each new room. Automatic dispatch is the best option if you want to assign the same agent to all new participants.
## Explicit agent dispatch
Explicit dispatch is available for greater control over when and how agents join rooms. This approach uses the same systems as automatic dispatch, allowing you to run agent servers in the same way.
To use explicit dispatch, set the `agent_name` field:
**Python**:
In Python, set the agent name in the `@server.rtc_session` decorator:
```python
@server.rtc_session(agent_name="test-agent")
async def my_agent(ctx: JobContext):
# Agent entrypointcode...
```
---
**Node.js**:
```ts
const opts = new WorkerOptions({
...
agentName: "test-agent",
});
```
> ❗ **Requires explicit dispatch**
>
> If you set the `agent_name` property, you turn off automatic dispatch. Agents must be explicitly dispatched to a room.
### Dispatch via API
You can explicitly dispatch an agent to a room using the `AgentDispatchService` API.
**Python**:
```python
import asyncio
from livekit import api
room_name = "my-room"
agent_name = "test-agent"
async def create_explicit_dispatch():
lkapi = api.LiveKitAPI()
dispatch = await lkapi.agent_dispatch.create_dispatch(
api.CreateAgentDispatchRequest(
agent_name=agent_name, room=room_name, metadata='{"user_id": "12345"}'
)
)
print("created dispatch", dispatch)
dispatches = await lkapi.agent_dispatch.list_dispatch(room_name=room_name)
print(f"there are {len(dispatches)} dispatches in {room_name}")
await lkapi.aclose()
asyncio.run(create_explicit_dispatch())
```
---
**Node.js**:
```ts
import { AgentDispatchClient } from 'livekit-server-sdk';
const roomName = 'my-room';
const agentName = 'test-agent';
async function createExplicitDispatch() {
const agentDispatchClient = new AgentDispatchClient(process.env.LIVEKIT_URL, process.env.LIVEKIT_API_KEY, process.env.LIVEKIT_API_SECRET);
// create a dispatch request for an agent named "test-agent" to join "my-room"
const dispatch = await agentDispatchClient.createDispatch(roomName, agentName, {
metadata: '{"user_id": "12345"}',
});
console.log('created dispatch', dispatch);
const dispatches = await agentDispatchClient.listDispatch(roomName);
console.log(`there are ${dispatches.length} dispatches in ${roomName}`);
}
```
---
**LiveKit CLI**:
```shell
lk dispatch create \
--agent-name test-agent \
--room my-room \
--metadata '{"user_id": "12345"}'
```
---
**Go**:
```go
func createAgentDispatch() {
req := &livekit.CreateAgentDispatchRequest{
Room: "my-room",
AgentName: "test-agent",
Metadata: "{\"user_id\": \"12345\"}",
}
dispatch, err := dispatchClient.CreateDispatch(context.Background(), req)
if err != nil {
panic(err)
}
fmt.Printf("Dispatch created: %v\n", dispatch)
}
```
The room, `my-room`, is automatically created during dispatch if it doesn't already exist, and the agent server assigns `test-agent` to it.
#### Job metadata
Explicit dispatch allows you to pass metadata to the agent, available in the `JobContext`. This is useful for including details such as the user's ID, name, or phone number.
The metadata field is a string. LiveKit recommends using JSON to pass structured data.
The [examples](#via-api) in the previous section demonstrate how to pass job metadata during dispatch.
For information on consuming job metadata in an agent, see the following guide:
- **[Job metadata](https://docs.livekit.io/agents/server/job.md#metadata)**: Learn how to consume job metadata in an agent.
### Dispatch from inbound SIP calls
Agents can be explicitly dispatched for inbound SIP calls. [SIP dispatch rules](https://docs.livekit.io/telephony/accepting-calls/dispatch-rule.md) can define one or more agents using the `room_config.agents` field.
LiveKit recommends explicit agent dispatch for SIP inbound calls rather than automatic agent dispatch as it allows multiple agents within a single project.
### Dispatch on participant connection
You can configure a participant's token to dispatch one or more agents immediately upon connection.
To dispatch multiple agents, include multiple `RoomAgentDispatch` entries in `RoomConfiguration`.
The following example creates a token that dispatches the `test-agent` agent to the `my-room` room when the participant connects:
**Python**:
```python
from livekit.api import (
AccessToken,
RoomAgentDispatch,
RoomConfiguration,
VideoGrants,
)
room_name = "my-room"
agent_name = "test-agent"
def create_token_with_agent_dispatch() -> str:
token = (
AccessToken()
.with_identity("my_participant")
.with_grants(VideoGrants(room_join=True, room=room_name))
.with_room_config(
RoomConfiguration(
agents=[
RoomAgentDispatch(agent_name="test-agent", metadata='{"user_id": "12345"}')
],
),
)
.to_jwt()
)
return token
```
---
**Node.js**:
```ts
import { RoomAgentDispatch, RoomConfiguration } from '@livekit/protocol';
import { AccessToken } from 'livekit-server-sdk';
const roomName = 'my-room';
const agentName = 'test-agent';
async function createTokenWithAgentDispatch(): Promise {
const at = new AccessToken();
at.identity = 'my-participant';
at.addGrant({ roomJoin: true, room: roomName });
at.roomConfig = new RoomConfiguration({
agents: [
new RoomAgentDispatch({
agentName: agentName,
metadata: '{"user_id": "12345"}',
}),
],
});
return await at.toJwt();
}
```
---
**Go**:
```go
func createTokenWithAgentDispatch() (string, error) {
at := auth.NewAccessToken(
os.Getenv("LIVEKIT_API_KEY"),
os.Getenv("LIVEKIT_API_SECRET"),
).
SetIdentity("my-participant").
SetName("Participant Name").
SetVideoGrant(&auth.VideoGrant{
Room: "my-room",
RoomJoin: true,
}).
SetRoomConfig(&livekit.RoomConfiguration{
Agents: []*livekit.RoomAgentDispatch{
{
AgentName: "test-agent",
Metadata: "{\"user_id\": \"12345\"}",
},
},
})
return at.ToJWT()
}
```
---
This document was rendered at 2026-02-03T03:24:58.053Z.
For the latest version of this document, see [https://docs.livekit.io/agents/server/agent-dispatch.md](https://docs.livekit.io/agents/server/agent-dispatch.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/logic/agents-handoffs.md
LiveKit docs › Logic & Structure › Agents & handoffs
---
# Agents and handoffs
> How to use agents and handoffs as part of a voice AI workflow.
## Overview
Agents are the core units of a voice AI [workflow](https://docs.livekit.io/agents/logic/workflows.md). They define the instructions, tools, and reasoning behavior that drive a conversation. An agent can transfer control to other agents when different logic or capabilities are required. Create separate agents when you need distinct reasoning behavior or tool access:
- **Different roles**: A moderator agent versus a coaching agent.
- **Model specialization**: A lightweight triage model before escalating to a larger one.
- **Different permissions**: An agent with payment API access versus one handling general inquiries.
- **Specialized contexts**: Agents optimized for particular conversation phases.
## Agents
Agents orchestrate the session flow—managing tools, reasoning steps, and control transfers between other agents or tasks.
### Defining an agent
Extend the `Agent` class to define a custom agent.
**Python**:
```python
from livekit.agents import Agent
class HelpfulAssistant(Agent):
def __init__(self):
super().__init__(instructions="You are a helpful voice AI assistant.")
async def on_enter(self) -> None:
await self.session.generate_reply(instructions="Greet the user and ask how you can help them.")
```
---
**Node.js**:
```ts
import { voice } from '@livekit/agents';
class HelpfulAssistant extends voice.Agent {
constructor() {
super({
instructions: 'You are a helpful voice AI assistant.',
});
}
async onEnter(): Promise {
this.session.generateReply({
instructions: 'Greet the user and ask how you can help them.',
});
}
}
```
You can also create an instance of `Agent` class directly:
**Python**:
```python
agent = Agent(instructions="You are a helpful voice AI assistant.")
```
---
**Node.js**:
```ts
const agent = new voice.Agent({
instructions: 'You are a helpful voice AI assistant.',
});
```
### Setting the active agent
The **active** agent is the agent currently in control of the session. The initial agent is defined in the `AgentSession` constructor. You can change the active agent using the `update_agent` method in Python, or a handoff from a [tool call](#tool-handoff).
Specify the initial agent in the `AgentSession` constructor:
**Python**:
```python
session = AgentSession(
agent=CustomerServiceAgent()
# ...
)
```
---
**Node.js**:
```ts
await session.start({
agent: new CustomerServiceAgent(),
room: ctx.room,
});
```
To set a new agent, use the `update_agent` method:
Available in:
- [ ] Node.js
- [x] Python
```python
session.update_agent(CustomerServiceAgent())
```
### Agent handoffs
A **handoff** transfers session control from one agent to another. You can return a different agent from within a tool call to hand off control automatically. This allows the LLM to make decisions about when a handoff should occur. For more information, see [tool return value](https://docs.livekit.io/agents/build/tools.md#return-value).
**Python**:
```python
from livekit.agents import Agent, function_tool
class CustomerServiceAgent(Agent):
def __init__(self):
super().__init__(
instructions="""You are a friendly customer service representative. Help customers with
general inquiries, account questions, and technical support. If a customer needs
specialized help, transfer them to the appropriate specialist."""
)
async def on_enter(self) -> None:
await self.session.generate_reply(instructions="Greet the user warmly and offer your assistance.")
@function_tool()
async def transfer_to_billing(self, context: RunContext):
"""Transfer the customer to a billing specialist for account and payment questions."""
return BillingAgent(chat_ctx=self.chat_ctx), "Transferring to billing"
@function_tool()
async def transfer_to_technical_support(self, context: RunContext):
"""Transfer the customer to technical support for product issues and troubleshooting."""
return TechnicalSupportAgent(chat_ctx=self.chat_ctx), "Transferring to technical support"
class BillingAgent(Agent):
def __init__(self):
super().__init__(
instructions="""You are a billing specialist. Help customers with account questions,
payments, refunds, and billing inquiries. Be thorough and empathetic."""
)
async def on_enter(self) -> None:
await self.session.generate_reply(instructions="Introduce yourself as a billing specialist and ask how you can help with their account.")
class TechnicalSupportAgent(Agent):
def __init__(self):
super().__init__(
instructions="""You are a technical support specialist. Help customers troubleshoot
product issues, setup problems, and technical questions. Ask clarifying questions
to diagnose problems effectively."""
)
async def on_enter(self) -> None:
await self.session.generate_reply(instructions="Introduce yourself as a technical support specialist and offer to help with any technical issues.")
```
---
**Node.js**:
```ts
import { voice, llm } from '@livekit/agents';
class CustomerServiceAgent extends voice.Agent {
constructor() {
super({
instructions: `You are a friendly customer service representative. Help customers with
general inquiries, account questions, and technical support. If a customer needs
specialized help, transfer them to the appropriate specialist.`,
tools: {
transferToBilling: llm.tool({
description: 'Transfer the customer to a billing specialist for account and payment questions.',
execute: async (_, { ctx }) => {
return llm.handoff({
agent: new BillingAgent(),
returns: 'Transferring to billing',
});
},
}),
transferToTechnicalSupport: llm.tool({
description: 'Transfer the customer to technical support for product issues and troubleshooting.',
execute: async (_, { ctx }) => {
return llm.handoff({
agent: new TechnicalSupportAgent(),
returns: 'Transferring to technical support',
});
},
}),
},
});
}
async onEnter(): Promise {
this.session.generateReply({
instructions: 'Greet the user warmly and offer your assistance.',
});
}
}
class BillingAgent extends voice.Agent {
constructor() {
super({
instructions: `You are a billing specialist. Help customers with account questions,
payments, refunds, and billing inquiries. Be thorough and empathetic.`,
});
}
async onEnter(): Promise {
this.session.generateReply({
instructions: 'Introduce yourself as a billing specialist and ask how you can help with their account.',
});
}
}
class TechnicalSupportAgent extends voice.Agent {
constructor() {
super({
instructions: `You are a technical support specialist. Help customers troubleshoot
product issues, setup problems, and technical questions. Ask clarifying questions
to diagnose problems effectively.`,
});
}
async onEnter(): Promise {
this.session.generateReply({
instructions: 'Introduce yourself as a technical support specialist and offer to help with any technical issues.',
});
}
}
```
#### Chat history
When an agent handoff occurs, an `AgentHandoff` item (or `AgentHandoffItem` in Node.js) is added to the chat context with the following properties:
- `old_agent_id`: ID of the agent that was active before the handoff.
- `new_agent_id`: ID of the agent that took over session control after the handoff.
### Passing state
To store custom state within your session, use the `userdata` attribute. The type of `userdata` is up to you, but the recommended approach is to use a `dataclass` in Python or a typed interface in TypeScript.
**Python**:
```python
from livekit.agents import AgentSession
from dataclasses import dataclass
@dataclass
class MySessionInfo:
user_name: str | None = None
age: int | None = None
```
---
**Node.js**:
```ts
interface MySessionInfo {
userName?: string;
age?: number;
}
```
To add userdata to your session, pass it in the constructor. You must also specify the type of userdata on the `AgentSession` itself.
**Python**:
```python
session = AgentSession[MySessionInfo](
userdata=MySessionInfo(),
# ... tts, stt, llm, etc.
)
```
---
**Node.js**:
```ts
const session = new voice.AgentSession({
userData: { userName: 'Steve' },
// ... vad, stt, tts, llm, etc.
});
```
Userdata is available as `session.userdata`, and is also available within function tools on the `RunContext`. The following example shows how to use userdata in an agent workflow that starts with the `IntakeAgent`.
**Python**:
```python
class IntakeAgent(Agent):
def __init__(self):
super().__init__(
instructions="""You are an intake agent. Learn the user's name and age."""
)
@function_tool()
async def record_name(self, context: RunContext[MySessionInfo], name: str):
"""Use this tool to record the user's name."""
context.userdata.user_name = name
return self._handoff_if_done()
@function_tool()
async def record_age(self, context: RunContext[MySessionInfo], age: int):
"""Use this tool to record the user's age."""
context.userdata.age = age
return self._handoff_if_done()
def _handoff_if_done(self):
if self.session.userdata.user_name and self.session.userdata.age:
return CustomerServiceAgent()
else:
return None
class CustomerServiceAgent(Agent):
def __init__(self):
super().__init__(instructions="You are a friendly customer service representative.")
async def on_enter(self) -> None:
userdata: MySessionInfo = self.session.userdata
await self.session.generate_reply(
instructions=f"Greet {userdata.user_name} personally and offer your assistance."
)
```
---
**Node.js**:
```ts
import { voice, llm } from '@livekit/agents';
import { z } from 'zod';
class IntakeAgent extends voice.Agent {
constructor() {
super({
instructions: "You are an intake agent. Learn the user's name and age.",
tools: {
recordName: llm.tool({
description: 'Use this tool to record the user\'s name.',
parameters: z.object({
name: z.string(),
}),
execute: async ({ name }, { ctx }) => {
ctx.userData.userName = name;
return this.handoffIfDone(ctx);
},
}),
recordAge: llm.tool({
description: 'Use this tool to record the user\'s age.',
parameters: z.object({
age: z.number(),
}),
execute: async ({ age }, { ctx }) => {
ctx.userData.age = age;
return this.handoffIfDone(ctx);
},
}),
},
});
}
private handoffIfDone(ctx: voice.RunContext) {
if (ctx.userData.userName && ctx.userData.age) {
return llm.handoff({
agent: new CustomerServiceAgent(),
returns: 'Information collected, transferring to customer service',
});
}
return 'Please provide both your name and age.';
}
}
class CustomerServiceAgent extends voice.Agent {
constructor() {
super({
instructions: 'You are a friendly customer service representative.',
});
}
async onEnter(): Promise {
const userData = this.session.userData;
this.session.generateReply({
instructions: `Greet ${userData.userName} personally and offer your assistance.`,
});
}
}
```
## Context preservation
By default, each new agent or task starts with a fresh conversation history for their LLM prompt. To include the prior conversation, set the `chat_ctx` parameter in the `Agent` or `AgentTask` constructor. You can either copy the prior agent's `chat_ctx`, or construct a new one based on custom business logic to provide the appropriate context.
**Python**:
```python
from livekit.agents import ChatContext, function_tool, Agent
class TechnicalSupportAgent(Agent):
def __init__(self, chat_ctx: ChatContext):
super().__init__(
instructions="""You are a technical support specialist. Help customers troubleshoot
product issues, setup problems, and technical questions.""",
chat_ctx=chat_ctx
)
class CustomerServiceAgent(Agent):
# ...
@function_tool()
async def transfer_to_technical_support(self):
"""Transfer the customer to technical support for product issues and troubleshooting."""
await self.session.generate_reply(instructions="Inform the customer that you're transferring them to the technical support team.")
# Pass the chat context during handoff
return TechnicalSupportAgent(chat_ctx=self.session.chat_ctx)
```
---
**Node.js**:
```ts
import { voice, llm } from '@livekit/agents';
class TechnicalSupportAgent extends voice.Agent {
constructor(chatCtx: llm.ChatContext) {
super({
instructions: `You are a technical support specialist. Help customers troubleshoot
product issues, setup problems, and technical questions.`,
chatCtx,
});
}
}
class CustomerServiceAgent extends voice.Agent {
constructor(chatCtx: llm.ChatContext) {
super({
// ... instructions, chatCtx, etc.
tools: {
transferToTechnicalSupport: llm.tool({
description: 'Transfer the customer to technical support for product issues and troubleshooting.',
execute: async (_, { ctx }) => {
await ctx.session.generateReply({
instructions: 'Inform the customer that you\'re transferring them to the technical support team.',
});
return llm.handoff({
agent: new TechnicalSupportAgent(ctx.session.chatCtx),
returns: 'Transferring to technical support team',
});
},
}),
},
});
}
}
```
The complete conversation history for the session is always available in `session.history`.
## Overriding plugins
You can override any of the plugins used in the session by setting the corresponding attributes in your `Agent` or `AgentTask` constructor. This allows you to customize the behavior and properties of the active agent or task in the session by modifying the prompt, TTS, LLM, STT plugins, and more.
For instance, you can change the voice for a specific agent by overriding the `tts` attribute:
**Python**:
```python
from livekit.agents import Agent
class CustomerServiceManager(Agent):
def __init__(self):
super().__init__(
instructions="You are a customer service manager who can handle escalated issues.",
tts="cartesia/sonic-3:6f84f4b8-58a2-430c-8c79-688dad597532"
)
```
---
**Node.js**:
```ts
import { voice } from '@livekit/agents';
class CustomerServiceManager extends voice.Agent {
constructor() {
super({
instructions: 'You are a customer service manager who can handle escalated issues.',
tts: "cartesia/sonic-3:6f84f4b8-58a2-430c-8c79-688dad597532",
});
}
}
```
## Examples
These examples show how to build more complex workflows with multiple agents:
- **[Drive-thru agent](https://github.com/livekit/agents/blob/main/examples/drive-thru)**: A complex food ordering agent with tasks, tools, and a complete evaluation suite.
- **[Front-desk agent](https://github.com/livekit/agents/blob/main/examples/frontdesk)**: A calendar booking agent with tasks, tools, and evaluations.
- **[Medical Office Triage](https://github.com/livekit-examples/python-agents-examples/tree/main/complex-agents/medical_office_triage)**: Agent that triages patients based on symptoms and medical history.
- **[Restaurant Agent](https://github.com/livekit/agents/blob/main/examples/voice_agents/restaurant_agent.py)**: A restaurant front-of-house agent that can take orders, add items to a shared cart, and checkout.
## Additional resources
For more information on concepts covered in this topic, see the following related topics:
- **[Workflows](https://docs.livekit.io/agents/build/workflows.md)**: Complete guide to defining and using workflows in your agents.
- **[Tool definition and use](https://docs.livekit.io/agents/build/tools.md)**: Complete guide to defining and using tools in your agents.
- **[Tasks & task groups](https://docs.livekit.io/agents/build/tasks.md)**: Complete guide to defining and using tasks and task groups in your agents workflows.
- **[Nodes](https://docs.livekit.io/agents/build/nodes.md)**: Add custom behavior to any component of the voice pipeline.
- **[Agent speech](https://docs.livekit.io/agents/build/audio.md)**: Customize the speech output of your agents.
- **[Testing & evaluation](https://docs.livekit.io/agents/start/testing.md)**: Test every aspect of your agents with a custom test suite.
---
This document was rendered at 2026-02-03T03:24:57.125Z.
For the latest version of this document, see [https://docs.livekit.io/agents/logic/agents-handoffs.md](https://docs.livekit.io/agents/logic/agents-handoffs.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/reference/components/agents-ui.md
# Source: https://docs.livekit.io/frontends/components/agents-ui.md
LiveKit docs › UI Components › Agents UI
---
# Agents UI overview
> Agents UI is the fastest way to build multi-modal, agentic experiences on top of LiveKit's platform primitives.
## Overview
Agents UI is a component library built on top of [Shadcn](https://ui.shadcn.com/) and [AI Elements](https://ai-sdk.dev/elements) to accelerate the creation of agentic applications built with LiveKit's real-time platform. It provides pre-built components for controlling IO, managing sessions, rendering transcripts, visualing audio streams, and more.
The [AgentAudioVisualizerAura](https://docs.livekit.io/reference/components/agents-ui/component/agent-audio-visualizer-aura.md) component featured above was designed in partnership with Unicorn Studio
## Quick reference
### Prerequisites
Before installing Agents UI, make sure your environment meets the following requirements:
- [Node.js](https://nodejs.org/), version 18 or later
- [Shadcn](https://ui.shadcn.com/docs/installation/next) is installed in your project.
> ℹ️ **Note**
>
> Running any install command will automatically install shadcn/ui for you. Agents UI is built targeting React 19 (no `forwardRef` usage) and Tailwind CSS 4.
### Installation
You can install Agents UI components using the Shadcn CLI.
Confirm you've navigated to the root of your project, and if you haven’t set up shadcn run:
```bash
npx shadcn@latest init
```
Then add the Agents UI registry with:
```bash
npx shadcn@latest registry add @agents-ui
```
Finally, install the components you need from the CLI with:
```bash
npx shadcn@latest add @agents-ui/{component-name}
```
### Usage
Most Agents UI components require access to a LiveKit session object for access to values like agent state or audio tracks. A Session object can be created from a [TokenSource](https://docs.livekit.io/reference/client-sdk-js/variables/TokenSource.html.md), and provided by wrapping the component in an [AgentSessionProvider](https://docs.livekit.io/reference/components/agents-ui/component/agent-session-provider.md).
```tsx
'use client';
import { useSession } from '@livekit/components-react';
import { AgentSessionProvider } from '@/components/agents-ui/agent-session-provider';
import { AgentControlBar } from '@/components/agents-ui/agent-control-bar';
const TOKEN_SOURCE = TokenSource.sandboxTokenServer(
process.env.NEXT_PUBLIC_ SANDBOX_TOKEN_SERVER_ID
);
export function Demo() {
const session = useSession(TOKEN_SOURCE);
return (
);
}
```
## Extensibility
Agents UI components take as many primitive attributes as possible. For example, the [AgentControlBar](https://docs.livekit.io/reference/components/agents-ui/component/agent-control-bar/page.mdoc.md) component extends `HTMLAttributes`, so you can pass any props that a div supports. This makes it easy to extend the component with your own styles or functionality.
You can edit any Agents UI component's source code in the `components/agents-ui` directory. For style changes, we recommend passing in tailwind classes to override the default styles. Take a look at the [source code](https://github.com/livekit/components-js/tree/main/packages/shadcn) to get a sense of how to override a component's default styles.
If you reinstall any Agents UI components by rerunning `npx shadcn@latest add @agents-ui/{component-name}`, the CLI will ask before overwriting the file so you can avoid losing any customizations you made.
After installation, no additional setup is needed. The component's styles (Tailwind CSS classes) and scripts are already integrated. You can start building with the component in your app immediately.
## Additional resources
Find in-depth API reference documentation for our Agents UI components references below.
- **[Agents UI reference](https://docs.livekit.io/reference/components/agents-ui.md)**: Beautiful components, built with shadcn/ui.
- **[GitHub repository](https://github.com/livekit/components-js/tree/main/packages/shadcn)**: Open source React component code.
---
This document was rendered at 2026-02-03T03:25:08.894Z.
For the latest version of this document, see [https://docs.livekit.io/frontends/components/agents-ui.md](https://docs.livekit.io/frontends/components/agents-ui.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/reference/python/v1/livekit/agents.md
# Source: https://docs.livekit.io/deploy/agents.md
# Source: https://docs.livekit.io/frontends/telephony/agents.md
# Source: https://docs.livekit.io/agents.md
# Source: https://docs.livekit.io/intro/basics/agents.md
LiveKit docs › Understanding LiveKit › Building AI agents
---
# Building AI agents
> Build AI agents that interact with users through realtime media and data streams.
## Overview
Build AI agents that join LiveKit rooms as participants, process realtime media and data streams, and interact with users through voice, text, and vision. The [LiveKit Agents framework](https://docs.livekit.io/agents.md) provides everything you need to build production-ready voice AI agents and programmatic participants.
When you build agents with the Agents framework, they join rooms as participants just like users from frontend apps. Agents can process audio, video, and data streams in realtime, making them ideal for voice assistants, multimodal AI applications, and custom programmatic participants.
The framework allows you to add Python or Node.js programs to any LiveKit room as full realtime participants. It includes tools and abstractions that make it easy to feed realtime media and data through an AI pipeline that works with any provider, and to publish realtime results back to the room.
## Getting started
Build your first agent with these resources:
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Build and deploy a simple voice assistant with Python or Node.js in less than 10 minutes.
- **[LiveKit Agent Builder](https://docs.livekit.io/agents/start/builder.md)**: Prototype and deploy voice agents directly in your browser, without writing any code.
## Learn more
For complete documentation on building agents:
- **[Agents framework](https://docs.livekit.io/agents.md)**: Learn how to build AI agents and programmatic participants with the LiveKit Agents framework.
- **[Multimodality](https://docs.livekit.io/agents/multimodality.md)**: Learn how to configure agents to process speech, text, and vision inputs.
- **[Logic & structure](https://docs.livekit.io/agents/logic.md)**: Learn how to structure your agent's logic and behavior with sessions, tasks, and workflows.
- **[Agent server](https://docs.livekit.io/agents/server.md)**: Learn how agent servers manage your agents' lifecycle and deployment.
- **[Models](https://docs.livekit.io/agents/models.md)**: Explore the full list of AI models and providers available for your agents.
---
This document was rendered at 2026-02-03T03:24:53.412Z.
For the latest version of this document, see [https://docs.livekit.io/intro/basics/agents.md](https://docs.livekit.io/intro/basics/agents.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/deploy/admin/analytics-api.md
LiveKit docs › Administration › Analytics API
---
# Analytics API
> Get information about your LiveKit Cloud sessions and participants
## Generate an access token for Analytics requests
Analytics API requests are authorized with a LiveKit token. This is generated by a server side SDK,much like [generating a token for joining Rooms](https://docs.livekit.io/frontends/authentication/tokens.md), except that the token needs the `roomList` grant.
> ℹ️ **Note**
>
> Analytics API is only available to LiveKit Cloud customers with a [Scale plan or higher](https://livekit.io/pricing).
**LiveKit CLI**:
```shell
lk token create \
--api-key $LIVEKIT_API_KEY \
--api-secret $LIVEKIT_SECRET_KEY \
--list \
--valid-for 24h
```
> 💡 **Tip**
>
> To streamline your workflow with the [CLI](https://docs.livekit.io/intro/basics/cli.md), add your projects using the command `lk project add`. This approach spares you from repeatedly entering your `--url`, `--api-key`, and `--api-secret` for each command you execute.
---
**Node.js**:
```js
const at = new AccessToken(apiKey, apiSecret, { ttl: 60 * 60 * 24 });
at.addGrant({ roomList: true });
```
## List sessions
To make a request, you'll need to know your project id, which you can see in the URL for your project dashboard. It's the part after `/projects/` that starts with `p_`.
**Shell**:
```shell
curl -H "Authorization: Bearer $TOKEN" \
"https://cloud-api.livekit.io/api/project/$PROJECT_ID/sessions"
```
---
**Node.js**:
```js
async function listLiveKitSessions() {
const endpoint = `https://cloud-api.livekit.io/api/project/${PROJECT_ID}/sessions/`;
try {
const response = await fetch(endpoint, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
console.log(data); // or do whatever you like here
} catch (error) {
console.log('There was a problem:', error.message);
}
}
listLiveKitSessions();
```
This will return a JSON object like this:
```json
{
sessions: [
{
sessionId, // string
roomName, // string
createdAt, // Timestamp
endedAt, // Timestamp
lastActive, // Timestamp
bandwidthIn, // bytes of bandwidth uploaded
bandwidthOut, // bytes of bandwidth downloaded
egress, // 0 = never started, 1 = active, 2 = ended
numParticipants, // int
numActiveParticipants, // int
connectionCounts: {
attempts, // int
success // int
},
},
// ...
]
}
```
### Query parameters
- **`limit`** _(int)_: You can limit the number of returned sessions by adding the limit query parameter like `?limit=100`.
> 🔥 **Caution**
>
> Higher `limit` values may result in a timeout from the Analytics API.
- **`page`** _(int)_: You can page through the results by adding `?page=n&limit=100` to the endpoint URL to get the `n`th page of results with `100` sessions per page. Pagination starts from `0`.
- **`start`** _(string)_: Specify the start date for the request time range in the format `YYYY-MM-DD`. Sessions starting on the specified start date will be included in the response.
> ℹ️ **Note**
>
> The start date must be within 7 days of the current date.
- **`end`** _(string)_: Specify the end date for the request time range using the format `YYYY-MM-DD`. Sessions up to and including this end date will be included in the response.
#### Examples
```shell
# Get the first page and limit the number of sessions to 100.
curl -H "Authorization: Bearer $TOKEN" \
"https://cloud-api.livekit.io/api/project/$PROJECT_ID/sessions\
?page=0&limit=100"
# Fetch sessions from a specified time range.
curl -H "Authorization: Bearer $TOKEN" \
"https://cloud-api.livekit.io/api/project/$PROJECT_ID/sessions\
?start=2024-01-12&end=2024-01-13"
```
## List session details
To get more details about a specific session, you can use the session_id returned from the list sessions request.
**Shell**:
```shell
curl -H "Authorization: Bearer $TOKEN" \
"cloud-api.livekit.io/api/project/$PROJECT_ID/sessions/$SESSION_ID"
```
---
**Node.js**:
```js
async function getLiveKitSessionDetails() {
const endpoint = `https://cloud-api.livekit.io/api/project/${PROJECT_ID}/sessions/${SESSION_ID}`;
try {
const response = await fetch(endpoint, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
console.log(data); // or do whatever you like here
} catch (error) {
console.log('There was a problem:', error.message);
}
}
getLiveKitSessionDetails();
```
This will return a JSON object like this:
```json
{
roomId, // string
roomName, // string
bandwidth, // billable bytes of bandwidth used
startTime, // Timestamp (e.g., "2025-09-29T13:59:40Z")
endTime, // Timestamp (e.g., "2025-09-29T14:59:40Z")
numParticipants, // int
connectionMinutes, // int: billable number of connection minutes for this session
quality: [
{
timestamp: // Timestamp (e.g., "2025-09-25T16:46:00Z")
value: // int
},
// ...
],
publishBps: [
{
timestamp: // Timestamp (e.g., "2025-09-25T16:46:00Z")
value: // int
},
// ...
]
participants: [
{
participantIdentity, // string
participantName, // string
roomId, // string
joinedAt, // Timestamp (e.g., "2025-09-29T13:59:40Z")
leftAt, // Timestamp (e.g., "2025-09-29T14:59:40Z")
location, // string
region, // string
connectionType, // string (e.g., "UDP")
connectionTimeMs, // int
deviceModel, // string (e.g., "Mac")
os, // string (e.g., "mac os x 10.15.7")
browser, // string (e.g., "Chrome 140.0.0")
sdkVersion, // string (e.g., "JS 2.15.7")
publishedSources: {
cameraTrack, // boolean
microphoneTrack, // boolean
screenShareTrack, // boolean
screenShareAudio, // boolean
},
sessions: [
{
participantId, // string
joinedAt, // Timestamp (e.g., "2025-09-29T13:59:40Z")
leftAt, // Timestamp (e.g., "2025-09-29T14:59:40Z")
},
// ...
],
},
// ...
]
}
```
`Timestamp` objects are [Protobuf Timestamps](https://protobuf.dev/reference/protobuf/google.protobuf/#timestamp).
---
This document was rendered at 2026-02-03T03:25:24.258Z.
For the latest version of this document, see [https://docs.livekit.io/deploy/admin/analytics-api.md](https://docs.livekit.io/deploy/admin/analytics-api.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/avatar/plugins/anam.md
LiveKit docs › Models › Virtual avatar › Plugins › Anam
---
# Anam virtual avatar integration guide
> How to use the Anam virtual avatar plugin for LiveKit Agents.
Available in:
- [x] Node.js
- [x] Python
## Overview
[Anam](https://anam.ai/) provides lifelike avatars for realtime conversational AI. You can use the open source Anam integration for LiveKit Agents to enable seamless integration of Anam avatars into your voice AI app.
## Quick reference
This section includes a basic usage example and some reference material. For links to more detailed documentation, see [Additional resources](#additional-resources).
### Installation
**Python**:
```shell
uv add "livekit-agents[anam]~=1.3"
```
---
**Node.js**:
```shell
pnpm add @livekit/agents-plugin-anam
```
### Authentication
The Anam plugin requires an [Anam API key](https://lab.anam.ai/api-keys).
Set `ANAM_API_KEY` in your `.env` file.
### Usage
Use the plugin in an `AgentSession`. For example, you can use this avatar in the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
**Python**:
```python
from livekit import agents
from livekit.agents import AgentServer, AgentSession
from livekit.plugins import anam
server = AgentServer()
@server.rtc_session()
async def my_agent(ctx: agents.JobContext):
session = AgentSession(
# ... stt, llm, tts, etc.
)
avatar = anam.AvatarSession(
persona_config=anam.PersonaConfig(
name="...", # Name of the avatar to use.
avatarId="...", # ID of the avatar to use. See "Avatar setup" for details.
),
)
# Start the avatar and wait for it to join
await avatar.start(session, room=ctx.room)
# Start your agent session with the user
await session.start(
# ... room, agent, room_options, etc....
)
```
---
**Node.js**:
```typescript
import { voice } from '@livekit/agents';
import * as anam from '@livekit/agents-plugin-anam';
const session = new voice.AgentSession({
// ... stt, llm, tts, etc.
});
const avatar = new anam.AvatarSession({
personaConfig: {
name: "...", // Name of the avatar to use.
avatarId: "...", // ID of the avatar to use. See "Avatar setup" for details.
},
});
// Start the avatar and wait for it to join
await avatar.start(session, room);
// Start your agent session with the user
await session.start(
// ... room, agent, room_options, etc.
);
```
Preview the avatar in the [Agents Playground](https://docs.livekit.io/agents/start/playground.md) or a frontend [starter app](https://docs.livekit.io/agents/start/frontend.md#starter-apps) that you build.
### Avatar setup
You can use stock avatars provided by Anam or create your own custom avatars using Anam Lab.
- **Stock Avatars**: Browse a collection of ready-to-use avatars in the [Avatar Gallery](https://docs.anam.ai/resources/avatar-gallery).
- **Custom Avatars**: Create your own personalized avatar using [Anam Lab](https://lab.anam.ai/avatars).
To use a stock avatar, copy the avatar ID from the gallery and use it in your `PersonaConfig`. For custom avatars, create them in the lab and use the generated avatar ID.
### Parameters
This section describes some of the available parameters. See the [plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/anam/index.html.md#livekit.plugins.anam.AvatarSession) for a complete list of all available parameters.
- **`persona_config`** _(anam.PersonaConfig)_ (optional): Configuration for the avatar to use.
- - **`name`** _(string)_: Name of the avatar to use. See [Avatar setup](#avatar-setup) for details.
- - **`avatarId`** _(string)_: ID of the avatar to use. See [Avatar setup](#avatar-setup) for details.
- **`avatar_participant_name`** _(string)_ (optional) - Default: `anam-avatar-agent`: The participant name to use for the avatar.
## Additional resources
The following resources provide more information about using Anam with LiveKit Agents.
- **[Anam API docs](https://docs.anam.ai/third-party-integrations/livekit)**: Anam's LiveKit integration docs.
- **[Agents Playground](https://docs.livekit.io/agents/start/playground.md)**: A virtual workbench to test your avatar agent.
- **[Frontend starter apps](https://docs.livekit.io/agents/start/frontend.md#starter-apps)**: Ready-to-use frontend apps with avatar support.
---
This document was rendered at 2026-02-03T03:25:06.874Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/avatar/plugins/anam.md](https://docs.livekit.io/agents/models/avatar/plugins/anam.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/sdk-platforms/android-compose.md
LiveKit docs › Get Started › SDK platform quickstarts › Android (Compose)
---
# Android quickstart (Jetpack Compose)
> Get started with LiveKit and Android using Jetpack Compose
## Voice AI quickstart
To build your first voice AI app for Android, use the following quickstart and the starter app. Otherwise follow the getting started guide below.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Create a voice AI agent in less than 10 minutes.
- **[Android Voice Agent](https://github.com/livekit-examples/agent-starter-android)**: A native Android voice AI assistant app built with Kotlin and Jetpack Compose.
## Getting started guide
This guide uses the Android Components library for the easiest way to get started on Android.
If you are using the traditional view-based system, check out the [Android quickstart](https://docs.livekit.io/transport/sdk-platforms/android.md).
Otherwise follow this guide to build your first LiveKit app with Android Compose.
### SDK installation
LiveKit Components for Android Compose is available as a Maven package.
```groovy
...
dependencies {
implementation "io.livekit:livekit-android-compose-components:"
}
```
See the [releases page](https://github.com/livekit/components-android/releases) for information on the latest version of the SDK.
You'll also need JitPack as one of your repositories. In your `settings.gradle` file:
```groovy
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
//...
maven { url 'https://jitpack.io' }
}
}
```
### Permissions
LiveKit relies on the `RECORD_AUDIO` and `CAMERA` permissions to use the microphone and camera. These permission must be requested at runtime, like so:
```kt
/**
* Checks if the RECORD_AUDIO and CAMERA permissions are granted.
*
* If not granted, will request them. Will call onPermissionGranted if/when
* the permissions are granted.
*/
fun ComponentActivity.requireNeededPermissions(onPermissionsGranted: (() -> Unit)? = null) {
val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { grants ->
// Check if any permissions weren't granted.
for (grant in grants.entries) {
if (!grant.value) {
Toast.makeText(
this,
"Missing permission: ${grant.key}",
Toast.LENGTH_SHORT
)
.show()
}
}
// If all granted, notify if needed.
if (onPermissionsGranted != null && grants.all { it.value }) {
onPermissionsGranted()
}
}
val neededPermissions = listOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.filter { ContextCompat.checkSelfPermission(this, it) == PackageManager.PERMISSION_DENIED }
.toTypedArray()
if (neededPermissions.isNotEmpty()) {
requestPermissionLauncher.launch(neededPermissions)
} else {
onPermissionsGranted?.invoke()
}
}
```
### Connecting to LiveKit
Note that this example hardcodes a token we generated for you that expires in 2 hours. In a real app, you’ll need your server to generate a token for you.
```kt
// !! Note !!
// This sample hardcodes a token which expires in 2 hours.
const val wsURL = "%{wsURL}%"
const val token = "%{token}%"
// In production you should generate tokens on your server, and your frontend
// should request a token from your server.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requireNeededPermissions {
setContent {
RoomScope(
url = wsURL,
token = token,
audio = true,
video = true,
connect = true,
) {
// Get all the tracks in the room.
val trackRefs = rememberTracks()
// Display the video tracks.
// Audio tracks are automatically played.
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(trackRefs.size) { index ->
VideoTrackView(
trackReference = trackRefs[index],
modifier = Modifier.fillParentMaxHeight(0.5f)
)
}
}
}
}
}
}
}
```
(For more details, you can reference the [Android Components SDK](https://github.com/livekit/components-android) and the [Meet example app](https://github.com/livekit-examples/android-components-meet).)
## Next steps
The following resources are useful for getting started with LiveKit on Android.
- **[Generating tokens](https://docs.livekit.io/frontends/authentication/tokens/generate.md)**: Guide to generating authentication tokens for your users.
- **[Realtime media](https://docs.livekit.io/transport/media.md)**: Complete documentation for live video and audio tracks.
- **[Realtime data](https://docs.livekit.io/transport/data.md)**: Send and receive realtime data between clients.
- **[Android SDK](https://github.com/livekit/client-sdk-android)**: LiveKit Android SDK on GitHub.
- **[Android components](https://github.com/livekit/components-android)**: LiveKit Android components on GitHub.
- **[Android SDK reference](https://docs.livekit.io/reference/client-sdk-android/index.html.md)**: LiveKit Android SDK reference docs.
- **[Android components reference](https://docs.livekit.io/reference/components/android.md)**: LiveKit Android components reference docs.
---
This document was rendered at 2026-02-03T03:25:14.780Z.
For the latest version of this document, see [https://docs.livekit.io/transport/sdk-platforms/android-compose.md](https://docs.livekit.io/transport/sdk-platforms/android-compose.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/reference/components/android.md
# Source: https://docs.livekit.io/transport/sdk-platforms/android.md
LiveKit docs › Get Started › SDK platform quickstarts › Android
---
# Android quickstart
> Get started with LiveKit and Android
## Voice AI quickstart
To build your first voice AI app for Android, use the following quickstart and the starter app. Otherwise follow the getting started guide below.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Create a voice AI agent in less than 10 minutes.
- **[Android Voice Agent](https://github.com/livekit-examples/agent-starter-android)**: A native Android voice AI assistant app built with Kotlin and Jetpack Compose.
## Getting started guide
This guide is for Android apps using the traditional view-based system. If you are using Jetpack Compose, check out the [Compose quickstart guide](https://docs.livekit.io/transport/sdk-platforms/android-compose.md).
### Install LiveKit SDK
LiveKit for Android is available as a Maven package.
```groovy
...
dependencies {
implementation "io.livekit:livekit-android:"
}
```
See the [releases page](https://github.com/livekit/client-sdk-android/releases) for information on the latest version of the SDK.
You'll also need JitPack as one of your repositories. In your `settings.gradle` file:
```groovy
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
//...
maven { url 'https://jitpack.io' }
}
}
```
### Permissions
LiveKit relies on the `RECORD_AUDIO` and `CAMERA` permissions to use the microphone and camera. These permission must be requested at runtime, like so:
```kt
private fun requestPermissions() {
val requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { grants ->
for (grant in grants.entries) {
if (!grant.value) {
Toast.makeText(
this,
"Missing permission: ${grant.key}",
Toast.LENGTH_SHORT
)
.show()
}
}
}
val neededPermissions = listOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
.filter {
ContextCompat.checkSelfPermission(
this,
it
) == PackageManager.PERMISSION_DENIED
}
.toTypedArray()
if (neededPermissions.isNotEmpty()) {
requestPermissionLauncher.launch(neededPermissions)
}
}
```
### Connect to LiveKit
Use the following code to connect and publish audio/video to a room, while rendering the video from other connected participants.
LiveKit uses `SurfaceViewRenderer` to render video tracks. A `TextureView` implementation is also provided through `TextureViewRenderer`. Subscribed audio tracks are automatically played.
Note that this example hardcodes a token we generated for you that expires in 2 hours. In a real app, you’ll need your server to generate a token for you.
```kt
// !! Note !!
// This sample hardcodes a token which expires in 2 hours.
const val wsURL = "%{wsURL}%"
const val token = "%{token}%"
// In production you should generate tokens on your server, and your frontend
// should request a token from your server.
class MainActivity : AppCompatActivity() {
lateinit var room: Room
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Create Room object.
room = LiveKit.create(applicationContext)
// Setup the video renderer
room.initVideoRenderer(findViewById(R.id.renderer))
connectToRoom()
}
private fun connectToRoom() {
lifecycleScope.launch {
// Setup event handling.
launch {
room.events.collect { event ->
when (event) {
is RoomEvent.TrackSubscribed -> onTrackSubscribed(event)
else -> {}
}
}
}
// Connect to server.
room.connect(
wsURL,
token,
)
// Publish audio/video to the room
val localParticipant = room.localParticipant
localParticipant.setMicrophoneEnabled(true)
localParticipant.setCameraEnabled(true)
}
}
private fun onTrackSubscribed(event: RoomEvent.TrackSubscribed) {
val track = event.track
if (track is VideoTrack) {
attachVideo(track)
}
}
private fun attachVideo(videoTrack: VideoTrack) {
videoTrack.addRenderer(findViewById(R.id.renderer))
findViewById(R.id.progress).visibility = View.GONE
}
}
```
(For more details, you can reference [the complete sample app](https://github.com/livekit/client-sdk-android/blob/d8c3b2c8ad8c129f061e953eae09fc543cc715bb/sample-app-basic/src/main/java/io/livekit/android/sample/basic/MainActivity.kt#L21).)
## Next steps
The following resources are useful for getting started with LiveKit on Android.
- **[Generating tokens](https://docs.livekit.io/frontends/authentication/tokens/generate.md)**: Guide to generating authentication tokens for your users.
- **[Realtime media](https://docs.livekit.io/transport/media.md)**: Complete documentation for live video and audio tracks.
- **[Realtime data](https://docs.livekit.io/transport/data.md)**: Send and receive realtime data between clients.
- **[Android SDK](https://github.com/livekit/client-sdk-android)**: LiveKit Android SDK on GitHub.
- **[Android components](https://github.com/livekit/components-android)**: LiveKit Android components on GitHub.
- **[Android SDK reference](https://docs.livekit.io/reference/client-sdk-android/index.html.md)**: LiveKit Android SDK reference docs.
- **[Android components reference](https://docs.livekit.io/reference/components/android.md)**: LiveKit Android components reference docs.
---
This document was rendered at 2026-02-03T03:25:15.012Z.
For the latest version of this document, see [https://docs.livekit.io/transport/sdk-platforms/android.md](https://docs.livekit.io/transport/sdk-platforms/android.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/recipes/answer_call.md
LiveKit docs › Telephony › Answer Incoming Calls
---
# Simple Call Answering Agent
> Basic agent for handling incoming phone calls with simple conversation
This example is a basic agent that can answer inbound phone calls. This doesn't require any SIP-specific code. When you point a LiveKit phone number at a dispatch rule, SIP callers are automatically delivered into the room and the running agent greets them.
## Prerequisites
- Buy a phone number in the LiveKit dashboard and create a dispatch rule that targets your worker:- Buy a number: Telephony → Phone Numbers → Buy number → Create dispatch rule
- Add a `.env` in this directory with your LiveKit credentials:```
LIVEKIT_URL=your_livekit_url
LIVEKIT_API_KEY=your_api_key
LIVEKIT_API_SECRET=your_api_secret
```
- Install dependencies:```bash
pip install "livekit-agents[silero]" python-dotenv
```
## Load environment, logging, and define an AgentServer
Start by importing the necessary modules and setting up the basic agent server. Load environment variables and configure logging for debugging.
```python
import logging
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, AgentServer, cli, inference
from livekit.plugins import silero
load_dotenv()
logger = logging.getLogger("answer-call")
logger.setLevel(logging.INFO)
server = AgentServer()
```
## Define the agent and session
Keep your Agent lightweight by only including the instructions. Preload VAD so that it runs once per process to cut down on connection latency.
Define STT, LLM, and TTS as a part of your AgentSession inside the RTC session. Start your session with your agent and connect to the room.
```python
import logging
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, AgentServer, cli, inference
from livekit.plugins import silero
load_dotenv()
logger = logging.getLogger("answer-call")
logger.setLevel(logging.INFO)
server = AgentServer()
```
```python
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
class SimpleAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""
You are a helpful agent.
"""
)
async def on_enter(self):
self.session.generate_reply()
@server.rtc_session()
async def my_agent(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
session = AgentSession(
stt=inference.STT(model="deepgram/nova-3-general"),
llm=inference.LLM(model="openai/gpt-4.1-mini"),
tts=inference.TTS(model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"),
vad=ctx.proc.userdata["vad"],
preemptive_generation=True,
)
agent = SimpleAgent()
await session.start(agent=agent, room=ctx.room)
await ctx.connect()
```
## Run the server
The `cli.run_app()` function starts the agent server. It manages the worker lifecycle, connects to LiveKit, and processes incoming jobs. When you run the script, it listens for incoming calls and automatically spawns agent sessions when calls arrive.
```python
import logging
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, AgentServer, cli, inference
from livekit.plugins import silero
load_dotenv()
logger = logging.getLogger("answer-call")
logger.setLevel(logging.INFO)
server = AgentServer()
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
class SimpleAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""
You are a helpful agent.
"""
)
async def on_enter(self):
self.session.generate_reply()
@server.rtc_session()
async def my_agent(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
session = AgentSession(
stt=inference.STT(model="deepgram/nova-3-general"),
llm=inference.LLM(model="openai/gpt-4.1-mini"),
tts=inference.TTS(model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"),
vad=ctx.proc.userdata["vad"],
preemptive_generation=True,
)
agent = SimpleAgent()
await session.start(agent=agent, room=ctx.room)
await ctx.connect()
```
```python
if __name__ == "__main__":
cli.run_app(server)
```
## Run it
Run the agent using the `console` command, which starts the agent in console mode. This mode is useful for testing and debugging. It connects to a mocked LiveKit room so you can test the agent locally before deploying. This will not work for real phone calls (since the room is mocked), but it's a great way to quickly test that your agent works.
```bash
python answer_call.py console
```
If you want to test your agent with a real phone call, you'll need to start it in dev mode instead. This will connect your agent to a LiveKit server, which makes it available to your dispatch rules.
```bash
python answer_call.py dev
```
## How inbound calls connect
1. An inbound call hits your LiveKit number.
2. The dispatch rule attaches the SIP participant to your room.
3. If the worker is running, the agent is already in the room and responds immediately—no special SIP handling needed.
## Complete code for the call answering agent
```python
import logging
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, AgentServer, cli, inference
from livekit.plugins import silero
load_dotenv()
logger = logging.getLogger("answer-call")
logger.setLevel(logging.INFO)
server = AgentServer()
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
class SimpleAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""
You are a helpful agent.
"""
)
async def on_enter(self):
self.session.generate_reply()
@server.rtc_session()
async def my_agent(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
session = AgentSession(
stt=inference.STT(model="deepgram/nova-3-general"),
llm=inference.LLM(model="openai/gpt-4.1-mini"),
tts=inference.TTS(model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"),
vad=ctx.proc.userdata["vad"],
preemptive_generation=True,
)
agent = SimpleAgent()
await session.start(agent=agent, room=ctx.room)
await ctx.connect()
if __name__ == "__main__":
cli.run_app(server)
```
---
This document was rendered at 2026-02-03T03:25:29.125Z.
For the latest version of this document, see [https://docs.livekit.io/recipes/answer_call.md](https://docs.livekit.io/recipes/answer_call.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/llm/plugins/anthropic.md
LiveKit docs › Models › LLM › Plugins › Anthropic
---
# Anthropic Claude LLM plugin guide
> How to use the Anthropic Claude LLM plugin for LiveKit Agents.
Available in:
- [ ] Node.js
- [x] Python
## Overview
This plugin allows you to use the the [Claude API](https://claude.com/platform/api) as a LLM provider for your voice agents.
## Quick reference
This section includes a basic usage example and some reference material. For links to more detailed documentation, see [Additional resources](#additional-resources).
### Installation
Install the plugin from PyPI:
```shell
uv add "livekit-agents[anthropic]~=1.3"
```
### Authentication
The Anthropic plugin requires an [Anthropic API key](https://console.anthropic.com/account/keys).
Set `ANTHROPIC_API_KEY` in your `.env` file.
### Usage
Use Claude within an `AgentSession` or as a standalone LLM service. For example, you can use this LLM in the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
```python
from livekit.plugins import anthropic
session = AgentSession(
llm=anthropic.LLM(
model="claude-3-5-sonnet-20241022",
temperature=0.8,
),
# ... tts, stt, vad, turn_detection, etc.
)
```
### Parameters
This section describes some of the available parameters. See the [plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/anthropic/index.html.md#livekit.plugins.anthropic.LLM) for a complete list of all available parameters.
- **`model`** _(str | ChatModels)_ (optional) - Default: `claude-3-5-sonnet-20241022`: Model to use. For a full list of available models, see the [Model options](https://docs.anthropic.com/en/docs/intro-to-claude#model-options).
- **`max_tokens`** _(int)_ (optional): The maximum number of tokens to generate before stopping. To learn more, see the [Anthropic API reference](https://docs.anthropic.com/en/api/messages#body-max-tokens).
- **`temperature`** _(float)_ (optional) - Default: `1`: Controls the randomness of the model's output. Higher values, for example 0.8, make the output more random, while lower values, for example 0.2, make it more focused and deterministic.
Valid values are between `0` and `1`. To learn more, see the [Anthropic API reference](https://docs.anthropic.com/en/api/messages#body-temperature).
- **`parallel_tool_calls`** _(bool)_ (optional): Controls whether the model can make multiple tool calls in parallel. When enabled, the model can make multiple tool calls simultaneously, which can improve performance for complex tasks.
- **`tool_choice`** _(ToolChoice | Literal['auto', 'required', 'none'])_ (optional) - Default: `auto`: Controls how the model uses tools. Set to 'auto' to let the model decide, 'required' to force tool usage, or 'none' to disable tool usage.
## Additional resources
The following links provide more information about the Anthropic LLM plugin.
- **[Python package](https://pypi.org/project/livekit-plugins-anthropic/)**: The `livekit-plugins-anthropic` package on PyPI.
- **[Plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/anthropic/index.html.md#livekit.plugins.anthropic.LLM)**: Reference for the Anthropic LLM plugin.
- **[GitHub repo](https://github.com/livekit/agents/tree/main/livekit-plugins/livekit-plugins-anthropic)**: View the source or contribute to the LiveKit Anthropic LLM plugin.
- **[Anthropic docs](https://docs.anthropic.com/en/docs/intro-to-claude)**: Anthropic Claude docs.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Get started with LiveKit Agents and Anthropic.
---
This document was rendered at 2026-02-03T03:24:59.465Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/llm/plugins/anthropic.md](https://docs.livekit.io/agents/models/llm/plugins/anthropic.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/home/get-started/api-primitives.md
LiveKit docs › Get Started › Rooms, participants, and tracks
---
# Rooms, participants, and tracks
> Guide to the core API primitives in LiveKit.
## Overview
LiveKit has only three core constructs: a room, participant, and track. A room is simply a realtime session between one or more participants. A participant can publish one or more tracks and/or subscribe to one or more tracks from another participant.
## Room
A `Room` is a container object representing a LiveKit session.
Each participant in a room receives updates about changes to other participants in the same room. For example, when a participant adds, removes, or modifies the state (for example, mute) of a track, other participants are notified of this change. This is a powerful mechanism for synchronizing state and fundamental to building any realtime experience.
A room can be created manually via [server API](https://docs.livekit.io/home/server/managing-rooms.md#create-a-room), or automatically, when the first participant joins it. Once the last participant leaves a room, it closes after a short delay.
## Participant
A `Participant` is a user or process that is participating in a realtime session. They are represented by a unique developer-provided `identity` and a server-generated `sid`. A participant object also contains metadata about its state and tracks they've published.
> ❗ **Important**
>
> A participant's identity is unique per room. Thus, if participants with the same identity join a room, only the most recent one to join will remain; the server automatically disconnects other participants using that identity.
There are two kinds of participant objects in the SDKs:
- A `LocalParticipant` represents the current user who, by default, can publish tracks in a room.
- A `RemoteParticipant` represents a remote user. The local participant, by default, can subscribe to any tracks published by a remote participant.
A participant may also [exchange data](https://docs.livekit.io/home/client/data.md) with one or many other participants.
### Hidden participants
A participant is hidden if their participant [permissions](https://docs.livekit.io/reference/server/server-apis.md#participantpermission) has `hidden` set to `true`. You can set this field in the participant's [access token](https://docs.livekit.io/home/get-started/authentication.md#video-grant). A hidden participant is not visible to other participants in the room.
### Participant fields
| Field | Type | Description |
| sid | string | A UID for this particular participant, generated by LiveKit server. |
| identity | string | Unique identity of the participant, as specified when connecting. |
| name | string | Optional display name. |
| state | ParticipantInfo.State | JOINING, JOINED, ACTIVE, or DISCONNECTED. |
| tracks | List<[TrackInfo](https://docs.livekit.io/reference/server/server-apis.md#trackinfo)> | Tracks published by the participant. |
| metadata | string | User-specified metadata for the participant. |
| joined_at | int64 | Timestamp when the participant joined the room. |
| kind | ParticipantInfo.Kind | [Type](#types-of-participants) of participant. |
| kind_detail | ParticipantInfo.KindDetail | Additional details about participant type. Valide values are `CLOUD_AGENT` or `FORWARDED`. |
| attributes | string | User-specified [attributes](https://docs.livekit.io/home/client/data.md) for the participant. |
| permission | [ParticipantPermission](https://docs.livekit.io/reference/server/server-apis.md#participantpermission) | Permissions granted to the participant. |
### Types of participants
In a realtime session, a participant could represent an end-user, as well as a server-side process. It's possible to distinguish between them with the `kind` field:
- `STANDARD`: A regular participant, typically an end-user in your application.
- `AGENT`: An agent spawned with the [Agents framework](https://docs.livekit.io/agents.md).
- `SIP`: A telephony user connected via [SIP](https://docs.livekit.io/sip.md).
- `EGRESS`: A server-side process that is recording the session using [LiveKit Egress](https://docs.livekit.io/home/egress/overview.md).
- `INGRESS`: A server-side process that is ingesting media into the session using [LiveKit Ingress](https://docs.livekit.io/home/ingress/overview.md).
## Track
A `Track` represents a stream of information, be it audio, video or custom data. By default, a participant in a room may publish tracks, such as their camera or microphone streams and subscribe to one or more tracks published by other participants. In order to model a track which may not be subscribed to by the local participant, all track objects have a corresponding `TrackPublication` object:
- `Track`: a wrapper around the native WebRTC `MediaStreamTrack`, representing a playable track.
- `TrackPublication`: a track that's been published to the server. If the track is subscribed to by the local participant and available for playback locally, it will have a `.track` attribute representing the associated `Track` object.
We can now list and manipulate tracks (via track publications) published by other participants, even if the local participant is not subscribed to them.
### TrackPublication fields
A `TrackPublication` contains information about its associated track:
| Field | Type | Description |
| sid | string | A UID for this particular track, generated by LiveKit server. |
| kind | Track.Kind | The type of track, whether it be audio, video or arbitrary data. |
| source | Track.Source | Source of media: Camera, Microphone, ScreenShare, or ScreenShareAudio. |
| name | string | The name given to this particular track when initially published. |
| subscribed | boolean | Indicates whether or not this track has been subscribed to by the local participant. |
| track | Track | If the local participant is subscribed, the associated `Track` object representing a WebRTC track. |
| muted | boolean | Whether this track is muted or not by the local participant. While muted, it won't receive new bytes from the server. |
### Track subscription
When a participant is subscribed to a track (which hasn't been muted by the publishing participant), they continuously receive its data. If the participant unsubscribes, they stop receiving media for that track and may resubscribe to it at any time.
When a participant creates or joins a room, the `autoSubscribe` option is set to `true` by default. This means the participant automatically subscribes to all existing tracks being published and any track published in the future. For more fine-grained control over track subscriptions, you can set `autoSubscribe` to `false` and instead use [selective subscriptions](https://docs.livekit.io/home/client/receive.md#selective-subscription).
> ℹ️ **Note**
>
> For most use cases, muting a track on the publisher side or unsubscribing from it on the subscriber side is typically recommended over unpublishing it. Publishing a track requires a negotiation phase and consequently has worse time-to-first-byte performance.
---
This document was rendered at 2025-11-18T23:54:49.181Z.
For the latest version of this document, see [https://docs.livekit.io/home/get-started/api-primitives.md](https://docs.livekit.io/home/get-started/api-primitives.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/reference/other/ingress/api.md
# Source: https://docs.livekit.io/reference/other/egress/api.md
# Source: https://docs.livekit.io/reference/python/v1/livekit/api.md
# Source: https://docs.livekit.io/reference/other/ingress/api.md
# Source: https://docs.livekit.io/reference/other/egress/api.md
LiveKit docs › Other › Egress › Egress API
---
# Egress API
> Use LiveKit's egress service to record or livestream a Room.
## API
The Egress API is available within our server SDKs and CLI:
- [Go Egress Client](https://pkg.go.dev/github.com/livekit/server-sdk-go/v2#EgressClient)
- [JS Egress Client](https://docs.livekit.io/reference/server-sdk-js/classes/EgressClient.html.md)
- [Ruby Egress Client](https://github.com/livekit/server-sdk-ruby/blob/main/lib/livekit/egress_service_client.rb)
- [Python Egress Client](https://docs.livekit.io/reference/python/v1/livekit/api/egress_service.html.md)
- [Java Egress Client](https://github.com/livekit/server-sdk-kotlin/blob/main/src/main/kotlin/io/livekit/server/EgressServiceClient.kt)
- [CLI](https://github.com/livekit/livekit-cli/blob/main/cmd/lk/egress.go)
> ❗ **Important**
>
> Requests to the Egress API need the `roomRecord` permission on the [access token](https://docs.livekit.io/concepts/authentication.md).
You can also use `curl` to interact with the Egress APIs. To do so, `POST` the arguments in JSON format to:
`https:///twirp/livekit.Egress/`
For example:
```shell
% curl -X POST https:///twirp/livekit.Egress/StartRoomCompositeEgress \
-H 'Authorization: Bearer ' \
-H 'Content-Type: application/json' \
-d '{"room_name": "your-room", "segments": {"filename_prefix": "your-hls-playlist.m3u8", "s3": {"access_key": "", "secret": "", "bucket": "", "region": ""}}}'
```
```shell
{"egress_id":"EG_MU4QwhXUhWf9","room_id":"","room_name":"your-room","status":"EGRESS_STARTING"...}
```
> 💡 **Tip**
>
> All RPC definitions and options can be found [here](https://github.com/livekit/protocol/blob/main/protobufs/livekit_egress.proto).
### StartRoomCompositeEgress
Starts a new [Composite Recording](https://docs.livekit.io/transport/media/ingress-egress/egress/composite-recording.md) using a web browser as the rendering engine.
| Parameter | Type | Required | Description |
| `room_name` | string | yes | name of room to record |
| `layout` | string | | layout parameter that is passed to the template |
| `audio_only` | bool | | true if resulting output should only contain audio |
| `audio_mixing` | [AudioMixing](#audiomixing) | | Audio mixing mode to use when `audio_only` is true. Defaults to DEFAULT_MIXING: All users are mixed together. |
| `video_only` | bool | | true if resulting output should only contain video |
| `custom_base_url` | string | | URL to the page that would composite tracks, uses embedded templates if left blank |
| `file_outputs` | [EncodedFileOutput](#EncodedFileOutput)[] | | output to MP4 file. currently only supports a single entry |
| `segment_outputs` | [SegmentedFileOutput](#SegmentedFileOutput)[] | | output to HLS segments. currently only supports a single entry |
| `stream_outputs` | [StreamOutput](#StreamOutput)[] | | output to a stream. currently only supports a single entry, though it could includ multiple destination URLs |
| `image_outputs` | [ImageOutput](#ImageOutput)[] | | output to a succession of snapshot images taken at a given interval (thumbnails). Currently only supports a single entry. |
| `preset` | [EncodingOptionsPreset](#EncodingOptionsPreset) | | encoding preset to use. only one of preset or advanced could be set |
| `advanced` | [EncodingOptions](#EncodingOptions) | | advanced encoding options. only one of preset or advanced could be set |
| `webhooks` | [WebhookConfig](#WebhookConfig)[] | | extra webhooks to send on egress events for this request |
### StartTrackCompositeEgress
Starts a new [Track Composite](https://docs.livekit.io/transport/media/ingress-egress/egress/participant.md)
| Parameter | Type | Required | Description |
| `room_name` | string | yes | name of room to record |
| `audio_track_id` | string | | ID of audio track to composite |
| `video_track_id` | string | | ID of video track to composite |
| `file_outputs` | [EncodedFileOutput](#EncodedFileOutput)[] | | output to MP4 file. currently only supports a single entry |
| `segment_outputs` | [SegmentedFileOutput](#SegmentedFileOutput)[] | | output to HLS segments. currently only supports a single entry |
| `stream_outputs` | [StreamOutput](#StreamOutput)[] | | output to a stream. currently only supports a single entry, though it could includ multiple destination URLs |
| `image_outputs` | [ImageOutput](#ImageOutput)[] | | output to a succession of snapshot images taken at a given interval (thumbnails). Currently only supports a single entry. |
| `preset` | [EncodingOptionsPreset](#EncodingOptionsPreset) | | encoding preset to use. only one of preset or advanced could be set |
| `advanced` | [EncodingOptions](#EncodingOptions) | | advanced encoding options. only one of preset or advanced could be set |
| `webhooks` | [WebhookConfig](#WebhookConfig)[] | | extra webhooks to send on egress events for this request |
### StartTrackEgress
Starts a new [Track Egress](https://docs.livekit.io/transport/media/ingress-egress/egress/track.md)
| Parameter | Type | Required | Description |
| `room_name` | string | yes | name of room to record |
| `track_id` | string | | ID of track to record |
| `file` | [DirectFileOutput](#DirectFileOutput) | | only one of file or websocket_url can be set |
| `websocket_url` | string | | url to websocket to receive audio output. only one of file or websocket_url can be set |
| `webhooks` | [WebhookConfig](#WebhookConfig)[] | | extra webhooks to send on egress events for this request |
### StartWebEgress
Starts a new [Web Egress](https://docs.livekit.io/transport/media/ingress-egress/egress/composite-recording.md)
| Parameter | Type | Required | Description |
| `url` | string | yes | URL of the web page to record |
| `audio_only` | bool | | true if resulting output should only contain audio |
| `video_only` | bool | | true if resulting output should only contain video |
| `file_outputs` | [EncodedFileOutput](#EncodedFileOutput)[] | | output to MP4 file. currently only supports a single entry |
| `segment_outputs` | [SegmentedFileOutput](#SegmentedFileOutput)[] | | output to HLS segments. currently only supports a single entry |
| `stream_outputs` | [StreamOutput](#StreamOutput)[] | | output to a stream. currently only supports a single entry, though it could includ multiple destination URLs |
| `image_outputs` | [ImageOutput](#ImageOutput)[] | | output to a succession of snapshot images taken at a given interval (thumbnails). Currently only supports a single entry. |
| `preset` | [EncodingOptionsPreset](#EncodingOptionsPreset) | | encoding preset to use. only one of preset or advanced could be set |
| `advanced` | [EncodingOptions](#EncodingOptions) | | advanced encoding options. only one of preset or advanced could be set |
| `webhooks` | [WebhookConfig](#WebhookConfig)[] | | extra webhooks to send on egress events for this request |
### UpdateLayout
Used to change the web layout on an active RoomCompositeEgress.
| Parameter | Type | Required | Description |
| `egress_id` | string | yes | Egress ID to update |
| `layout` | string | yes | layout to update to |
**JavaScript**:
```typescript
const info = await egressClient.updateLayout(egressID, 'grid-light');
```
---
**Go**:
```go
info, err := egressClient.UpdateLayout(ctx, &livekit.UpdateLayoutRequest{
EgressId: egressID,
Layout: "grid-light",
})
```
---
**Ruby**:
```ruby
egressClient.update_layout('egress-id', 'grid-dark')
```
---
**Java**:
```java
try {
egressClient.updateLayout("egressId", "grid-light").execute();
} catch (IOException e) {
// handle exception
}
```
---
**LiveKit CLI**:
```shell
lk egress update-layout --id --layout speaker
```
### UpdateStream
Used to add or remove stream urls from an active stream
Note: you can only add outputs to an Egress that was started with `stream_outputs` set.
| Parameter | Type | Required | Description |
| `egress_id` | string | yes | Egress ID to update |
| `add_output_urls` | string[] | | URLs to add to the egress as output destinations |
| `remove_output_urls` | string[] | | URLs to remove from the egress |
**JavaScript**:
```typescript
const streamOutput = new StreamOutput({
protocol: StreamProtocol.RTMP,
urls: ['rtmp://live.twitch.tv/app/'],
});
var info = await egressClient.startRoomCompositeEgress('my-room', { stream: streamOutput });
const streamEgressID = info.egressId;
info = await egressClient.updateStream(streamEgressID, [
'rtmp://a.rtmp.youtube.com/live2/stream-key',
]);
```
---
**Go**:
```go
streamRequest := &livekit.RoomCompositeEgressRequest{
RoomName: "my-room",
Layout: "speaker",
Output: &livekit.RoomCompositeEgressRequest_Stream{
Stream: &livekit.StreamOutput{
Protocol: livekit.StreamProtocol_RTMP,
Urls: []string{"rtmp://live.twitch.tv/app/"},
},
},
}
info, err := egressClient.StartRoomCompositeEgress(ctx, streamRequest)
streamEgressID := info.EgressId
info, err = egressClient.UpdateStream(ctx, &livekit.UpdateStreamRequest{
EgressId: streamEgressID,
AddOutputUrls: []string{"rtmp://a.rtmp.youtube.com/live2/"}
})
```
---
**Ruby**:
```ruby
# to add streams
egressClient.update_stream(
'egress-id',
add_output_urls: ['rtmp://new-url'],
remove_output_urls: ['rtmp://old-url']
)
```
---
**Java**:
```java
try {
egressClient.updateStream(
"egressId",
Collections.singletonList("rtmp://new-url"),
Collections.singletonList("rtmp://old-url")
).execute();
} catch (IOException e) {
// handle exception
}
```
---
**LiveKit CLI**:
```shell
lk update-stream \
--id \
--add-urls "rtmp://a.rtmp.youtube.com/live2/stream-key"
```
### ListEgress
Used to list active egress. Does not include completed egress.
**JavaScript**:
```typescript
const res = await egressClient.listEgress();
```
---
**Go**:
```go
res, err := egressClient.ListEgress(ctx, &livekit.ListEgressRequest{})
```
---
**Ruby**:
```ruby
# to list egress on myroom
egressClient.list_egress(room_name: 'myroom')
# to list all egresses
egressClient.list_egress()
```
---
**Java**:
```java
try {
List egressInfos = egressClient.listEgress().execute().body();
} catch (IOException e) {
// handle exception
}
```
---
**LiveKit CLI**:
```shell
lk egress list
```
### StopEgress
Stops an active egress.
**JavaScript**:
```typescript
const info = await egressClient.stopEgress(egressID);
```
---
**Go**:
```go
info, err := egressClient.StopEgress(ctx, &livekit.StopEgressRequest{
EgressId: egressID,
})
```
---
**Ruby**:
```ruby
egressClient.stop_egress('egress-id')
```
---
**Java**:
```java
try {
egressClient.stopEgress("egressId").execute();
} catch (IOException e) {
// handle exception
}
```
---
**LiveKit CLI**:
```shell
lk egress stop --id
```
## Types
### AudioMixing
Enum, valid values are as follows:
| Name | Value | Description |
| `DEFAULT_MIXING` | 0 | all users are mixed together |
| `DUAL_CHANNEL_AGENT` | 1 | agent audio in the left channel, all other audio in the right channel |
| `DUAL_CHANNEL_ALTERNATE` | 2 | each new audio track alternates between left and right channels |
### EncodedFileOutput
| Field | Type | Description |
| `filepath` | string | default {room_name}-{time} |
| `disable_manifest` | bool | by default, Egress outputs a {filepath}.json with metadata of the file |
| `s3` | [S3Upload](#S3Upload) | set if uploading to S3 compatible storage. only one storage output can be set |
| `gcp` | [GCPUpload](#GCPUpload) | set if uploading to GCP |
| `azure` | [AzureBlobUpload](#AzureBlobUpload) | set if uploading to Azure |
| `aliOSS` | [AliOSSUpload](#AliOSSUpload) | set if uploading to AliOSS |
### DirectFileOutput
| Field | Type | Description |
| `filepath` | string | default {track_id}-{time} |
| `disable_manifest` | bool | by default, Egress outputs a {filepath}.json with metadata of the file |
| `s3` | [S3Upload](#S3Upload) | set if uploading to S3 compatible storage. only one storage output can be set |
| `gcp` | [GCPUpload](#GCPUpload) | set if uploading to GCP |
| `azure` | [AzureBlobUpload](#AzureBlobUpload) | set if uploading to Azure |
| `aliOSS` | [AliOSSUpload](#AliOSSUpload) | set if uploading to AliOSS |
### SegmentedFileOutput
| Field | Type | Description |
| `filename_prefix` | string | prefix used in each segment (include any paths here) |
| `playlist_name` | string | name of the m3u8 playlist. when empty, matches filename_prefix |
| `segment_duration` | uint32 | length of each segment (defaults to 4s) |
| `filename_suffix` | SegmentedFileSuffix | INDEX (1, 2, 3) or TIMESTAMP (in UTC) |
| `disable_manifest` | bool | |
| `s3` | [S3Upload](#S3Upload) | set if uploading to S3 compatible storage. only one storage output can be set |
| `gcp` | [GCPUpload](#GCPUpload) | set if uploading to GCP |
| `azure` | [AzureBlobUpload](#AzureBlobUpload) | set if uploading to Azure |
| `aliOSS` | [AliOSSUpload](#AliOSSUpload) | set if uploading to AliOSS |
### StreamOutput
| Field | Type | Description |
| `protocol` | SreamProtocol | (optional) only RTMP is supported |
| `urls` | string[] | list of URLs to send stream to |
### ImageOutput
| Field | Type | Description |
| `capture_interval` | uint32 | time in seconds between each snapshot |
| `width` | int32 | width of the snapshot images (optional, the original width will be used if not provided) |
| `height` | int32 | height of the snapshot images (optional, the original width will be used if not provided) |
| `filename_prefix` | string | prefix used in each image filename (include any paths here) |
| `filename_suffix` | ImageFileSuffix | INDEX (1, 2, 3) or TIMESTAMP (in UTC) |
| `image_codec` | ImageCodec | IC_DEFAULT or IC_JPEG (optional, both options will cause JPEGs to be generated currently) |
| `disable_manifest` | bool | by default, Egress outputs a {filepath}.json with a list of exported snapshots |
| `s3` | [S3Upload](#S3Upload) | set if uploading to S3 compatible storage. only one storage output can be set |
| `gcp` | [GCPUpload](#GCPUpload) | set if uploading to GCP |
| `azure` | [AzureBlobUpload](#AzureBlobUpload) | set if uploading to Azure |
| `aliOSS` | [AliOSSUpload](#AliOSSUpload) | set if uploading to AliOSS |
### S3Upload
| Field | Type | Description |
| `access_key` | string | |
| `secret` | string | S3 secret key |
| `bucket` | string | destination bucket |
| `region` | string | region of the S3 bucket (optional) |
| `endpoint` | string | URL to use for S3 (optional) |
| `force_path_style` | bool | leave bucket in the path and never to sub-domain (optional) |
| `metadata` | map | metadata key/value pairs to store (optional) |
| `tagging` | string | (optional) |
| `proxy` | [ProxyConfig](#ProxyConfig) | Proxy server to use when uploading(optional) |
### GCPUpload
| Field | Type | Description |
| `credentials` | string | Contents of credentials.json |
| `bucket` | string | destination bucket |
| `proxy` | [ProxyConfig](#ProxyConfig) | Proxy server to use when uploading(optional) |
### AzureBlobUpload
| Field | Type | Description |
| `account_name` | string | |
| `account_key` | string | |
| `container_name` | string | destination container |
### AliOSSUpload
| Field | Type | Description |
| `access_key` | string | |
| `secret` | string | |
| `bucket` | string | |
| `region` | string | |
| `endpoint` | string | |
### EncodingOptions
| Field | Type | Description |
| `width` | int32 | |
| `height` | int32 | |
| `depth` | int32 | default 24 |
| `framerate` | int32 | default 30 |
| `audio_codec` | AudioCodec | default AAC |
| `audio_bitrate` | int32 | 128 |
| `audio_frequency` | int32 | 44100 |
| `video_codec` | VideoCodec | default H264_MAIN |
| `video_bitrate` | int32 | default 4500 |
| `key_frame_interval` | int32 | default 4s |
### EncodingOptionsPreset
Enum, valid values:
| `H264_720P_30` | 0 |
| `H264_720P_60` | 1 |
| `H264_1080P_30` | 2 |
| `H264_1080P_60` | 3 |
| `PORTRAIT_H264_720P_30` | 4 |
| `PORTRAIT_H264_720P_60` | 5 |
| `PORTRAIT_H264_1080P_30` | 6 |
| `PORTRAIT_H264_1080P_60` | 7 |
### ProxyConfig
For S3 and GCP, you can specify a proxy server for Egress to use when uploading files.
This can be helpful to avoid network restrictions on the destination buckets.
| Field | Type | Description |
| `url` | string | URL of the proxy |
| `username` | string | username for basic auth (optional) |
| `password` | string | password for basic auth (optional) |
### WebhookConfig
Extra webhooks can be configured for a specific Egress request. These webhooks are called for Egress lifecycle events in addition to the project wide webhooks. To learn more, see [Webhooks](https://docs.livekit.io/intro/basics/rooms-participants-tracks/webhooks-events.md).
| Field | Type | Description |
| `url` | string | URL of the webhook |
| `signing_key` | string | API key to use to sign the request, must be defined for the project |
---
This document was rendered at 2026-02-03T03:25:21.755Z.
For the latest version of this document, see [https://docs.livekit.io/reference/other/egress/api.md](https://docs.livekit.io/reference/other/egress/api.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/home/cloud/architecture.md
LiveKit docs › LiveKit Cloud › Architecture
---
# LiveKit Cloud Architecture
> LiveKit Cloud gives you the flexibility of LiveKit's WebRTC stack, combined with global, CDN-scale infrastructure offering 99.99% uptime.
## Built with LiveKit SFU
[LiveKit Cloud](https://livekit.io/cloud) builds on our open-source [SFU](https://github.com/livekit/livekit). This means it supports the exact same SDKs and APIs as the open-source [stack](https://github.com/livekit).
Maintaining compatibility with LiveKit's Open Source stack (OSS) is important to us. We didn't want any developer locked into using LiveKit Cloud, or needing to integrate a different set of features, APIs or SDKs for their applications to work with it. Our design goal: a developer should be able to switch between LiveKit Cloud or self-hosted without changing a line of code.
## Distributed Mesh Architecture
In contrast to traditional [WebRTC architectures](https://docs.livekit.io/reference/internals/livekit-sfu.md), LiveKit Cloud runs multiple SFU instances in a mesh formation. We've developed capabilities for media servers to discover and connect to one another, in order to relay media between servers. This key capability allows us to bypass the single-server limitation that exists in traditional SFU and MCU architectures.
### Multi-home

With a multi-home architecture, participants no longer need to connect to the same server. When participants from different regions join the same meeting, they'll each connect to the SFU closest to them, minimizing latency and transmission loss between the participant and SFU.
Each SFU instance establishes connections to other instances over optimized inter-data center networks. Inter-data center networks often run close to internet backbones, delivering high throughput with a minimal number of network hops.
### No SPOF
Anything that can fail, will. LiveKit Cloud is designed to anticipate (and recover from) failures in every software and hardware component.
Layers of redundancy are built into the system. A media server failure is recovered from by moving impacted participants to another instance. We isolate shared infrastructure, like our message bus, to individual data centers.
When an entire data center fails, customer traffic is automatically migrated to the next closest data center. LiveKit's SDKs will perform a "session migration": moving existing WebRTC sessions to a different media server without service interruption for your users.
### Globally distributed
To serve end users around the world, our infrastructure runs across multiple cloud vendors and data centers, delivering under 100ms of latency in each region. Today, we have data centers in the following regions:
- North America (US East, US Central, US West)
- South America (Brazil)
- Oceania (Australia)
- East Asia (Japan)
- Southeast Asia (Singapore)
- South Asia (India)
- Middle East (Israel, Saudi Arabia, UAE)
- Africa (South Africa)
- Europe (France, Germany, UK)
### Designed to scale
When you need to support many viewers on a media track, such as in a livestream, LiveKit Cloud dynamically manages that capacity by forming a distribution mesh, similar to a CDN. This process occurs automatically as your session scales, with no special configurations required. Every LiveKit Cloud project scales seamlessly to accommodate millions of concurrent users in any session.

For a deeper look into the design decisions we've made for LiveKit Cloud, you can [read more](https://blog.livekit.io/scaling-webrtc-with-distributed-mesh/) on our blog.
---
This document was rendered at 2025-11-18T23:54:58.510Z.
For the latest version of this document, see [https://docs.livekit.io/home/cloud/architecture.md](https://docs.livekit.io/home/cloud/architecture.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/stt/plugins/assemblyai.md
# Source: https://docs.livekit.io/agents/models/stt/inference/assemblyai.md
LiveKit docs › Models › STT › Inference › AssemblyAI
---
# AssemblyAI STT
> Reference for AssemblyAI STT in LiveKit Inference.
## Overview
LiveKit Inference offers transcription powered by AssemblyAI. Pricing information is available on the [pricing page](https://livekit.io/pricing/inference#stt).
| Model name | Model ID | Languages |
| -------- | -------- | --------- |
| Universal-Streaming | `assemblyai/universal-streaming` | `en`, `en-US` |
| Universal-Streaming-Multilingual | `assemblyai/universal-streaming-multilingual` | `en`, `en-US`, `en-GB`, `en-AU`, `en-CA`, `en-IN`, `en-NZ`, `es`, `es-ES`, `es-MX`, `es-AR`, `es-CO`, `es-CL`, `es-PE`, `es-VE`, `es-EC`, `es-GT`, `es-CU`, `es-BO`, `es-DO`, `es-HN`, `es-PY`, `es-SV`, `es-NI`, `es-CR`, `es-PA`, `es-UY`, `es-PR`, `fr`, `fr-FR`, `fr-CA`, `fr-BE`, `fr-CH`, `de`, `de-DE`, `de-AT`, `de-CH`, `it`, `it-IT`, `it-CH`, `pt`, `pt-BR`, `pt-PT` |
## Usage
To use AssemblyAI, pass a descriptor with the model and language to the `stt` argument in your `AgentSession`:
**Python**:
```python
from livekit.agents import AgentSession
session = AgentSession(
stt="assemblyai/universal-streaming:en",
# ... tts, stt, vad, turn_detection, etc.
)
```
---
**Node.js**:
```typescript
import { AgentSession } from '@livekit/agents';
session = new AgentSession({
stt: "assemblyai/universal-streaming:en",
// ... tts, stt, vad, turn_detection, etc.
});
```
### Parameters
To customize additional parameters, use the `STT` class from the `inference` module:
**Python**:
```python
from livekit.agents import AgentSession, inference
session = AgentSession(
stt=inference.STT(
model="assemblyai/universal-streaming",
language="en"
),
# ... tts, stt, vad, turn_detection, etc.
)
```
---
**Node.js**:
```typescript
import { AgentSession, inference } from '@livekit/agents';
session = new AgentSession({
stt: new inference.STT({
model: "assemblyai/universal-streaming",
language: "en"
}),
// ... tts, stt, vad, turn_detection, etc.
});
```
- **`model`** _(string)_: The model to use for the STT.
- **`language`** _(string)_ (optional): Language code for the transcription. If not set, the provider default applies.
- **`extra_kwargs`** _(dict)_ (optional): Additional parameters to pass to the AssemblyAI Universal Streaming API, including `format_turns`, `end_of_turn_confidence_threshold`, `min_end_of_turn_silence_when_confident`, `max_turn_silence`, and `keyterms_prompt`. See the provider's [documentation](#additional-resources) for more information.
In Node.js this parameter is called `modelOptions`.
## Turn detection
AssemblyAI includes a custom phrase endpointing model that uses both audio and linguistic information to detect turn boundaries. To use this model for [turn detection](https://docs.livekit.io/agents/build/turns.md), set `turn_detection="stt"` in the `AgentSession` constructor. You should also provide a VAD plugin for responsive interruption handling.
```python
session = AgentSession(
turn_detection="stt",
stt=inference.STT(
model="assemblyai/universal-streaming",
language="en"
),
vad=silero.VAD.load(), # Recommended for responsive interruption handling
# ... llm, tts, etc.
)
```
## Additional resources
The following links provide more information about AssemblyAI in LiveKit Inference.
- **[AssemblyAI Plugin](https://docs.livekit.io/agents/models/stt/plugins/assemblyai.md)**: Plugin to use your own AssemblyAI account instead of LiveKit Inference.
- **[AssemblyAI docs](https://www.assemblyai.com/docs/speech-to-text/universal-streaming)**: AssemblyAI's official documentation.
---
This document was rendered at 2026-02-03T03:25:02.318Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/stt/inference/assemblyai.md](https://docs.livekit.io/agents/models/stt/inference/assemblyai.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/tts/plugins/asyncai.md
LiveKit docs › Models › TTS › Plugins › AsyncAI
---
# AsyncAI TTS plugin guide
> How to use the AsyncAI TTS plugin for LiveKit Agents.
Available in:
- [ ] Node.js
- [x] Python
## Overview
This plugin allows you to use [AsyncAI](https://async.com/ai-voices) as a TTS provider for your voice agents.
## Quick reference
This section provides a quick reference for the AsyncAI TTS plugin. For more information, see [Additional resources](#additional-resources).
### Installation
Install the plugin from PyPI:
**Python**:
```shell
uv add "livekit-agents[asyncai]~=1.3"
```
### Authentication
The AsyncAI plugin requires a [AsyncAI API key](https://docs.async.com/getting-started-with-the-async-voice-api-990331m0#get-your-api-key).
Set `ASYNCAI_API_KEY` in your `.env` file.
### Usage
Use AsyncAI TTS within an `AgentSession` or as a standalone speech generator. For example, you can use this TTS in the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
**Python**:
```python
from livekit.plugins import asyncai
session = AgentSession(
tts=asyncai.TTS(
model="asyncflow_multilingual_v1.0",
)
# ... llm, stt, etc.
)
```
### Parameters
This section describes some of the parameters you can set when you create a AsyncAI TTS. See the plugin reference links in the [Additional resources](#additional-resources) section for a complete list of all available parameters.
- **`model`** _(str | TTSModels)_ (optional) - Default: `asyncflow_multilingual_v1.0`: The AsyncAI TTS model to use. Defaults to "asyncflow_multilingual_v1.0". To learn more, see the [AsyncAI documentation](https://docs.async.com/text-to-speech-stream-16699696e0).
- **`voice`** _(str)_ (optional) - Default: `e0f39dc4-f691-4e78-bba5-5c636692cc04`: Voice identifier to use for generation. See the [voice library](https://async.com/developer/voice-library) for available voice IDs.
- **`language`** _(str)_ (optional) - Default: `None`: The language code for synthesis. To learn more, see the list of supported language codes for `language` in the [AsyncAI documentation](https://docs.async.com/text-to-speech-stream-16699696e0).
## Additional resources
The following resources provide more information about using AsyncAI with LiveKit Agents.
- **[AsyncAI docs](https://docs.async.com/welcome-to-async-voice-api-990330m0)**: AsyncAI TTS docs.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Get started with LiveKit Agents and AsyncAI TTS.
---
This document was rendered at 2026-02-03T03:25:04.974Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/tts/plugins/asyncai.md](https://docs.livekit.io/agents/models/tts/plugins/asyncai.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/multimodality/audio.md
LiveKit docs › Multimodality › Speech & audio
---
# Agent speech and audio
> Speech and audio capabilities for LiveKit agents.
## Overview
Speech capabilities are a core feature of LiveKit agents, enabling them to interact with users through voice. This guide covers the various speech features and functionalities available for agents.
LiveKit Agents provide a unified interface for controlling agents using both the STT-LLM-TTS pipeline and realtime models.
To learn more and see usage examples, see the following topics:
- **[Text-to-speech (TTS)](https://docs.livekit.io/agents/models/tts.md)**: TTS is a synthesis process that converts text into audio, giving AI agents a "voice."
- **[Speech-to-speech](https://docs.livekit.io/agents/models/realtime.md)**: Multimodal, realtime APIs can understand speech input and generate speech output directly.
## Instant connect
The instant connect feature reduces perceived connection time by capturing microphone input before the agent connection is established. This pre-connect audio buffer sends speech as context to the agent, avoiding awkward gaps between a user's connection and their ability to interact with an agent.
Microphone capture begins locally while the agent is connecting. Once the connection is established, the speech and metadata is sent over a byte stream with the topic `lk.agent.pre-connect-audio-buffer`. If no agent connects before timeout, the buffer is discarded.
You can enable this feature using `withPreconnectAudio`:
**JavaScript**:
In the Javascript SDK, this functionality is exposed via `TrackPublishOptions`.
```typescript
await room.localParticipant.setMicrophoneEnabled(!enabled, undefined, {
preConnectBuffer: true,
});
```
---
**Swift**:
```swift
try await room.withPreConnectAudio(timeout: 10) {
try await room.connect(url: serverURL, token: token)
} onError: { err in
print("Pre-connect audio send failed:", err)
}
```
---
**Android**:
```kotlin
try {
room.withPreconnectAudio {
// Audio is being captured automatically
// Perform other async setup
val (url, token) = tokenService.fetchConnectionDetails()
room.connect(
url = url,
token = token,
)
room.localParticipant.setMicrophoneEnabled(true)
}
} catch (e: Throwable) {
Log.e(TAG, "Error!")
}
```
---
**Flutter**:
```dart
try {
await room.withPreConnectAudio(() async {
// Audio is being captured automatically, perform other async setup
// Get connection details from token service etc.
final connectionDetails = await tokenService.fetchConnectionDetails();
await room.connect(
connectionDetails.serverUrl,
connectionDetails.participantToken,
);
// Mic already enabled
});
} catch (error) {
print("Error: $error");
}
```
## Preemptive speech generation
**Preemptive generation** allows the agent to begin generating a response before the user's end of turn is committed. The response is based on partial transcription or early signals from user input, helping reduce perceived response delay and improving conversational flow.
When enabled, the agent starts generating a response as soon as the final transcript is available. If the chat context or tools change in the `on_user_turn_completed` [node](https://docs.livekit.io/agents/build/nodes.md#on_user_turn_completed), the preemptive response is canceled and replaced with a new one based on the final transcript.
This feature reduces latency when the following are true:
- [STT node](https://docs.livekit.io/agents/build/nodes.md#stt_node) returns the final transcript faster than [VAD](https://docs.livekit.io/agents/logic/turns/vad.md) emits the `end_of_speech` event.
- [Turn detection model](https://docs.livekit.io/agents/logic/turns/turn-detector.md) is enabled.
You can enable this feature for STT-LLM-TTS pipeline agents using the `preemptive_generation` parameter for AgentSession:
**Python**:
```python
session = AgentSession(
preemptive_generation=True,
... # STT, LLM, TTS, etc.
)
```
---
**Node.js**:
```typescript
const session = new voice.AgentSession({
// ... llm, stt, etc.
voiceOptions: {
preemptiveGeneration: true,
},
});
```
> ℹ️ **Note**
>
> Preemptive generation doesn't guarantee reduced latency. Use [Agent observability](https://docs.livekit.io/deploy/observability/insights.md) to validate and fine tune agent performance.
## Initiating speech
By default, the agent waits for user input before responding—the Agents framework automatically handles response generation.
In some cases, though, the agent might need to initiate the conversation. For example, it might greet the user at the start of a session or check in after a period of silence.
### session.say
To have the agent speak a predefined message, use `session.say()`. This triggers the configured TTS to synthesize speech and play it back to the user.
You can also optionally provide pre-synthesized audio for playback. This skips the TTS step and reduces response time.
> 💡 **Realtime models and TTS**
>
> The `say` method requires a TTS plugin. If you're using a realtime model, you need to add a TTS plugin to your session or use the [`generate_reply()`](#manually-interrupt-and-generate-responses) method instead.
**Python**:
```python
await session.say(
"Hello. How can I help you today?",
allow_interruptions=False,
)
```
---
**Node.js**:
```typescript
await session.say(
'Hello. How can I help you today?',
{
allowInterruptions: false,
}
);
```
#### Parameters
You can call `session.say()` with the following options:
- `text` only: Synthesizes speech using TTS, which is added to the transcript and chat context (unless `add_to_chat_ctx=False`).
- `audio` only: Plays audio, which is not added to the transcript or chat context.
- `text` + `audio`: Plays the provided audio and the `text` is used for the transcript and chat context.
- **`text`** _(str | AsyncIterable[str])_ (optional): Text for TTS playback, added to the transcript and by default to the chat context.
- **`audio`** _(AsyncIterable[rtc.AudioFrame])_ (optional): Pre-synthesized audio to play. If used without `text`, nothing is added to the transcript or chat context.
- **`allow_interruptions`** _(boolean)_ (optional) - Default: `True`: If `True`, allow the user to interrupt the agent while speaking.
- **`add_to_chat_ctx`** _(boolean)_ (optional) - Default: `True`: If `True`, add the text to the agent's chat context after playback. Has no effect if `text` is not provided.
#### Returns
Returns a [`SpeechHandle`](#speechhandle) object.
#### Events
This method triggers a [`speech_created`](https://docs.livekit.io/reference/other/events.md#speech_created) event.
### generate_reply
To make conversations more dynamic, use `session.generate_reply()` to prompt the LLM to generate a response.
There are two ways to use `generate_reply`:
1. give the agent instructions to generate a response
**Python**:
```python
session.generate_reply(
instructions="greet the user and ask where they are from",
)
```
---
**Node.js**:
```typescript
session.generateReply({
instructions: 'greet the user and ask where they are from',
});
```
2. provide the user's input via text
**Python**:
```python
session.generate_reply(
user_input="how is the weather today?",
)
```
---
**Node.js**:
```typescript
session.generateReply({
userInput: 'how is the weather today?',
});
```
> ℹ️ **Impact to chat history**
>
> When using `generate_reply` with `instructions`, the agent uses the instructions to generate a response, which is added to the chat history. The instructions themselves are not recorded in the history.
>
> In contrast, `user_input` is directly added to the chat history.
#### Parameters
- **`user_input`** _(string)_ (optional): The user input to respond to.
- **`instructions`** _(string)_ (optional): Instructions for the agent to use for the reply.
- **`allow_interruptions`** _(boolean)_ (optional): If `True`, allow the user to interrupt the agent while speaking. (default `True`)
#### Returns
Returns a [`SpeechHandle`](#speechhandle) object.
#### Events
This method triggers a [`speech_created`](https://docs.livekit.io/reference/other/events.md#speech_created) event.
## Controlling agent speech
You can control agent speech using the `SpeechHandle` object returned by the `say()` and `generate_reply()` methods, and allowing user interruptions.
### SpeechHandle
The `say()` and `generate_reply()` methods return a `SpeechHandle` object, which lets you track the state of the agent's speech. This can be useful for coordinating follow-up actions—for example, notifying the user before ending the call.
**Python**:
```python
# The following is a shortcut for:
# handle = session.say("Goodbye for now.", allow_interruptions=False)
# await handle.wait_for_playout()
await session.say("Goodbye for now.", allow_interruptions=False)
```
---
**Node.js**:
```typescript
// The following is a shortcut for:
// const handle = session.say('Goodbye for now.', { allowInterruptions: false });
// await handle.waitForPlayout();
await session.say('Goodbye for now.', { allowInterruptions: false });
```
You can wait for the agent to finish speaking before continuing:
**Python**:
```python
handle = session.generate_reply(instructions="Tell the user we're about to run some slow operations.")
# perform an operation that takes time
...
await handle # finally wait for the speech
```
---
**Node.js**:
```typescript
const handle = session.generateReply({
instructions: "Tell the user we're about to run some slow operations."
});
// perform an operation that takes time
...
await handle.waitForPlayout(); // finally wait for the speech
```
The following example makes a web request for the user, and cancels the request when the user interrupts:
**Python**:
```python
async with aiohttp.ClientSession() as client_session:
web_request = client_session.get('https://api.example.com/data')
handle = await session.generate_reply(instructions="Tell the user we're processing their request.")
if handle.interrupted:
# if the user interrupts, cancel the web_request too
web_request.cancel()
```
---
**Node.js**:
```typescript
import { Task } from '@livekit/agents';
const webRequestTask = Task.from(async (controller) => {
const response = await fetch('https://api.example.com/data', {
signal: controller.signal
});
return response.json();
});
const handle = session.generateReply({
instructions: "Tell the user we're processing their request.",
});
await handle.waitForPlayout();
if (handle.interrupted) {
// if the user interrupts, cancel the web_request too
webRequestTask.cancel();
}
```
`SpeechHandle` has an API similar to `ayncio.Future`, allowing you to add a callback:
**Python**:
```python
handle = session.say("Hello world")
handle.add_done_callback(lambda _: print("speech done"))
```
---
**Node.js**:
```typescript
const handle = session.say('Hello world');
handle.then(() => console.log('speech done'));
```
### Getting the current speech handle
Available in:
- [ ] Node.js
- [x] Python
The agent session's active speech handle, if any, is available with the `current_speech` property. If no speech is active, this property returns `None`. Otherwise, it returns the active `SpeechHandle`.
Use the active speech handle to coordinate with the speaking state. For instance, you can ensure that a hang up occurs only after the current speech has finished, rather than mid-speech:
```python
# to hang up the call as part of a function call
@function_tool
async def end_call(self, ctx: RunContext):
"""Use this tool when the user has signaled they wish to end the current call. The session ends automatically after invoking this tool."""
await ctx.wait_for_playout() # let the agent finish speaking
# call API to delete_room
...
```
### Interruptions
By default, the agent stops speaking when it detects that the user has started speaking. You can customize this behavior. To learn more, see [Interruptions](https://docs.livekit.io/agents/build/turns.md#interruptions) in the Turn detection topic.
## Customizing pronunciation
Most TTS providers allow you to customize pronunciation of words using Speech Synthesis Markup Language (SSML). The following example uses the [tts_node](https://docs.livekit.io/agents/build/nodes.md#tts_node) to add custom pronunciation rules:
** Filename: `agent.py`**
```python
async def tts_node(
self,
text: AsyncIterable[str],
model_settings: ModelSettings
) -> AsyncIterable[rtc.AudioFrame]:
# Pronunciation replacements for common technical terms and abbreviations.
# Support for custom pronunciations depends on the TTS provider.
pronunciations = {
"API": "A P I",
"REST": "rest",
"SQL": "sequel",
"kubectl": "kube control",
"AWS": "A W S",
"UI": "U I",
"URL": "U R L",
"npm": "N P M",
"LiveKit": "Live Kit",
"async": "a sink",
"nginx": "engine x",
}
async def adjust_pronunciation(input_text: AsyncIterable[str]) -> AsyncIterable[str]:
async for chunk in input_text:
modified_chunk = chunk
# Apply pronunciation rules
for term, pronunciation in pronunciations.items():
# Use word boundaries to avoid partial replacements
modified_chunk = re.sub(
rf'\b{term}\b',
pronunciation,
modified_chunk,
flags=re.IGNORECASE
)
yield modified_chunk
# Process with modified text through base TTS implementation
async for frame in Agent.default.tts_node(
self,
adjust_pronunciation(text),
model_settings
):
yield frame
```
** Filename: `Required imports`**
```python
import re
from livekit import rtc
from livekit.agents.voice import ModelSettings
from livekit.agents import tts
from typing import AsyncIterable
```
** Filename: `agent.ts`**
```typescript
async ttsNode(
text: ReadableStream,
modelSettings: voice.ModelSettings,
): Promise | null> {
// Pronunciation replacements for common technical terms and abbreviations.
// Support for custom pronunciations depends on the TTS provider.
const pronunciations = {
API: 'A P I',
REST: 'rest',
SQL: 'sequel',
kubectl: 'kube control',
AWS: 'A W S',
UI: 'U I',
URL: 'U R L',
npm: 'N P M',
LiveKit: 'Live Kit',
async: 'a sink',
nginx: 'engine x',
};
const adjustPronunciation = (inputText: ReadableStream): ReadableStream => {
return new ReadableStream({
async start(controller) {
const reader = inputText.getReader();
try {
while (true) {
const { done, value: chunk } = await reader.read();
if (done) break;
let modifiedChunk = chunk;
// Apply pronunciation rules
for (const [term, pronunciation] of Object.entries(pronunciations)) {
// Use word boundaries to avoid partial replacements
const regex = new RegExp(`\\b${term}\\b`, 'gi');
modifiedChunk = modifiedChunk.replace(regex, pronunciation);
}
controller.enqueue(modifiedChunk);
}
} finally {
reader.releaseLock();
controller.close();
}
},
});
};
// Process with modified text through base TTS implementation
return voice.Agent.default.ttsNode(this, adjustPronunciation(text), modelSettings);
}
```
** Filename: `Required imports`**
```typescript
import type { AudioFrame } from '@livekit/rtc-node';
import { ReadableStream } from 'stream/web';
import { voice } from '@livekit/agents';
```
The following table lists the SSML tags supported by most TTS providers:
| SSML Tag | Description |
| `phoneme` | Used for phonetic pronunciation using a standard phonetic alphabet. These tags provide a phonetic pronunciation for the enclosed text. |
| `say as` | Specifies how to interpret the enclosed text. For example, use `character` to speak each character individually, or `date` to specify a calendar date. |
| `lexicon` | A custom dictionary that defines the pronunciation of certain words using phonetic notation or text-to-pronunciation mappings. |
| `emphasis` | Speak text with an emphasis. |
| `break` | Add a manual pause. |
| `prosody` | Controls pitch, speaking rate, and volume of speech output. |
## Adjusting speech volume
To adjust the volume of the agent's speech, add a processor to the `tts_node` or the `realtime_audio_output_node`. Alternative, you can also [adjust the volume of playback](https://docs.livekit.io/transport/media/subscribe.md#volume) in the frontend SDK.
The following example agent has an adjustable volume between 0 and 100, and offers a [tool call](https://docs.livekit.io/agents/build/tools.md) to change it.
** Filename: `agent.py`**
```python
class Assistant(Agent):
def __init__(self) -> None:
self.volume: int = 50
super().__init__(
instructions=f"You are a helpful voice AI assistant. Your starting volume level is {self.volume}."
)
@function_tool()
async def set_volume(self, volume: int):
"""Set the volume of the audio output.
Args:
volume (int): The volume level to set. Must be between 0 and 100.
"""
self.volume = volume
# Audio node used by STT-LLM-TTS pipeline models
async def tts_node(self, text: AsyncIterable[str], model_settings: ModelSettings):
return self._adjust_volume_in_stream(
Agent.default.tts_node(self, text, model_settings)
)
# Audio node used by realtime models
async def realtime_audio_output_node(
self, audio: AsyncIterable[rtc.AudioFrame], model_settings: ModelSettings
) -> AsyncIterable[rtc.AudioFrame]:
return self._adjust_volume_in_stream(
Agent.default.realtime_audio_output_node(self, audio, model_settings)
)
async def _adjust_volume_in_stream(
self, audio: AsyncIterable[rtc.AudioFrame]
) -> AsyncIterable[rtc.AudioFrame]:
stream: utils.audio.AudioByteStream | None = None
async for frame in audio:
if stream is None:
stream = utils.audio.AudioByteStream(
sample_rate=frame.sample_rate,
num_channels=frame.num_channels,
samples_per_channel=frame.sample_rate // 10, # 100ms
)
for f in stream.push(frame.data):
yield self._adjust_volume_in_frame(f)
if stream is not None:
for f in stream.flush():
yield self._adjust_volume_in_frame(f)
def _adjust_volume_in_frame(self, frame: rtc.AudioFrame) -> rtc.AudioFrame:
audio_data = np.frombuffer(frame.data, dtype=np.int16)
audio_float = audio_data.astype(np.float32) / np.iinfo(np.int16).max
audio_float = audio_float * max(0, min(self.volume, 100)) / 100.0
processed = (audio_float * np.iinfo(np.int16).max).astype(np.int16)
return rtc.AudioFrame(
data=processed.tobytes(),
sample_rate=frame.sample_rate,
num_channels=frame.num_channels,
samples_per_channel=len(processed) // frame.num_channels,
)
```
** Filename: `Required imports`**
```python
import numpy as np
from typing import AsyncIterable
from livekit.agents import Agent, function_tool, utils
from livekit.plugins import rtc
```
** Filename: `agent.ts`**
```typescript
class Assistant extends voice.Agent {
private volume = 50;
constructor(initialVolume: number) {
super({
instructions: `You are a helpful voice AI assistant. Your starting volume level is ${initialVolume}.`,
tools: {
setVolume: llm.tool({
description: 'Set the volume of the audio output.',
parameters: z.object({
volume: z
.number()
.min(0)
.max(100)
.describe('The volume level to set. Must be between 0 and 100.'),
}),
execute: async ({ volume }) => {
this.volume = volume;
return `Volume set to ${volume}`;
},
}),
},
});
this.volume = initialVolume;
}
// Audio node used by STT-LLM-TTS pipeline models
async ttsNode(
text: ReadableStream,
modelSettings: voice.ModelSettings,
): Promise | null> {
const baseStream = await voice.Agent.default.ttsNode(this, text, modelSettings);
if (!baseStream) return null;
return this.adjustVolumeInStream(baseStream);
}
// Audio node used by realtime models
async realtimeAudioOutputNode(
audio: ReadableStream,
modelSettings: voice.ModelSettings,
): Promise | null> {
const baseStream = await voice.Agent.default.realtimeAudioOutputNode(
this,
audio,
modelSettings,
);
if (!baseStream) return null;
return this.adjustVolumeInStream(baseStream);
}
private adjustVolumeInStream(
audioStream: ReadableStream,
): ReadableStream {
return new ReadableStream({
start: async (controller) => {
const reader = audioStream.getReader();
try {
while (true) {
const { done, value: frame } = await reader.read();
if (done) break;
const adjustedFrame = this.adjustVolumeInFrame(frame);
controller.enqueue(adjustedFrame);
}
} finally {
reader.releaseLock();
controller.close();
}
},
});
}
private adjustVolumeInFrame(frame: AudioFrame): AudioFrame {
const audioData = new Int16Array(frame.data);
const volumeMultiplier = Math.max(0, Math.min(this.volume, 100)) / 100.0;
const processedData = new Int16Array(audioData.length);
for (let i = 0; i < audioData.length; i++) {
const floatSample = audioData[i]! / 32767.0;
const adjustedSample = floatSample * volumeMultiplier;
processedData[i] = Math.round(adjustedSample * 32767.0);
}
return new AudioFrame(processedData, frame.sampleRate, frame.channels, frame.samplesPerChannel);
}
}
```
** Filename: `Required imports`**
```typescript
import { voice } from '@livekit/agents';
import { AudioFrame } from '@livekit/rtc-node';
import { ReadableStream } from 'stream/web';
```
## Adding background audio
To add more realism to your agent, or add additional sound effects, publish background audio. This audio is played on a separate audio track. The `BackgroundAudioPlayer` class supports on-demand playback of custom audio as well as automatic ambient and thinking sounds synchronized to the agent lifecycle.
For a complete example, see the following recipes:
- **[Background audio](https://github.com/livekit/agents/blob/main/examples/voice_agents/background_audio.py)**: A voice AI agent with background audio for thinking states and ambiance.
- **[Background audio example in Node.js](https://github.com/livekit/agents-js/blob/main/examples/src/background_audio.ts)**: A voice AI agent with background audio for ambiance.
### Create the player
The `BackgroundAudioPlayer` class manages audio playback to a room. It can also play ambient and thinking sounds automatically during the lifecycle of the agent session, if desired.
- **`ambient_sound`** _(AudioSource | AudioConfig | list[AudioConfig])_ (optional): Ambient sound plays on a loop in the background during the agent session. See [Supported audio sources](#audio-sources) and [Multiple audio clips](#multiple-audio-clips) for more details.
- **`thinking_sound`** _(AudioSource | AudioConfig | list[AudioConfig])_ (optional): Thinking sound plays while the agent is in the "thinking" state. See [Supported audio sources](#audio-sources) and [Multiple audio clips](#multiple-audio-clips) for more details. This parameter is currently [only supported](#background-audio-limitations) in Python.
Create the player within your entrypoint function:
**Python**:
```python
from livekit.agents import BackgroundAudioPlayer, AudioConfig, BuiltinAudioClip
# An audio player with automated ambient and thinking sounds
background_audio = BackgroundAudioPlayer(
ambient_sound=AudioConfig(BuiltinAudioClip.OFFICE_AMBIENCE, volume=0.8),
thinking_sound=[
AudioConfig(BuiltinAudioClip.KEYBOARD_TYPING, volume=0.8),
AudioConfig(BuiltinAudioClip.KEYBOARD_TYPING2, volume=0.7),
],
)
# An audio player with a custom ambient sound played on a loop
background_audio = BackgroundAudioPlayer(
ambient_sound="/path/to/my-custom-sound.mp3",
)
# An audio player for on-demand playback only
background_audio = BackgroundAudioPlayer()
```
---
**Node.js**:
```typescript
import { voice } from '@livekit/agents';
const backgroundAudio = new voice.BackgroundAudioPlayer({
ambientSound: {
source: voice.BuiltinAudioClip.OFFICE_AMBIENCE,
volume: 0.8,
},
// Thinking sounds are not yet supported in Node.js
});
# An audio player with a custom ambient sound played on a loop
backgroundAudio = new voice.BackgroundAudioPlayer({
ambientSound: "/path/to/my-custom-sound.mp3",
})
# An audio player for on-demand playback only
backgroundAudio = new voice.BackgroundAudioPlayer()
```
### Start and stop the player
Call the `start` method after room connection and after starting the agent session. Ambient sounds, if any, begin playback immediately.
- `room`: The room to publish the audio to.
- `agent_session`: The agent session to publish the audio to.
**Python**:
```python
await background_audio.start(room=ctx.room, agent_session=session)
```
---
**Node.js**:
```typescript
await backgroundAudio.start({ room: ctx.room, agentSession: session });
```
To stop and clean up the player, call the `aclose` (or `close` in Node.js) method. You must create a new player instance if you want to start again.
**Python**:
```python
await background_audio.aclose()
```
---
**Node.js**:
```typescript
await backgroundAudio.close();
```
### Play audio on-demand
You can play audio at any time, after starting the player, with the `play` method.
- **`audio`** _(AudioSource | AudioConfig | list[AudioConfig])_: The audio source or a probabilistic list of sources to play. To learn more, see [Supported audio sources](#audio-sources) and [Multiple audio clips](#multiple-audio-clips).
- **`loop`** _(boolean)_ (optional) - Default: `False`: Set to `True` to continuously loop playback.
For example, if you created `background_audio` in the [previous example](#publishing-background-audio), you can play an audio file like this:
**Python**:
```python
background_audio.play("/path/to/my-custom-sound.mp3")
```
---
**Node.js**:
```typescript
backgroundAudio.play("/path/to/my-custom-sound.mp3");
```
The `play` method returns a `PlayHandle` which you can use to await or cancel the playback.
The following example uses the handle to await playback completion:
**Python**:
```python
# Wait for playback to complete
await background_audio.play("/path/to/my-custom-sound.mp3")
```
---
**Node.js**:
```typescript
const handle = await backgroundAudio.play("/path/to/my-custom-sound.mp3");
```
The next example shows the handle's `stop` method, which stops playback early:
**Python**:
```python
handle = background_audio.play("/path/to/my-custom-sound.mp3")
await(asyncio.sleep(1))
handle.stop() # Stop playback early
```
---
**Node.js**:
```typescript
const handle = backgroundAudio.play("/path/to/my-custom-sound.mp3");
await new Promise(resolve => setTimeout(resolve, 1000));
handle.stop(); // Stop playback early
```
### Multiple audio clips
You can pass a list of audio sources to any of `play`, `ambient_sound`, or `thinking_sound`. The player selects a single entry in the list based on the `probability` parameter. This is useful to avoid repetitive sound effects. To allow for the possibility of no audio at all, ensure the sum of the probabilities is less than 1.
`AudioConfig` has the following properties:
- **`source`** _(AudioSource)_: The audio source to play. See [Supported audio sources](#audio-sources) for more details.
- **`volume`** _(float)_ (optional) - Default: `1`: The volume at which to play the given audio.
- **`probability`** _(float)_ (optional) - Default: `1`: The relative probability of selecting this audio source from the list.
**Python**:
```python
# Play the KEYBOARD_TYPING sound with an 80% probability and the KEYBOARD_TYPING2 sound with a 20% probability
background_audio.play([
AudioConfig(BuiltinAudioClip.KEYBOARD_TYPING, volume=0.8, probability=0.8),
AudioConfig(BuiltinAudioClip.KEYBOARD_TYPING2, volume=0.7, probability=0.2),
])
```
---
**Node.js**:
```typescript
// Play the KEYBOARD_TYPING sound with an 80% probability and the KEYBOARD_TYPING2 sound with a 20% probability
backgroundAudio.play([
{ source: voice.BuiltinAudioClip.KEYBOARD_TYPING, volume: 0.8, probability: 0.8 },
{ source: voice.BuiltinAudioClip.KEYBOARD_TYPING2, volume: 0.7, probability: 0.2 },
])
```
### Supported audio sources
The following audio sources are supported:
#### Local audio file
Pass a string path to any local audio file. The player decodes files with FFmpeg via [PyAV](https://github.com/PyAV-Org/PyAV) and supports all common audio formats including MP3, WAV, AAC, FLAC, OGG, Opus, WebM, and MP4.
> 💡 **WAV files**
>
> The player uses an optimized custom decoder to load WAV data directly to audio frames, without the overhead of FFmpeg. For small files, WAV is the highest-efficiency option.
#### Built-in audio clips
The following built-in audio clips are available by default for common sound effects:
- `BuiltinAudioClip.OFFICE_AMBIENCE`: Chatter and general background noise of a busy office.
- `BuiltinAudioClip.KEYBOARD_TYPING`: The sound of an operator typing on a keyboard, close to their microphone.
- `BuiltinAudioClip.KEYBOARD_TYPING2`: A shorter version of `KEYBOARD_TYPING`.
#### Raw audio frames
Pass an `AsyncIterator[rtc.AudioFrame]` to play raw audio frames from any source.
#### Limitations
Thinking sounds are not yet supported in Node.js.
## Additional resources
To learn more, see the following resources.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Use the quickstart as a starting base for adding audio code.
- **[Speech related event](https://docs.livekit.io/agents/build/events.md#speech_created)**: Learn more about the `speech_created` event, triggered when new agent speech is created.
- **[LiveKit SDK](https://docs.livekit.io/transport/media/publish.md#publishing-audio-tracks)**: Learn how to use the LiveKit SDK to play audio tracks.
- **[Background audio](https://github.com/livekit/agents/blob/main/examples/voice_agents/background_audio.py)**: A voice AI agent with background audio for thinking states and ambiance.
- **[Background audio example in Node.js](https://github.com/livekit/agents-js/blob/main/examples/src/background_audio.ts)**: A voice AI agent with background audio for ambiance.
- **[Text-to-speech (TTS)](https://docs.livekit.io/agents/models/tts.md)**: TTS models for pipeline agents.
- **[Speech-to-speech](https://docs.livekit.io/agents/models/realtime.md)**: Realtime models that understand speech input and generate speech output directly.
---
This document was rendered at 2026-02-03T03:24:55.092Z.
For the latest version of this document, see [https://docs.livekit.io/agents/multimodality/audio.md](https://docs.livekit.io/agents/multimodality/audio.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/frontends/authentication.md
LiveKit docs › Authentication › Overview
---
# Authentication overview
> An overview of authentication for LiveKit frontends.
## Overview
LiveKit uses JWT-based access tokens to authenticate users and control access to rooms. Generate tokens on your backend server to encode participant identity, room permissions, and capabilities.
## Authentication components
Learn how to generate tokens and understand their structure, grants, and permissions.
| Component | Description | Use cases |
| **Tokens** | Reference documentation and tutorials for creating access tokens, configuring grants, and managing permissions. | Token generation, understanding token structure, configuring video and SIP grants, and setting up room configuration. |
## In this section
Read more about authentication components.
- **[Tokens](https://docs.livekit.io/frontends/authentication/tokens.md)**: Creating access tokens, configuring grants, and managing permissions.
---
This document was rendered at 2026-02-03T03:25:09.039Z.
For the latest version of this document, see [https://docs.livekit.io/frontends/authentication.md](https://docs.livekit.io/frontends/authentication.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/media/ingress-egress/egress/autoegress.md
LiveKit docs › Media › Stream export & import › Egress › Auto egress
---
# Auto egress
> Automatically start recording with a room.
## Start recordings automatically
To record every track published to the room or to start recording the room as soon as it's created, you can use auto egress.
Auto egress is designed to simplify these workflows. When a room is created with `CreateRoom`, you can set the `egress` field to have it automatically record the room as a composite as well as each published track separately.
## Examples
### Automatically record all tracks to S3
```shell
curl -X POST /twirp/livekit.RoomService/CreateRoom \
-H "Authorization: Bearer " \
-H 'Content-Type: application/json' \
--data-binary @- << EOF
{
"name": "my-room",
"egress": {
"tracks": {
"filepath": "bucket-path/{room_name}-{publisher_identity}-{time}"
"s3": {
"access_key": "",
"secret": "",
"bucket": "mybucket",
"region": "",
}
}
}
}
EOF
```
### Record each room to HLS on GCP
```shell
curl -X POST /twirp/livekit.RoomService/CreateRoom \
-H "Authorization: Bearer " \
-H 'Content-Type: application/json' \
--data-binary @- << EOF
{
"name": "my-room",
"egress": {
"room": {
"customBaseUrl": "https://your-template-url"
"segments": {
"filename_prefix": "path-in-bucket/myfile",
"segment_duration": 3,
"gcp": {
"credentials": "",
"bucket": "mybucket"
}
}
}
}
}
EOF
```
---
This document was rendered at 2026-02-03T03:25:17.674Z.
For the latest version of this document, see [https://docs.livekit.io/transport/media/ingress-egress/egress/autoegress.md](https://docs.livekit.io/transport/media/ingress-egress/egress/autoegress.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/avatar.md
LiveKit docs › Models › Virtual avatar › Overview
---
# Virtual avatar models overview
> Guides for adding virtual avatars to your agents.
## Overview
Virtual avatars add lifelike video output for your voice AI agents. You can integrate a variety of providers to LiveKit Agents with just a few lines of code.
### Plugins
The following plugins are available. Choose a plugin from this list for a step-by-step guide:
| Provider | Python | Node.js |
| -------- | ------ | ------- |
| [Anam](https://docs.livekit.io/agents/models/avatar/plugins/anam.md) | ✓ | ✓ |
| [Beyond Presence](https://docs.livekit.io/agents/models/avatar/plugins/bey.md) | ✓ | ✓ |
| [bitHuman](https://docs.livekit.io/agents/models/avatar/plugins/bithuman.md) | ✓ | — |
| [Hedra](https://docs.livekit.io/agents/models/avatar/plugins/hedra.md) | ✓ | — |
| [LemonSlice](https://docs.livekit.io/agents/models/avatar/plugins/lemonslice.md) | ✓ | — |
| [LiveAvatar](https://docs.livekit.io/agents/models/avatar/plugins/liveavatar.md) | ✓ | — |
| [Simli](https://docs.livekit.io/agents/models/avatar/plugins/simli.md) | ✓ | — |
| [Tavus](https://docs.livekit.io/agents/models/avatar/plugins/tavus.md) | ✓ | — |
Have another provider in mind? LiveKit is open source and welcomes [new plugin contributions](https://docs.livekit.io/agents/models.md#contribute).
## Usage
The virtual avatar plugins work with the `AgentSession` class automatically. The plugin adds a separate participant, the avatar worker, to the room. The agent session sends its audio output to the avatar worker instead of to the room, which the avatar worker uses to publish synchronized audio + video tracks to the room and the end user.
To add a virtual avatar:
1. Install the selected plugin and API keys
2. Create an `AgentSession`, as in the [voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)
3. Create an `AvatarSession` and configure it as necessary
4. Start the avatar session, passing in the `AgentSession` instance
5. Start the `AgentSession` with audio output disabled (the audio is sent to the avatar session instead)
### Sample code
Here is an example using [Hedra Realtime Avatars](https://docs.livekit.io/agents/models/avatar/plugins/hedra.md):
```python
from livekit import agents
from livekit.agents import AgentServer, AgentSession
from livekit.plugins import hedra
server = AgentServer()
@server.rtc_session()
async def my_agent(ctx: agents.JobContext):
session = AgentSession(
# ... stt, llm, tts, etc.
)
avatar = hedra.AvatarSession(
avatar_id="...", # ID of the Hedra avatar to use
)
# Start the avatar and wait for it to join
await avatar.start(session, room=ctx.room)
# Start your agent session with the user
await session.start(
# ... room, agent, room_options, etc....
)
```
## Avatar workers
To minimize latency, the avatar provider joins the LiveKit room directly as a secondary participant to publish synchronized audio and video to the room. In your frontend app, you must distinguish between the agent — your Python program running the `AgentSession` — and the avatar worker.
```mermaid
graph LR
User[User] --"User Audio"--> Agent[Agent]
Agent -."Audio Data".-> Avatar[Avatar Worker]
Avatar --"Agent Video"--> User
Avatar --"Agent Audio"--> User
```
You can identify an avatar worker as a participant of kind `agent` with the attribute `lk.publish_on_behalf`. Check for these values in your frontend code to associate the worker's audio and video tracks with the agent.
```typescript
const agent = room.remoteParticipants.find(
p => p.kind === Kind.Agent && p.attributes['lk.publish_on_behalf'] === null
);
const avatarWorker = room.remoteParticipants.find(
p => p.kind === Kind.Agent && p.attributes['lk.publish_on_behalf'] === agent.identity
);
```
In React apps, use the [useVoiceAssistant hook](https://docs.livekit.io/reference/components/react/hook/usevoiceassistant.md) to get the correct audio and video tracks automatically:
```typescript
const {
agent, // The agent participant
audioTrack, // the worker's audio track
videoTrack, // the worker's video track
} = useVoiceAssistant();
```
## Frontend starter apps
The following [frontend starter apps](https://docs.livekit.io/agents/start/frontend.md#starter-apps) include out-of-the-box support for virtual avatars.
- **[SwiftUI Voice Agent](https://github.com/livekit-examples/agent-starter-swift)**: A native iOS, macOS, and visionOS voice AI assistant built in SwiftUI.
- **[Next.js Voice Agent](https://github.com/livekit-examples/agent-starter-react)**: A web voice AI assistant built with React and Next.js.
- **[Flutter Voice Agent](https://github.com/livekit-examples/agent-starter-flutter)**: A cross-platform voice AI assistant app built with Flutter.
- **[React Native Voice Agent](https://github.com/livekit-examples/agent-starter-react-native)**: A native voice AI assistant app built with React Native and Expo.
- **[Android Voice Agent](https://github.com/livekit-examples/agent-starter-android)**: A native Android voice AI assistant app built with Kotlin and Jetpack Compose.
- **[Agents Playground](https://docs.livekit.io/agents/start/playground.md)**: A virtual workbench to test your multimodal AI agent.
## Additional resources
- **[Web and mobile frontends](https://docs.livekit.io/agents/start/frontend.md)**: Guide to adding web or mobile frontends to your agent.
- **[Vision](https://docs.livekit.io/agents/build/vision.md)**: Give your agent the ability to see you, too.
---
This document was rendered at 2026-02-03T03:25:06.722Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/avatar.md](https://docs.livekit.io/agents/models/avatar.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/stt/plugins/aws.md
# Source: https://docs.livekit.io/agents/models/tts/plugins/aws.md
# Source: https://docs.livekit.io/agents/models/llm/plugins/aws.md
# Source: https://docs.livekit.io/agents/integrations/aws.md
# Source: https://docs.livekit.io/agents/models/tts/plugins/aws.md
# Source: https://docs.livekit.io/agents/models/stt/plugins/aws.md
# Source: https://docs.livekit.io/agents/models/llm/plugins/aws.md
LiveKit docs › Models › LLM › Plugins › AWS
---
# Amazon Bedrock LLM plugin guide
> How to use the Amazon Bedrock LLM plugin for LiveKit Agents.
Available in:
- [ ] Node.js
- [x] Python
## Overview
This plugin allows you to use [Amazon Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) as a LLM provider for your voice agents.
## Quick reference
This section includes a basic usage example and some reference material. For links to more detailed documentation, see [Additional resources](#additional-resources).
### Installation
Install the plugin from PyPI:
```shell
uv add "livekit-agents[aws]~=1.3"
```
### Authentication
The AWS plugin requires AWS credentials. Set the following environment variables in your `.env` file:
```shell
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
```
### Usage
Use Bedrock within an `AgentSession` or as a standalone LLM service. For example, you can use this LLM in the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
```python
from livekit.plugins import aws
session = AgentSession(
llm=aws.LLM(
model="anthropic.claude-3-5-sonnet-20240620-v1:0",
temperature=0.8,
),
# ... tts, stt, vad, turn_detection, etc.
)
```
### Parameters
This section describes some of the available parameters. For a complete reference of all available parameters, see the [plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/aws/index.html.md#livekit.plugins.aws.LLM).
- **`model`** _(string | TEXT_MODEL)_ (optional) - Default: `anthropic.claude-3-5-sonnet-20240620-v1:0`: The model to use for the LLM. For more information, see the documentation for the `modelId` parameter in the [Amazon Bedrock API reference](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse_stream.html).
- **`region`** _(string)_ (optional) - Default: `us-east-1`: The region to use for AWS API requests.
- **`temperature`** _(float)_ (optional): Controls the randomness of the model's output. Higher values, for example 0.8, make the output more random, while lower values, for example 0.2, make it more focused and deterministic.
Default values vary depending on the model you select. To learn more, see [Inference request parameters and response fields for foundation models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters.html).
- **`tool_choice`** _([ToolChoice | Literal['auto', 'required', 'none']])_ (optional) - Default: `auto`: Controls how the model uses tools. Set to 'auto' to let the model decide, 'required' to force tool usage, or 'none' to disable tool usage.
## Amazon Nova Sonic
To use Amazon Nova Sonic on AWS Bedrock, refer to the following integration guide:
- **[Amazon Nova Sonic](https://docs.livekit.io/agents/models/realtime/plugins/nova-sonic.md)**: Integration guide for the Amazon Nova Sonic speech-to-speech model on AWS Bedrock.
## Additional resources
The following links provide more information about the Amazon Bedrock LLM plugin.
- **[Python package](https://pypi.org/project/livekit-plugins-aws/)**: The `livekit-plugins-aws` package on PyPI.
- **[Plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/aws/index.html.md#livekit.plugins.aws.LLM)**: Reference for the Amazon Bedrock LLM plugin.
- **[GitHub repo](https://github.com/livekit/agents/tree/main/livekit-plugins/livekit-plugins-aws)**: View the source or contribute to the LiveKit Amazon Bedrock LLM plugin.
- **[Bedrock docs](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html)**: Amazon Bedrock docs.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Get started with LiveKit Agents and Amazon Bedrock.
- **[AWS ecosystem guide](https://docs.livekit.io/agents/integrations/aws.md)**: Overview of the entire AWS and LiveKit Agents integration.
---
This document was rendered at 2026-02-03T03:24:59.609Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/llm/plugins/aws.md](https://docs.livekit.io/agents/models/llm/plugins/aws.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/stt/plugins/azure-openai.md
# Source: https://docs.livekit.io/agents/models/tts/plugins/azure-openai.md
# Source: https://docs.livekit.io/agents/models/llm/plugins/azure-openai.md
# Source: https://docs.livekit.io/agents/models/realtime/plugins/azure-openai.md
# Source: https://docs.livekit.io/agents/models/tts/plugins/azure-openai.md
# Source: https://docs.livekit.io/agents/models/stt/plugins/azure-openai.md
# Source: https://docs.livekit.io/agents/models/llm/plugins/azure-openai.md
LiveKit docs › Models › LLM › Plugins › Azure OpenAI
---
# Azure OpenAI LLM plugin guide
> How to use the Azure OpenAI LLM plugin for LiveKit Agents.
Available in:
- [x] Node.js
- [x] Python
## Overview
This plugin allows you to use [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) as a LLM provider for your voice agents.
> 💡 **LiveKit Inference**
>
> Azure OpenAI is also available in LiveKit Inference, with billing and integration handled automatically. See [the docs](https://docs.livekit.io/agents/models/llm/inference/openai.md) for more information.
## Quick reference
This section includes a basic usage example and some reference material. For links to more detailed documentation, see [Additional resources](#additional-resources).
### Installation
Install the plugin:
**Python**:
```shell
uv add "livekit-agents[openai]~=1.3"
```
---
**Node.js**:
```shell
pnpm add @livekit/agents-plugin-openai@1.x
```
### Authentication
The Azure OpenAI plugin requires either an [Azure OpenAI API key](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource) or a Microsoft Entra ID token.
Set the following environment variables in your `.env` file:
- `AZURE_OPENAI_API_KEY` or `AZURE_OPENAI_ENTRA_TOKEN`
- `AZURE_OPENAI_ENDPOINT`
- `OPENAI_API_VERSION`
### Usage
Use Azure OpenAI within an `AgentSession` or as a standalone LLM service. For example, you can use this LLM in the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
**Python**:
```python
from livekit.plugins import openai
session = AgentSession(
llm=openai.LLM.with_azure(
azure_deployment="",
azure_endpoint="https://.openai.azure.com/", # or AZURE_OPENAI_ENDPOINT
api_key="", # or AZURE_OPENAI_API_KEY
api_version="2024-10-01-preview", # or OPENAI_API_VERSION
),
# ... tts, stt, vad, turn_detection, etc.
)
```
---
**Node.js**:
```typescript
import * as openai from '@livekit/agents-plugin-openai';
const session = new voice.AgentSession({
llm: openai.LLM.withAzure({
azureDeployment: "",
azureEndpoint: "https://.openai.azure.com/", // or AZURE_OPENAI_ENDPOINT
apiKey: "", // or AZURE_OPENAI_API_KEY
apiVersion: "2024-10-01-preview", // or OPENAI_API_VERSION
}),
// ... tts, stt, vad, turn_detection, etc.
});
```
### Parameters
This section describes the Azure-specific parameters. For a complete list of all available parameters, see the plugin reference links in the [Additional resources](#additional-resources) section.
- **`azure_deployment`** _(string)_: Name of your model deployment.
- **`entra_token`** _(string)_ (optional): Microsoft Entra ID authentication token. Required if not using API key authentication. To learn more see Azure's [Authentication](https://learn.microsoft.com/en-us/azure/ai-services/openai/realtime-audio-reference#authentication) documentation.
- **`temperature`** _(float)_ (optional) - Default: `0.1`: Controls the randomness of the model's output. Higher values, for example 0.8, make the output more random, while lower values, for example 0.2, make it more focused and deterministic.
Valid values are between `0` and `2`.
- **`parallel_tool_calls`** _(bool)_ (optional): Controls whether the model can make multiple tool calls in parallel. When enabled, the model can make multiple tool calls simultaneously, which can improve performance for complex tasks.
- **`tool_choice`** _(ToolChoice | Literal['auto', 'required', 'none'])_ (optional) - Default: `auto`: Controls how the model uses tools. Set to 'auto' to let the model decide, 'required' to force tool usage, or 'none' to disable tool usage.
## Additional resources
The following links provide more information about the Azure OpenAI LLM plugin.
- **[Azure OpenAI docs](https://learn.microsoft.com/en-us/azure/ai-services/openai/)**: Azure OpenAI service documentation.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Get started with LiveKit Agents and Azure OpenAI.
- **[Azure ecosystem overview](https://docs.livekit.io/agents/integrations/azure.md)**: Overview of the entire Azure AI ecosystem and LiveKit Agents integration.
---
This document was rendered at 2026-02-03T03:24:59.752Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/llm/plugins/azure-openai.md](https://docs.livekit.io/agents/models/llm/plugins/azure-openai.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/stt/plugins/azure.md
# Source: https://docs.livekit.io/agents/models/tts/plugins/azure.md
# Source: https://docs.livekit.io/agents/integrations/azure.md
# Source: https://docs.livekit.io/agents/models/tts/plugins/azure.md
# Source: https://docs.livekit.io/agents/models/stt/plugins/azure.md
LiveKit docs › Models › STT › Plugins › Azure
---
# Azure Speech STT plugin guide
> How to use the Azure Speech STT plugin for LiveKit Agents.
Available in:
- [ ] Node.js
- [x] Python
## Overview
This plugin allows you to use [Azure Speech](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/overview) as an STT provider for your voice agents.
## Quick reference
This section provides a brief overview of the Azure Speech STT plugin. For more information, see [Additional resources](#additional-resources).
### Installation
Install the plugin from PyPI:
```shell
uv add "livekit-agents[azure]~=1.3"
```
### Authentication
The Azure Speech plugin requires an [Azure Speech key](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/get-started-speech-to-text?tabs=macos,terminal&pivots=programming-language-python#prerequisites).
Set the following environment variables in your `.env` file:
```shell
AZURE_SPEECH_KEY=
AZURE_SPEECH_REGION=
AZURE_SPEECH_HOST=
```
### Usage
Use Azure Speech STT in an `AgentSession` or as a standalone transcription service. For example, you can use this STT in the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
```python
from livekit.plugins import azure
azure_stt = stt.STT(
speech_key="",
speech_region="",
)
```
> ℹ️ **Note**
>
> To create an instance of `azure.STT`, one of the following options must be met:
>
> - `speech_host` must be set, _or_
> - `speech_key` _and_ `speech_region` must both be set, _or_
> - `speech_auth_token` _and_ `speech_region` must both be set
### Parameters
This section describes some of the available parameters. See the [plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/azure/index.html.md#livekit.plugins.azure.STT) for a complete list of all available parameters.
- **`speech_key`** _(string)_ (optional) - Environment: `AZURE_SPEECH_KEY`: Azure Speech speech-to-text key. To learn more, see [Azure Speech prerequisites](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/get-started-speech-to-text?tabs=macos,terminal&pivots=programming-language-python#prerequisites).
- **`speech_region`** _(string)_ (optional) - Environment: `AZURE_SPEECH_REGION`: Azure Speech speech-to-text region. To learn more, see [Azure Speech prerequisites](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/get-started-speech-to-text#prerequisites).
- **`speech_host`** _(string)_ (optional) - Environment: `AZURE_SPEECH_HOST`: Azure Speech endpoint.
- **`speech_auth_token`** _(string)_ (optional): Azure Speech authentication token.
- **`languages`** _(list[string])_ (optional): List of potential source languages. To learn more, see [Standard locale names](https://learn.microsoft.com/en-us/globalization/locale/standard-locale-names).
## Additional resources
The following resources provide more information about using Azure Speech with LiveKit Agents.
- **[Python package](https://pypi.org/project/livekit-plugins-azure/)**: The `livekit-plugins-azure` package on PyPI.
- **[Plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/azure/index.html.md#livekit.plugins.azure.STT)**: Reference for the Azure Speech STT plugin.
- **[GitHub repo](https://github.com/livekit/agents/tree/main/livekit-plugins/livekit-plugins-azure)**: View the source or contribute to the LiveKit Azure Speech STT plugin.
- **[Azure Speech docs](https://learn.microsoft.com/en-us/azure/ai-services/speech-service/overview)**: Azure Speech's full docs site.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Get started with LiveKit Agents and Azure Speech.
- **[Azure ecosystem guide](https://docs.livekit.io/agents/integrations/azure.md)**: Overview of the entire Azure AI and LiveKit Agents integration.
---
This document was rendered at 2026-02-03T03:25:02.972Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/stt/plugins/azure.md](https://docs.livekit.io/agents/models/stt/plugins/azure.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/tts/plugins/baseten.md
# Source: https://docs.livekit.io/agents/models/stt/plugins/baseten.md
# Source: https://docs.livekit.io/agents/models/llm/plugins/baseten.md
LiveKit docs › Models › LLM › Plugins › Baseten
---
# Baseten LLM plugin guide
> How to use the Baseten LLM plugin for LiveKit Agents.
Available in:
- [ ] Node.js
- [x] Python
## Overview
This plugin allows you to use [Baseten](https://www.baseten.co/) as an LLM provider for your voice agents.
> 💡 **LiveKit Inference**
>
> Some Baseten models are also available in LiveKit Inference, with billing and integration handled automatically. See [the docs](https://docs.livekit.io/agents/models/llm.md) for more information.
## Quick reference
This section includes a basic usage example and some reference material. For links to more detailed documentation, see [Additional resources](#additional-resources).
### Installation
Install the plugin from PyPI:
```shell
uv add "livekit-agents[baseten]~=1.3"
```
### Authentication
The Baseten plugin requires a [Baseten API key](https://app.baseten.co/settings/api-keys).
Set the following in your `.env` file:
```shell
BASETEN_API_KEY=
```
### Model selection
LiveKit Agents integrates with Baseten's Model API, which supports the most popular open source LLMs with per-token billing. To use the Model API, you only need to activate the model and then copy its name.
1. Activate your desired model in the [Model API](https://app.baseten.co/model-apis/create)
2. Copy its name from your model API endpoint dialog in your [model library](https://app.baseten.co/model-apis)
3. Use the model name in the plugin (e.g. `"openai/gpt-oss-120b"`)
### Usage
Use a Baseten LLM in your `AgentSession` or as a standalone LLM service. For example, you can use this LLM in the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
```python
from livekit.plugins import baseten
session = AgentSession(
llm=baseten.LLM(
model="openai/gpt-oss-120b"
),
# ... tts, stt, vad, turn_detection, etc.
)
```
### Parameters
This section describes some of the available parameters. For a complete reference of all available parameters, see the [plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/groq/services.html.md#livekit.plugins.groq.services.LLM).
- **`model`** _(string)_ (optional) - Default: `meta-llama/Llama-4-Maverick-17B-128E-Instruct`: Name of the LLM model to use from the [Model API](https://www.baseten.co/model-apis). See [Model selection](#model-selection) for more information.
## Additional resources
The following resources provide more information about using Baseten with LiveKit Agents.
- **[Python package](https://pypi.org/project/livekit-plugins-baseten/)**: The `livekit-plugins-baseten` package on PyPI.
- **[Plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/baseten/index.html.md#livekit.plugins.baseten.LLM)**: Reference for the Baseten LLM plugin.
- **[GitHub repo](https://github.com/livekit/agents/tree/main/livekit-plugins/livekit-plugins-baseten)**: View the source or contribute to the LiveKit Baseten LLM plugin.
- **[Baseten docs](https://docs.baseten.co/)**: Baseten docs.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Get started with LiveKit Agents and Baseten.
- **[Baseten TTS](https://docs.livekit.io/agents/models/tts/plugins/baseten.md)**: Baseten TTS integration guide.
- **[Baseten STT](https://docs.livekit.io/agents/models/stt/plugins/baseten.md)**: Baseten STT integration guide.
---
This document was rendered at 2026-02-03T03:24:59.888Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/llm/plugins/baseten.md](https://docs.livekit.io/agents/models/llm/plugins/baseten.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/intro/basics.md
LiveKit docs › Understanding LiveKit › Overview
---
# Understanding LiveKit overview
> An overview of the core concepts and fundamentals to get started with LiveKit.
## Overview
LiveKit is a realtime communication platform that enables you to build AI-native apps with audio, video, and data streaming capabilities. The topics in this section cover core concepts to help you connect to LiveKit, manage projects, and understand the basics of how LiveKit works.
LiveKit's architecture is built around rooms, participants, and tracks—virtual spaces where users and agents connect and share media and data across web, mobile, and embedded platforms. When you build agents with the [LiveKit Agents framework](https://docs.livekit.io/agents.md), they join rooms as participants, process realtime media and data streams, and interact with users through the same infrastructure that powers all LiveKit applications.
## Key concepts
The core concepts in this section can help you get started building LiveKit apps and agents.
### LiveKit CLI
The LiveKit CLI provides command-line tools for managing LiveKit Cloud projects, creating applications from templates, and streamlining your development workflow. Use the CLI to initialize projects, manage configurations, and deploy applications.
- **[LiveKit CLI overview](https://docs.livekit.io/intro/basics/cli.md)**: Learn how to use the LiveKit CLI to manage projects and create applications.
### LiveKit Cloud
LiveKit Cloud is a fully managed, globally distributed platform for building, hosting, and operating AI agent applications at scale. It combines realtime audio, video, and data streaming with agent development tools, managed agent hosting, built-in inference, native telephony, and production-grade observability in a single, cohesive platform.
- **[LiveKit Cloud](https://docs.livekit.io/intro/cloud.md)**: Learn about LiveKit Cloud's features, benefits, and how it compares to self-hosted deployments.
### Connecting to LiveKit
Connect your applications to LiveKit servers using access tokens, WebRTC connections, and platform-specific SDKs. Understanding how to establish and manage connections is essential for building realtime applications.
- **[Connecting to LiveKit](https://docs.livekit.io/intro/basics/connect.md)**: Learn how to connect your applications to LiveKit rooms and manage WebRTC connections.
### Rooms, participants, & tracks
Rooms, participants, and tracks are the fundamental building blocks of every LiveKit app. Rooms are virtual spaces where communication happens, participants are the entities that join rooms, and tracks are the media streams that flow between participants. Use webhooks and events to monitor and respond to changes in rooms, participants, and tracks.
- **[Rooms, participants, & tracks overview](https://docs.livekit.io/intro/basics/rooms-participants-tracks.md)**: Learn about the core building blocks of LiveKit applications.
### Building AI agents
Build AI agents that join LiveKit rooms as participants, process realtime media and data streams, and interact with users through voice, text, and vision. The LiveKit Agents framework provides everything you need to build production-ready voice AI agents and programmatic participants.
- **[Building AI agents](https://docs.livekit.io/intro/basics/agents.md)**: Learn how to build AI agents that join LiveKit rooms and interact with users through realtime media and data streams.
---
This document was rendered at 2026-02-03T03:24:51.447Z.
For the latest version of this document, see [https://docs.livekit.io/intro/basics.md](https://docs.livekit.io/intro/basics.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/self-hosting/benchmark.md
LiveKit docs › Self-hosting › Benchmarks
---
# Benchmarking
> Guide to load-testing and benchmarking your LiveKit installation.
## Measuring performance
LiveKit can scale to many simulteneous rooms by running a distributed setup across multiple nodes. However, each room must fit within a single node. For this reason, benchmarks below will be focused on stressing the number of concurrent users in a room.
With WebRTC SFUs, a few factors determine the amount of work a server could perform:
- Number of tracks published
- Number of subscribers
- Amount of data sent to each subscriber
An SFU needs to perform work to receive every track - this means receiving tens of packets per second. It then needs to forward that received data to every subscriber. That adds up to a significant amount of work in decryption and encryption, packet processing, and data forwarding.
Due to these variations, it can be difficult to understand the capacity of the SFU for an specific application. We provide tooling that help with simulating workload according to your specifications.
## Load testing
The LiveKit [CLI](https://github.com/livekit/livekit-cli) includes the `lk load-test` subcommand, which can simulate real-world loading conditions for various scenarios. It uses the Go SDK to simulate publishers and subscribers in a room.
When publishing, it could send both video and audio tracks:
- video: looping video clips at 720p, with keyframes every ~3s (simulcast enabled)
- audio: sends blank packets that aren't audible, but would simulate a target bitrate.
As a subscriber, it can simulate an application that takes advantage of adaptive stream, rendering a specified number of remote streams on-screen.
When benchmarking with the load tester, be sure to run it on a machine with plenty of CPU and bandwidth, and ensure it has sufficient file handles (`ulimit -n 65535`). You can also run the load tester from multiple machines.
> 🔥 **Caution**
>
> Load testing traffic on your cloud instance _will_ count toward your [quotas](https://docs.livekit.io/deploy/admin/quotas-and-limits.md), and is subject to the limits of your plan.
## Benchmarks
We've run benchmarks for a few common scenarios to give a general understanding of performance. All benchmarks below are to demonstrate max number of participants supported in a single room.
All benchmarks were ran with the server running on a 16-core, compute optimized instance on Google Cloud. ( `c2-standard-16`)
In the tables below:
- `Pubs` - Number of publishers
- `Subs` - Number of subscribers
### Audio only
This simulates an audio only experience with a large number of listeners in the room. It uses an average audio bitrate of 3kbps. In large audio sessions, only a small number of people are usually speaking (while everyone are on mute). We use 10 as the approximate number of speakers here.
| Use case | Pubs | Subs | Bytes/s in/out | Packets/s in/out | CPU utilization |
| Large audio rooms | 10 | 3000 | 7.3 kBps / 23 MBps | 305 / 959,156 | 80% |
Command:
```shell
lk load-test \
--url \
--api-key \
--api-secret \
--room load-test \
--audio-publishers 10 \
--subscribers 1000
```
### Video room
Default video resolution of 720p was used in the load tests.
| Use case | Pubs | Subs | Bytes/s in/out | Packets/s in/out | CPU utilization |
| Large meeting | 150 | 150 | 50 MBps / 93 MBps | 51,068 / 762,749 | 85% |
| Livestreaming | 1 | 3000 | 233 kBps / 531 MBps | 246 / 560,962 | 92% |
To simulate large meeting:
```shell
lk load-test \
--url \
--api-key \
--api-secret \
--room load-test \
--video-publishers 150 \
--subscribers 150
```
To simulate livestreaming:
```shell
lk load-test \
--url \
--api-key \
--api-secret \
--room load-test \
--video-publishers 1 \
--subscribers 3000 \
```
---
This document was rendered at 2026-02-03T03:25:21.212Z.
For the latest version of this document, see [https://docs.livekit.io/transport/self-hosting/benchmark.md](https://docs.livekit.io/transport/self-hosting/benchmark.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/avatar/plugins/bey.md
LiveKit docs › Models › Virtual avatar › Plugins › BEY
---
# Beyond Presence virtual avatar integration guide
> How to use the Beyond Presence virtual avatar plugin for LiveKit Agents.
Available in:
- [x] Node.js
- [x] Python
## Overview
[Beyond Presence](https://www.beyondpresence.ai/) provides hyper-realistic interactive avatars for conversational video AI agents. You can use the open source Beyond Presence integration for LiveKit Agents to add virtual avatars to your voice AI app.
## Quick reference
This section includes a basic usage example and some reference material. For links to more detailed documentation, see [Additional resources](#additional-resources).
### Installation
**Python**:
```shell
uv add "livekit-agents[bey]~=1.3"
```
---
**Node.js**:
```shell
pnpm add @livekit/agents-plugin-bey
```
### Authentication
The Beyond Presence plugin requires a [Beyond Presence API key](https://docs.bey.dev/api-key).
Set `BEY_API_KEY` in your `.env` file.
### Usage
Use the plugin in an `AgentSession`. For example, you can use this avatar in the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
**Python**:
```python
from livekit.plugins import bey
session = AgentSession(
# ... stt, llm, tts, etc.
)
avatar = bey.AvatarSession(
avatar_id="...", # ID of the Beyond Presence avatar to use
)
# Start the avatar and wait for it to join
await avatar.start(session, room=ctx.room)
# Start your agent session with the user
await session.start(
room=ctx.room,
)
```
---
**Node.js**:
```typescript
import { voice } from '@livekit/agents';
import * as bey from '@livekit/agents-plugin-bey';
const session = new voice.AgentSession({
// ... stt, llm, tts, etc.
});
const avatar = new bey.AvatarSession({
avatarId: "...", // ID of the Beyond Presence avatar to use
});
// Start the avatar and wait for it to join
await avatar.start(session, room);
// Start your agent session with the user
await session.start();
```
Preview the avatar in the [Agents Playground](https://docs.livekit.io/agents/start/playground.md) or a frontend [starter app](https://docs.livekit.io/agents/start/frontend.md#starter-apps) that you build.
### Parameters
This section describes some of the available parameters. See the [plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/bey/index.html.md#livekit.plugins.bey.AvatarSession) for a complete list of all available parameters.
- **`avatar_id`** _(string)_ (optional) - Default: `b9be11b8-89fb-4227-8f86-4a881393cbdb`: ID of the Beyond Presence avatar to use.
- **`avatar_participant_identity`** _(string)_ (optional) - Default: `bey-avatar-agent`: The identity of the participant to use for the avatar.
- **`avatar_participant_name`** _(string)_ (optional) - Default: `bey-avatar-agent`: The name of the participant to use for the avatar.
## Additional resources
The following resources provide more information about using Beyond Presence with LiveKit Agents.
- **[Beyond Presence docs](https://docs.bey.dev/docs)**: Beyond Presence's full docs site.
- **[Agents Playground](https://docs.livekit.io/agents/start/playground.md)**: A virtual workbench to test your avatar agent.
- **[Frontend starter apps](https://docs.livekit.io/agents/start/frontend.md#starter-apps)**: Ready-to-use frontend apps with avatar support.
---
This document was rendered at 2026-02-03T03:25:07.033Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/avatar/plugins/bey.md](https://docs.livekit.io/agents/models/avatar/plugins/bey.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/deploy/admin/billing.md
LiveKit docs › Administration › Billing
---
# LiveKit Cloud billing
> Guide to LiveKit Cloud invoices and billing cycles.
## Pricing
Refer to the following pages for current pricing information:
- **[LiveKit Cloud pricing](https://livekit.io/pricing)**: Current pricing, limits, and quotas for LiveKit Cloud plans.
- **[LiveKit Inference pricing](https://livekit.io/pricing/inference)**: Current pricing for LiveKit Inference models.
> ⚠️ **Prohibited usage**
>
> Attempting to manipulate or circumvent billing through any means violates the LiveKit [Terms of Service](https://livekit.io/legal/terms-of-service). If suspicious activity is detected, your account may be suspended or terminated.
## Resource metering
All LiveKit Cloud pricing plans include usage-based pricing, metered by resource consumption. The following sections provide more information about how each specific type of resource is metered. For information on quotas and limits, see the [Quotas and limits](https://docs.livekit.io/deploy/admin/quotas-and-limits.md) guide.
> ℹ️ **Rounding up**
>
> Each invididual resource usage is rounded up to the minimum increment prior to summation. For example, a connection lasting 10 seconds is billed as 1 connection minute, and one lasting 70 seconds is billed as 2 connection minutes.
### Realtime media and data
LiveKit Cloud transport services, including [WebRTC media](https://docs.livekit.io/transport/media.md), [telephony](https://docs.livekit.io/telephony.md), and [Stream import](https://docs.livekit.io/transport/media/ingress-egress/ingress.md), and [Recording and export](https://docs.livekit.io/transport/media/ingress-egress/ingress.md), are metered on a combination of **time** and **data transfer**. The following table shows the units and minimum increments for each resource.
| Resource type | Unit | Minimum increment |
| Time | Minute | 1 minute |
| Data transfer | GB | 0.01 GB |
### Agent deployment
Agents deployed to LiveKit Cloud are metered by the **agent session minute**, in increments of 1 minute. This reflects the amount of time the agent is actively connected to a WebRTC or SIP-based session.
Metering starts after the agent connects to the room. Metering stops when either the room ends or the agent disconnects, whichever occurs first. If an agent receives a job but never connects to the room, no metering occurs.
To explicitly end a session and stop metering, call `ctx.shutdown()` in your entrypoint function:
**Python**:
```python
async def entrypoint(ctx: JobContext):
try:
await ctx.connect()
# ... agent logic ...
except Exception as e:
logger.error(f"Error: {e}")
ctx.shutdown()
```
---
**Node.js**:
```typescript
export default defineAgent({
entry: async (ctx: JobContext) => {
try {
await ctx.connect();
// ... agent logic ...
} catch (e) {
logger.error(`Error: ${e}`);
ctx.shutdown();
}
},
});
```
### Agent observability
Agent observability is metered in two ways. First, by [events](https://docs.livekit.io/deploy/observability/insights.md#events), which include individual transcripts, observations, and logs. Second, by [recorded audio](https://docs.livekit.io/deploy/observability/insights.md#audio), in increments of 1 minute.
The following table shows the units and minimum increments for each resource.
| Resource type | Unit | Minimum increment |
| Transcripts, observations, and logs | Event | 1 event |
| Recorded audio | Minute | 1 minute |
### LiveKit Inference
LiveKit Inference usage is metered by **tokens**, **time**, or **characters**, depending on the specific resource, according to the following table.
| Model type | Unit | Minimum increment |
| STT | Seconds (connection time) | 1 second |
| LLM | Tokens (input and output) | 1 token |
| TTS | Characters (text) | 1 character |
### LiveKit Phone Numbers
LiveKit Phone Numbers are metered by the **minute** of inbound call time, plus a small fixed monthly fee per number. The following table shows the units and minimum increments for each resource.
| Resource type | Unit | Minimum increment |
| Inbound call time | Minute | 1 minute |
| Number rental | Monthly rental | 1 number |
## Invoices
LiveKit Cloud invoices are issued at the end of each month. The invoice total is based on resource consumption and the project's selected plan. No invoice is issued for projects with no amount due.
### Downloading invoices
Past monthly invoices are available on the project's [billing page](https://cloud.livekit.io/projects/p_/billing) for project admins. Click the **View Invoices** link in the **Statements** section to download the invoice.
---
This document was rendered at 2026-02-03T03:25:24.084Z.
For the latest version of this document, see [https://docs.livekit.io/deploy/admin/billing.md](https://docs.livekit.io/deploy/admin/billing.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/avatar/plugins/bithuman.md
LiveKit docs › Models › Virtual avatar › Plugins › Bithuman
---
# bitHuman virtual avatar integration guide
> How to use the bitHuman virtual avatar plugin for LiveKit Agents.
Available in:
- [ ] Node.js
- [x] Python
## Overview
[bitHuman](https://www.bithuman.ai/) provides realtime virtual avatars that you can run either locally or in the cloud. You can use the open source bitHuman integration for LiveKit Agents to add virtual avatars to your voice AI app.
## Quick reference
This section includes a basic usage example and some reference material. For links to more detailed documentation, see [Additional resources](#additional-resources).
### Installation
Install the plugin from PyPI:
```shell
uv add "livekit-agents[bithuman]~=1.3"
```
If you plan to use cloud-hosted models with images, also install the LiveKit images dependency, which includes Pillow version 10.3 and above:
```shell
uv add "livekit-agents[images]"
```
### Authentication
The bitHuman plugin requires a [bitHuman API Secret](https://imaginex.bithuman.ai/#api).
Set `BITHUMAN_API_SECRET` in your `.env` file.
### Avatar setup
The bitHuman plugin supports three ways to set up avatars:
- pass `.imx` model files
- pass an image directly using PIL image objects or a source image path/URL
- pass bitHuman avatar IDs
#### Pass model files
Create and download a bitHuman `.imx` file from the [bitHuman ImagineX console](https://imaginex.bithuman.ai). You can pass the model path to the avatar session or set the `BITHUMAN_MODEL_PATH` environment variable.
> ℹ️ **Note**
>
> Agents consume more CPU when using `.imx` models directly.
#### Pass image directly
Pass an image directly in the `avatar_image` parameter using PIL image objects or a source image path/URL.
```python
from PIL import Image
from livekit.plugins import bithuman
bithuman_avatar = bithuman.AvatarSession(
avatar_image=Image.open(os.path.join(os.path.dirname(__file__), "avatar.jpg")),
)
```
The image can come from anywhere, including your local filesystem, a remote URL, [uploaded in realtime from your frontend](https://docs.livekit.io/transport/data/byte-streams.md#sending-files) or generated by an external API or AI model.
#### Pass avatar ID
You can use an existing avatar by passing the `avatar_id` parameter to the plugin. You can find the ID in the [bitHuman ImagineX console](https://imaginex.bithuman.ai) in the description of the avatar on the **My Avatars** page.
### Usage
You can use the bitHuman plugin in an `AgentSession`. For example, you can use this avatar in the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
You can preview your agent in the [Agents Playground](https://docs.livekit.io/agents/start/playground.md) or a frontend [starter app](https://docs.livekit.io/agents/start/frontend.md#starter-apps) that you build.
The following code uses a local bitHuman `.imx` model.
```python
from livekit.plugins import bithuman
session = AgentSession(
# ... stt, llm, tts, etc.
)
avatar = bithuman.AvatarSession(
model_path="./albert_einstein.imx", # This example uses a demo model installed in the current directory
)
# Start the avatar and wait for it to join
await avatar.start(session, room=ctx.room)
# Start your agent session with the user
await session.start(
room=ctx.room,
)
```
The following code uses an image or avatar ID.
```python
from livekit.agents import room_io
from livekit.plugins import bithuman
from PIL import Image
avatar = bithuman.AvatarSession(
avatar_image=Image.open("avatar.jpg").convert("RGB"), # This example uses an image in the current directory.
# or: avatar_id="your-avatar-id" # You can also use an existing avatar ID.
)
await avatar.start(session, room=ctx.room)
await session.start(
room=ctx.room,
room_options=room_io.RoomOptions(audio_output=False),
)
```
### Parameters
This section describes some of the available parameters. See the [plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/bithuman/index.html.md#livekit.plugins.bithuman.AvatarSession) for a complete list of all available parameters.
- **`model`** _(string | Literal['essence', 'expression'])_: Model to use. `expression` provides dynamic expressions and emotional responses. `essence` uses predefined actions and expressions.
- **`model_path`** _(string)_ (optional) - Environment: `BITHUMAN_MODEL_PATH`: Path to the bitHuman `.imx` model.
- **`avatar_image`** _(PIL.Image.Image | str)_ (optional): Avatar image to use. Pass a PIL image (`Image.open("avatar.jpg")`) or a string (local path to the image).
- **`avatar_id`** _(string)_ (optional): The avatar ID from bitHuman.
## Additional resources
The following resources provide more information about using bitHuman with LiveKit Agents.
- **[Python package](https://pypi.org/project/livekit-plugins-bithuman/)**: The `livekit-plugins-bithuman` package on PyPI.
- **[Plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/bithuman.md)**: Reference for the bitHuman avatar plugin.
- **[GitHub repo](https://github.com/livekit/agents/tree/main/livekit-plugins/livekit-plugins-bithuman)**: View the source or contribute to the LiveKit bitHuman avatar plugin.
- **[bitHuman docs](https://sdk.docs.bithuman.ai)**: bitHuman's full API docs site.
- **[Agents Playground](https://docs.livekit.io/agents/start/playground.md)**: A virtual workbench to test your avatar agent.
- **[Frontend starter apps](https://docs.livekit.io/agents/start/frontend.md#starter-apps)**: Ready-to-use frontend apps with avatar support.
---
This document was rendered at 2026-02-03T03:25:07.197Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/avatar/plugins/bithuman.md](https://docs.livekit.io/agents/models/avatar/plugins/bithuman.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/build.md
LiveKit docs › Building voice agents › Overview
---
# Building voice agents
> In-depth guide to voice AI with LiveKit Agents.
## Overview
Building a great voice AI app requires careful orchestration of multiple components. LiveKit Agents is built on top of the [Realtime SDK](https://github.com/livekit/python-sdks) to provide dedicated abstractions that simplify development while giving you full control over the underlying code.
## Voice AI providers
You can choose from a variety of providers for each part of the voice pipeline to fit your needs. The framework supports both high-performance STT-LLM-TTS pipelines and speech-to-speech models. In either case, the framework automatically manages interruptions, transcription forwarding, turn detection, and more.
You may add these components to the `AgentSession`, where they act as global defaults within the app, or to each individual `Agent` if needed.
- **[TTS](https://docs.livekit.io/agents/models/tts.md)**: Text-to-speech models
- **[STT](https://docs.livekit.io/agents/models/stt.md)**: Speech-to-text models
- **[LLM](https://docs.livekit.io/agents/models/llm.md)**: Language model models
- **[Realtime](https://docs.livekit.io/agents/models/realtime.md)**: Realtime models
## Capabilities
The following guides, in addition to others in this section, cover the core capabilities of the `AgentSession` and how to leverage them in your app.
- **[Workflows](https://docs.livekit.io/agents/build/workflows.md)**: Core constructs for building complex voice AI workflows.
- **[Agent sessions](https://docs.livekit.io/agents/build/sessions.md)**: An agent session orchestrates your voice AI app's lifecycle.
- **[Agents & handoffs](https://docs.livekit.io/agents/build/agents-handoffs.md)**: Define agents and agent handoffs to build multi-agent voice AI workflows.
- **[Tool definition & use](https://docs.livekit.io/agents/build/tools.md)**: Use tools to call external services, inject custom logic, and more.
- **[Tasks & task groups](https://docs.livekit.io/agents/build/tasks.md)**: Use tasks and task groups to execute discrete operations and build complex workflows.
- **[Pipeline nodes](https://docs.livekit.io/agents/build/nodes.md)**: Add custom behavior to any component of the voice pipeline.
---
This document was rendered at 2025-11-18T23:55:02.766Z.
For the latest version of this document, see [https://docs.livekit.io/agents/build.md](https://docs.livekit.io/agents/build.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/start/builder.md
LiveKit docs › Get Started › Agent builder
---
# Agent Builder
> Prototype simple voice agents directly in your browser.
## Overview
The LiveKit Agent Builder lets you prototype and deploy simple voice agents through your browser, without writing any code. It's a great way to build a proof-of-concept, explore ideas, or stand up a working prototype quickly.
The agent builder produces best-practice Python code using the LiveKit Agents SDK, and deploys your agents directly to LiveKit Cloud. The result is an agent that is fully compatible with the rest of LiveKit Cloud, including [LiveKit Inference](https://docs.livekit.io/agents/models.md#inference), and [agent insights](https://docs.livekit.io/deploy/observability/insights.md), and [agent dispatch](https://docs.livekit.io/agents/server/agent-dispatch.md). You can continue iterating your agent in the builder, or convert it to code at any time to refine its behavior using [SDK-only features](#limitations).
Access the agent builder by selecting **Deploy new agent** in your project's [Agents dashboard](https://cloud.livekit.io/projects/p_/agents).
[Video: LiveKit Agents Builder](https://www.youtube.com/watch?v=FerHhAVELto)
## Agent features
The following provides a short overview of the features available to agents built in the agent builder.
### Agent name
The agent name is used for [explicit agent dispatch](https://docs.livekit.io/agents/server/agent-dispatch.md#explicit). Be careful if you change the name after deploying your agent, as it may break existing dispatch rules and frontends.
### Instructions
This is the most important component of any agent. You can write a single prompt for your agent, to control its identity and behavior. See the [prompting guide](https://docs.livekit.io/agents/start/prompting.md) for tips on how to write a good prompt. You can use [variables](#variables) to include dynamic information in your prompt.
### Welcome greeting
You can choose if your agent should greet the user when they join the call, or not. If you choose to have the agent greet the user, you can also write custom instructions for the greeting. The greeting also supports [variables](#variables) for dynamic content.
### Models
Your agents support most of the models available in [LiveKit Inference](https://docs.livekit.io/agents/models.md#inference) to construct a high-performance STT-LLM-TTS pipeline. Consult the documentation on [Speech-to-text](https://docs.livekit.io/agents/models/stt.md), [Large language models](https://docs.livekit.io/agents/models/llm.md), and [Text-to-speech](https://docs.livekit.io/agents/models/tts.md) for more details on support models and voices.
### Actions
Extend your agent's functionality with tools that allow your agent to interact with external systems and services. The agent builder supports three types of tools:
#### HTTP tools
HTTP tools call external APIs and services. HTTP tools support the following features:
- HTTP Method: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`
- Endpoint URL: The endpoint to call, with optional path parameters using a colon prefix, for example `:user_id`
- Parameters: Query parameters (`GET`) or JSON body (`POST`, `PUT`, `DELETE`, `PATCH`), with optional type and description.
- Headers: Optional HTTP headers for authentication or other purposes, with support for [secrets](#secrets) and [metadata](#variables).
- Silent: When enabled, hides the tool call result from the agent and prevents the agent from generating a response. Useful for tools that perform actions without needing acknowledgment.
#### Client tools
Client tools connect your agent to client-side RPC methods to retrieve data or perform actions. This is useful when the data needed to fulfill a function call is only available at the frontend, or when you want to trigger actions or UI updates in a structured way. Client tools support the following features:
- Description: The tool's purpose, outcomes, usage instructions, and examples.
- Parameters: Arguments passed by the LLM when the tool is called, with optional type and description.
- Preview response: A sample response returned by the client, used to help the LLM understand the expected return format.
- Silent: When enabled, hides the tool call result from the agent and prevents the agent from generating a response. Useful for tools that perform actions without needing acknowledgment.
See the [RPC documentation](https://docs.livekit.io/transport/data/rpc.md) for more information on implementing client-side RPC methods.
#### MCP servers
Configure external Model Context Protocol (MCP) servers for your agent to connect and interact with. MCP servers expose tools that your agent can discover and use automatically, and support both streaming HTTP and SSE protocols. MCP servers support the following features:
- Server name: A human-readable name for this MCP server.
- URL: The endpoint URL of the MCP server.
- Headers: Optional HTTP headers for authentication or other purposes, with support for [secrets](#secrets) and [metadata](#variables).
See the [tools documentation](https://docs.livekit.io/agents/build/tools.md#external-tools-and-mcp) for more information on MCP integration.
### Variables and metadata
Your agents automatically parse [Job metadata](https://docs.livekit.io/agents/server/job.md#metadata) as JSON and make the values available as variables in fields such as the instructions and welcome greeting. To add mock values for testing, and to add hints to the editor interface, define the metadata you intend to pass in the **Advanced** tab in the agent builder.
For instance, you can add a metadata field called `user_name`. When you dispatch the agent, include JSON `{"user_name": ""}` in the metadata field, populated by your frontend app. The agent can access this value in instructions or greeting using `{{metadata.user_name}}`.
### Secrets
Secrets are secure variables that can store sensitive information like API keys, database credentials, and authentication tokens. The agent builder uses the same [secrets store](https://docs.livekit.io/deploy/agents/secrets.md) as other LiveKit Cloud agents, and you can manage secrets in the same way.
Secrets are available as [variables](#variables) inside tool header values. For instance, if you have set a secret called `ACCESS_TOKEN`, then you can use add a tool header with the name `Authorization` and value `Bearer {{secrets.ACCESS_TOKEN}}`.
### End-of-call summary
Optionally summarize and report outcomes at the end of each call. When enabled, the agent automatically generates a summary of the conversation using the selected large language model (LLM) and sends it to the specified endpoint.
End-of-call summary configuration includes:
- Large language model (LLM): The language model used to generate the end-of-call summary.
- Summary endpoint URL: The endpoint to which the end-of-call summary will be sent.
- Headers: Optional HTTP headers for authentication or other purposes, with support for [secrets](#secrets) and [metadata](#variables).
### Other features
Your agent is built to use the following features, which are recommended for all voice agents built with LiveKit:
- [Background voice cancellation](https://docs.livekit.io/transport/media/enhanced-noise-cancellation.md) to improve agent comprehensision and reduce false interruptions.
- [Preemptive generation](https://docs.livekit.io/agents/build/speech.md#preemptive-generation) to improve agent responsiveness and reduce latency.
- [LiveKit turn detector](https://docs.livekit.io/agents/logic/turns/turn-detector.md) for best-in-class conversational behavior
## Agent preview
The agent builder includes a live preview mode to talk to your agent as you work on it. This is a great way to quickly test your agent's behavior and iterate on your prompt or try different models and voices. Changes made in the builder are automatically applied to the preview agent.
Sessions with the preview agent use your own project's LiveKit Inference credits, but do not otherwise count against LiveKit Cloud usage. They also do not appear in [Agent observability](https://docs.livekit.io/deploy/observability/insights.md) for your project.
## Deploying to production
To deploy your agent to production, click the **Deploy agent** button in the top right corner of the builder. Your agent is now deployed just like any other LiveKit Cloud agent. See the guides on [custom frontends](https://docs.livekit.io/agents/start/frontend.md) and [telephony integrations](https://docs.livekit.io/agents/start/telephony.md) for more information on how to connect your agent to your users.
## Test frontend
After your agent is deployed to production, you can test it in a frontend built on the LiveKit Cloud [Sandbox](https://docs.livekit.io/deploy/admin/sandbox.md) by clicking **Test Agent** in the top right corner of the builder. If you do not have this option, choose **Regenerate test app** from the dropdown menu to make it available.
This test frontend is a public URL that you can share with others to test your agent. More configuration for the test frontend is available in your project's [Sandbox settings](https://cloud.livekit.io/projects/p_/sandbox).
## Observing production sessions
After deploying your agent, you can observe production sessions in the [Agent insights](https://docs.livekit.io/deploy/observability/insights.md) tab in your [project's sessions dashboard](https://cloud.livekit.io/projects/p_/sessions).
## Convert to code
At any time, you can convert your agent to code by choosing the **Download code** button in the top right corner of the builder. This downloads a ZIP file containing a complete Python agent project, ready to [deploy with the LiveKit CLI](https://docs.livekit.io/deploy/agents.md). Once you have deployed the new agent, you should delete the old agent in the builder so it stops receiving requests.
The generated project includes a helpful README as well as an AGENTS.md file that includes best-practices and an integration with the [LiveKit Docs MCP Server](https://docs.livekit.io/intro/mcp-server.md) so that you can code in confidence with expert help from the coding assistant of your choice.
## Limitations
The agent builder is not intended to replace the LiveKit Agents SDK, but instead to make it easier to get started with voice agents which can be extended with custom code later after a proof-of-concept. The following are some of the agents SDK features that are not currently supported in the builder:
- [Workflows](https://docs.livekit.io/agents/logic/workflows.md), including [handoffs](https://docs.livekit.io/agents/logic/agents-handoffs.md), and [tasks](https://docs.livekit.io/agents/logic/tasks.md)
- [Virtual avatars](https://docs.livekit.io/agents/models/avatar.md)
- [Vision](https://docs.livekit.io/agents/build/vision.md)
- [Realtime models](https://docs.livekit.io/agents/models/realtime.md) and [model plugins](https://docs.livekit.io/agents/models.md#plugins)
- [Tests](https://docs.livekit.io/agents/start/testing.md)
## Billing and limits
The agent builder is subject to the same [quotas and limits](https://docs.livekit.io/deploy/admin/quotas-and-limits.md) as any other agent deployed to LiveKit Cloud. There is no additional cost to use the agent builder.
---
This document was rendered at 2026-02-03T03:24:54.173Z.
For the latest version of this document, see [https://docs.livekit.io/agents/start/builder.md](https://docs.livekit.io/agents/start/builder.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/deploy/agents/builds.md
LiveKit docs › Agent deployment › Builds and Dockerfiles
---
# Builds and Dockerfiles
> Guide to the LiveKit Cloud build process, plus Dockerfile templates and resources.
## Build process
LiveKit Cloud builds container images for your agents based on your code and Dockerfile, when you run `lk agent create` or `lk agent deploy`. This build occurs on the LiveKit Cloud build service. The process is as follows:
1. **Gather files:** The CLI prepares a build context from your working directory, which is the directory you run the command from. To use a different directory, pass it explicitly, for example `lk agent deploy /path/to/code`.
2. **Exclusions:** The build context excludes `.env.*` files and any files matched by `.dockerignore` or `.gitignore`.
3. **Upload:** The CLI uploads the prepared build context to the LiveKit Cloud build service.
4. **Build:** The build service uses your Dockerfile to create the container image, streaming logs to the CLI.
After the build is complete, deployment begins. See [Deploying new versions](https://docs.livekit.io/deploy/agents/managing-deployments.md#deploy) for more information.
To view build logs, see [Build logs](https://docs.livekit.io/deploy/agents/logs.md#build-logs).
### Build timeout
Builds have a maximum duration of 10 minutes. Builds exceeding this limit are terminated and the deployment fails.
## Dockerfile
Most projects can use the default Dockerfile generated by the LiveKit CLI, which is based on the [templates at the end of this section](#templates).
To create your own Dockerfile or modify the templates, refer to the following requirements and best practices:
- **Base image**: Use a glibc-based image such as Debian or Ubuntu. Alpine (musl) is not supported.- LiveKit recommends using `-slim` images, which contain only the essential system packages for your runtime.
- **Unprivileged user**: Do not run as the root user.
- **Working directory**: Set an explicit `WORKDIR` (for example, `/app`).
- **Dependencies and caching**:- Copy lockfiles and manifests first, install dependencies, then copy the rest of the source to maximize cache reuse.
- Pin versions and use lockfiles.
- **System packages and layers**:- Install required build tools up front.
- Clean package lists (for example, `/var/lib/apt/lists`) to keep layers small.
- **Build time limit**: Keep total build duration under 10 minutes; long builds fail due to the [build timeout](#timeout).
- **Secrets and configuration**:- Do not copy `.env*` files or include secrets in the image.
- Use LiveKit Cloud [secrets management](https://docs.livekit.io/deploy/agents/secrets.md) to inject any necessary secrets at runtime.
- Do not set `LIVEKIT_URL`, `LIVEKIT_API_KEY`, or `LIVEKIT_API_SECRET` environment variables. These are injected at runtime by LiveKit Cloud.
- **Startup command**: Provide a fixed `ENTRYPOINT`/`CMD` that directly launches the agent using the `start` command, without backgrounding or wrapper scripts.
- **Assets and models**: Download models and other assets during the image build, not on first run, so containers start quickly. Use `download-files` to download assets required by LiveKit plugins.
### Tips for Python projects
- Use the [uv](https://docs.astral.sh/uv/) package manager: This modern Rust-based package manager is faster than pip, and supports [lockfiles](https://docs.astral.sh/uv/concepts/projects/sync/).
- The recommended base image for uv-based projects is `ghcr.io/astral-sh/uv:python3.11-bookworm-slim` (or another Python version).
- The recommended base image for pip-based projects is `python:3.11-slim` (or another Python version).
- Check your `uv.lock` file into source control. This ensures everyone on your team is using the same dependencies.
- Install dependencies with `uv sync --locked`. This ensures that the dependencies in production always match
### Tips for Node.js projects
- Use the [pnpm](https://pnpm.io/) package manager: This modern package manager is faster and more efficient than npm, and it's the recommended way to manage Node.js dependencies.
- The recommended base image for pnpm-based projects is `node:22-slim` (or another Node.js version).
### Templates
These templates are automatically created by the LiveKit CLI to match your project type. They support both Python and Node.js projects.
The most up-to-date version of these templates is always available in the LiveKit CLI [examples folder](https://github.com/livekit/livekit-cli/tree/main/pkg/agentfs/examples).
**Python**:
This template is offered for both [uv](https://docs.astral.sh/uv/) and [pip](https://pip.pypa.io/en/stable/).
It assumes that your code in the `src/` directory and your agent entrypoint is in `src/agent.py`. You can modify these paths as needed.
** Filename: `Dockerfile`**
```dockerfile
# syntax=docker/dockerfile:1
# Use the official UV Python base image with Python 3.11 on Debian Bookworm
# UV is a fast Python package manager that provides better performance than pip
# We use the slim variant to keep the image size smaller while still having essential tools
ARG PYTHON_VERSION=3.11
FROM ghcr.io/astral-sh/uv:python${PYTHON_VERSION}-bookworm-slim AS base
# Keeps Python from buffering stdout and stderr to avoid situations where
# the application crashes without emitting any logs due to buffering.
ENV PYTHONUNBUFFERED=1
# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/app" \
--shell "/sbin/nologin" \
--uid "${UID}" \
appuser
# Install build dependencies required for Python packages with native extensions
# gcc: C compiler needed for building Python packages with C extensions
# python3-dev: Python development headers needed for compilation
# We clean up the apt cache after installation to keep the image size down
RUN apt-get update && apt-get install -y \
gcc \
python3-dev \
&& rm -rf /var/lib/apt/lists/*
# Create a new directory for our application code
# And set it as the working directory
WORKDIR /app
# Copy just the dependency files first, for more efficient layer caching
COPY pyproject.toml uv.lock ./
RUN mkdir -p src
# Install Python dependencies using UV's lock file
# --locked ensures we use exact versions from uv.lock for reproducible builds
# This creates a virtual environment and installs all dependencies
# Ensure your uv.lock file is checked in for consistency across environments
RUN uv sync --locked
# Copy all remaining application files into the container
# This includes source code, configuration files, and dependency specifications
# (Excludes files specified in .dockerignore)
COPY . .
# Change ownership of all app files to the non-privileged user
# This ensures the application can read/write files as needed
RUN chown -R appuser:appuser /app
# Switch to the non-privileged user for all subsequent operations
# This improves security by not running as root
USER appuser
# Pre-download any ML models or files the agent needs
# This ensures the container is ready to run immediately without downloading
# dependencies at runtime, which improves startup time and reliability
RUN uv run src/agent.py download-files
# Run the application using UV
# UV will activate the virtual environment and run the agent.
# The "start" command tells the agent server to connect to LiveKit and begin waiting for jobs.
CMD ["uv", "run", "src/agent.py", "start"]
```
** Filename: `.dockerignore`**
```text
# Python bytecode and artifacts
__pycache__/
*.py[cod]
*.pyo
*.pyd
*.egg-info/
dist/
build/
# Virtual environments
.venv/
venv/
# Caches and test output
.cache/
.pytest_cache/
.ruff_cache/
coverage/
# Logs and temp files
*.log
*.gz
*.tgz
.tmp
.cache
# Environment variables
.env
.env.*
# VCS, editor, OS
.git
.gitignore
.gitattributes
.github/
.idea/
.vscode/
.DS_Store
# Project docs and misc
README.md
LICENSE
# Project tests
test/
tests/
eval/
evals/
```
** Filename: `Dockerfile`**
```dockerfile
# syntax=docker/dockerfile:1
# Use the official Python base image with Python 3.11
# We use the slim variant to keep the image size smaller while still having essential tools
ARG PYTHON_VERSION=3.11
FROM python:${PYTHON_VERSION}-slim AS base
# Keeps Python from buffering stdout and stderr to avoid situations where
# the application crashes without emitting any logs due to buffering.
ENV PYTHONUNBUFFERED=1
# Disable pip version check to speed up builds
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
# Create a non-privileged user that the app will run under.
# See https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/app" \
--shell "/sbin/nologin" \
--uid "${UID}" \
appuser
# Install build dependencies required for Python packages with native extensions
# gcc: C compiler needed for building Python packages with C extensions
# g++: C++ compiler needed for building Python packages with C++ extensions
# python3-dev: Python development headers needed for compilation
# We clean up the apt cache after installation to keep the image size down
RUN apt-get update && apt-get install -y \
gcc \
g++ \
python3-dev \
&& rm -rf /var/lib/apt/lists/*
# Create a new directory for our application code
# And set it as the working directory
WORKDIR /app
# Copy just the dependency files first, for more efficient layer caching
COPY requirements.txt ./
# Install Python dependencies using pip
# --no-cache-dir ensures we don't use the system cache
RUN pip install --no-cache-dir -r requirements.txt
# Copy all remaining pplication files into the container
# This includes source code, configuration files, and dependency specifications
# (Excludes files specified in .dockerignore)
COPY . .
# Change ownership of all app files to the non-privileged user
# This ensures the application can read/write files as needed
RUN chown -R appuser:appuser /app
# Switch to the non-privileged user for all subsequent operations
# This improves security by not running as root
USER appuser
# Pre-download any ML models or files the agent needs
# This ensures the container is ready to run immediately without downloading
# dependencies at runtime, which improves startup time and reliability
RUN python agent.py download-files
# Run the application
# The "start" command tells the worker to connect to LiveKit and begin waiting for jobs.
CMD ["python", "agent.py", "start"]
```
** Filename: `.dockerignore`**
```text
# Python bytecode and artifacts
__pycache__/
*.py[cod]
*.pyo
*.pyd
*.egg-info/
dist/
build/
# Virtual environments
.venv/
venv/
# Caches and test output
.cache/
.pytest_cache/
.ruff_cache/
coverage/
# Logs and temp files
*.log
*.gz
*.tgz
.tmp
.cache
# Environment variables
.env
.env.*
# VCS, editor, OS
.git
.gitignore
.gitattributes
.github/
.idea/
.vscode/
.DS_Store
# Project docs and misc
README.md
LICENSE
# Project tests
test/
tests/
eval/
evals/
```
---
**Node.js**:
This template uses [pnpm](https://pnpm.io/) and TypeScript but can be modified for other environments.
The Dockerfile assumes that your project contains `build`, `download-files`, and `start` scripts. See the `package.json` file template for examples.
** Filename: `Dockerfile`**
```dockerfile
# syntax=docker/dockerfile:1
# Use the official Node.js v22 base image with Node.js 22.10.0
# We use the slim variant to keep the image size smaller while still having essential tools
ARG NODE_VERSION=22
FROM node:${NODE_VERSION}-slim AS base
# Configure pnpm installation directory and ensure it is on PATH
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
# Install required system packages and pnpm, then clean up the apt cache for a smaller image
# ca-certificates: enables TLS/SSL for securely fetching dependencies and calling HTTPS services
# --no-install-recommends keeps the image minimal
RUN apt-get update -qq && apt-get install --no-install-recommends -y ca-certificates && rm -rf /var/lib/apt/lists/*
# Pin pnpm version for reproducible builds
RUN npm install -g pnpm@10
# Create a new directory for our application code
# And set it as the working directory
WORKDIR /app
# Copy just the dependency files first, for more efficient layer caching
COPY package.json pnpm-lock.yaml ./
# Install dependencies using pnpm
# --frozen-lockfile ensures we use exact versions from pnpm-lock.yaml for reproducible builds
RUN pnpm install --frozen-lockfile
# Copy all remaining pplication files into the container
# This includes source code, configuration files, and dependency specifications
# (Excludes files specified in .dockerignore)
COPY . .
# Build the project
# Your package.json must contain a "build" script, such as `"build": "tsc"`
RUN pnpm build
# Create a non-privileged user that the app will run under
# See https://docs.docker.com/develop/develop-images/dockerfile_best_practices/#user
ARG UID=10001
RUN adduser \
--disabled-password \
--gecos "" \
--home "/app" \
--shell "/sbin/nologin" \
--uid "${UID}" \
appuser
# Set proper permissions
RUN chown -R appuser:appuser /app
USER appuser
# Pre-download any ML models or files the agent needs
# This ensures the container is ready to run immediately without downloading
# dependencies at runtime, which improves startup time and reliability
# Your package.json must contain a "download-files" script, such as `"download-files": "pnpm run build && node dist/agent.js download-files"`
RUN pnpm download-files
# Switch back to root to remove dev dependencies and finalize setup
USER root
RUN pnpm prune --prod && chown -R appuser:appuser /app
USER appuser
# Set Node.js to production mode
ENV NODE_ENV=production
# Run the application
# The "start" command tells the worker to connect to LiveKit and begin waiting for jobs.
# Your package.json must contain a "start" script, such as `"start": "node dist/agent.js start"`
CMD [ "pnpm", "start" ]
```
** Filename: `.dockerignore`**
```text
# Node.js dependencies
node_modules
npm-debug.log
yarn-error.log
pnpm-debug.log
# Build outputs
dist/
build/
coverage/
# Logs and temp files
*.log
*.gz
*.tgz
.tmp
.cache
# Environment variables
.env
.env.*
# VCS, editor, OS
.git
.gitignore
.gitattributes
.github/
.idea/
.vscode/
.DS_Store
# Project docs and misc
README.md
LICENSE
```
** Filename: `package.json`**
```json
{
"scripts": {
// ... other scripts ...
"build": "tsc",
"clean": "rm -rf dist",
"download-files": "pnpm run build && node dist/agent.js download-files",
"start": "node dist/agent.js start"
},
// ... other config ...
}
```
---
This document was rendered at 2026-02-03T03:25:22.541Z.
For the latest version of this document, see [https://docs.livekit.io/deploy/agents/builds.md](https://docs.livekit.io/deploy/agents/builds.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/data/byte-streams.md
LiveKit docs › Data › Sending files & bytes
---
# Sending files & bytes
> Use byte streams to send files, images, or any other kind of data between participants.
## Overview
Byte streams provide a simple way to send files, images, or other binary data between participants in realtime. Each individual stream is associated with a topic, and you must register a handler to receive incoming streams for that topic. Streams can target specific participants or the entire room.
To send text data, use [text streams](https://docs.livekit.io/transport/data/text-streams.md) instead.
## Sending files
To send a file or an image, use the `sendFile` method. Precise support varies by SDK, as this is integrated with the platform's own file APIs.
**JavaScript**:
```typescript
// Send a `File` object
const file = ($('file') as HTMLInputElement).files?.[0]!;
const info = await room.localParticipant.sendFile(file, {
mimeType: file.type,
topic: 'my-topic',
// Optional, allows progress to be shown to the user
onProgress: (progress) => console.log('sending file, progress', Math.ceil(progress * 100)),
});
console.log(`Sent file with stream ID: ${info.id}`);
```
---
**Swift**:
```swift
// Send a file from disk by specifying its path
let fileURL = URL(filePath: "path/to/file.jpg")
let info = try await room.localParticipant
.sendFile(fileURL, for: "my-topic")
print("Sent file with stream ID: \(info.id)")
```
---
**Python**:
```python
# Send a file from disk by specifying its path
info = await room.local_participant.send_file(
file_path="path/to/file.jpg",
topic="my-topic",
)
print(f"Sent file with stream ID: {info.stream_id}")
```
---
**Rust**:
```rust
let options = StreamByteOptions {
topic: "my-topic".to_string(),
..Default::default()
};
let info = room.local_participant()
.send_file("path/to/file.jpg", options).await?;
println!("Sent file with stream ID: {}", info.id);
```
---
**Node.js**:
```typescript
// Send a file from disk by specifying its path
const info = await room.localParticipant.sendFile("path/to/file.jpg", {
topic: "my-topic",
});
console.log(`Sent file with stream ID: ${info.id}`);
```
---
**Go**:
```go
filePath := "path/to/file.jpg"
info, err := room.LocalParticipant.SendFile(filePath, livekit.StreamBytesOptions{
Topic: "my-topic",
FileName: &filePath,
})
if err != nil {
fmt.Printf("failed to send file: %v\n", err)
}
fmt.Printf("Sent file with stream ID: %s\n", info.ID)
```
---
**Android**:
```kotlin
val file = File("path/to/file.jpg")
val result = room.localParticipant.sendFile(file, StreamBytesOptions(topic = "my-topic"))
result.onSuccess { info ->
Log.i("Datastream", "sent file id: ${info.id}")
}
```
---
**Flutter**:
```dart
final fileToSend = File('path/to/file.jpg');
var info = await room.localParticipant?.sendFile(fileToSend,
options: SendFileOptions(
topic: 'my-topic',
onProgress: (p0) {
// progress is a value between 0 and 1
// it indicates the progress of the file transfer
print('progress: ${p0 * 100} %');
},
)
);
print('Sent file with stream ID: ${info['id']}');
```
## Streaming bytes
To stream any kind of binary data, open a stream writer with the `streamBytes` method. You must explicitly close the stream when you are done sending data.
**Swift**:
```swift
let writer = try await room.localParticipant
.streamBytes(for: "my-topic")
print("Opened byte stream with ID: \(writer.info.id)")
// Example sending arbitrary binary data
// For sending files, use `sendFile` instead
let dataChunks = [Data([0x00, 0x01]), Data([0x03, 0x04])]
for chunk in dataChunks {
try await writer.write(chunk)
}
// The stream must be explicitly closed when done
try await writer.close()
print("Closed byte stream with ID: \(writer.info.id)")
```
---
**Python**:
```python
writer = await self.stream_bytes(
# All byte streams must have a name, which is like a filename
name="my-byte-stream",
# The topic must match the topic used in the receiver's `register_byte_stream_handler`
topic="my-topic",
)
print(f"Opened byte stream with ID: {writer.stream_id}")
chunk_size = 15000 # 15KB, a recommended max chunk size
# This an example to send a file, but you can send any kind of binary data
async with aiofiles.open(file_path, "rb") as f:
while bytes := await f.read(chunk_size):
await writer.write(bytes)
await writer.aclose()
```
---
**Rust**:
```rust
let options = StreamByteOptions {
topic: "my-topic".to_string(),
..Default::default()
};
let stream_writer = room.local_participant()
.stream_bytes(options).await?;
let id = stream_writer.info().id.clone();
println!("Opened text stream with ID: {}", id);
// Example sending arbitrary binary data
// For sending files, use `send_file` instead
let data_chunks = [[0x00, 0x01], [0x03, 0x04]];
for chunk in data_chunks {
stream_writer.write(&chunk).await?;
}
// The stream can be closed explicitly or will be closed implicitly
// when the last writer is dropped
stream_writer.close().await?;
println!("Closed text stream with ID: {}", id);
```
---
**Node.js**:
```typescript
const writer = await room.localParticipant.streamBytes({
// All byte streams must have a name, which is like a filename
name: "my-byte-stream",
// The topic must match the topic used in the receiver's `registerByteStreamHandler`
topic: "my-topic",
});
console.log(`Opened byte stream with ID: ${writer.info.id}`);
const chunkSize = 15000; // 15KB, a recommended max chunk size
// This is an example to send a file, but you can send any kind of binary data
const fileStream = fs.createReadStream(filePath, { highWaterMark: chunkSize });
for await (const chunk of fileStream) {
await writer.write(chunk);
}
await writer.close();
```
---
**Go**:
```go
writer := room.LocalParticipant.StreamBytes(livekit.StreamBytesOptions{
Topic: "my-topic",
})
// Use the writer to send data
// onDone is called when a chunk is sent
// writer can be closed in onDone of the last chunk
writer.Write(data, onDone)
// Close the writer when done, if you haven't already
writer.Close()
```
---
**Android**:
```kotlin
val writer = room.localParticipant.streamBytes(StreamBytesOptions(topic = "my-topic"))
Log.i("Datastream", "id: ${writer.info.id}")
val dataChunks = listOf(byteArrayOf(0x00, 0x01), byteArrayOf(0x02, 0x03))
for (chunk in dataChunks) {
writer.write(chunk)
}
writer.close()
```
---
**Flutter**:
```dart
var stream = await room.localParticipant?.streamText(StreamTextOptions(
topic: 'my-topic',
));
var chunks = ['Lorem ', 'ipsum ', 'dolor ', 'sit ', 'amet...'];
for (var chunk in chunks) {
// write each chunk to the stream
await stream?.write(chunk);
}
// close the stream to signal that no more data will be sent
await stream?.close();
```
## Handling incoming streams
Whether the data was sent as a file or a stream, it is always received as a stream. You must register a handler to receive it.
**JavaScript**:
```typescript
room.registerByteStreamHandler('my-topic', (reader, participantInfo) => {
const info = reader.info;
// Optional, allows you to display progress information if the stream was sent with `sendFile`
reader.onProgress = (progress) => {
console.log(`"progress ${progress ? (progress * 100).toFixed(0) : 'undefined'}%`);
};
// Option 1: Process the stream incrementally using a for-await loop.
for await (const chunk of reader) {
// Collect these however you want.
console.log(`Next chunk: ${chunk}`);
}
// Option 2: Get the entire file after the stream completes.
const result = new Blob(await reader.readAll(), { type: info.mimeType });
console.log(
`File "${info.name}" received from ${participantInfo.identity}\n` +
` Topic: ${info.topic}\n` +
` Timestamp: ${info.timestamp}\n` +
` ID: ${info.id}\n` +
` Size: ${info.size}` // Optional, only available if the stream was sent with `sendFile`
);
});
```
---
**Swift**:
```swift
try await room.localParticipant
.registerByteStreamHandler(for: "my-topic") { reader, participantIdentity in
let info = reader.info
// Option 1: Process the stream incrementally using a for-await loop
for try await chunk in reader {
// Collect these however you want
print("Next chunk received: \(chunk.count) bytes")
}
// Option 2: Get the entire file after the stream completes
let data = try await reader.readAll()
// Option 3: Write the stream to a local file on disk as it arrives
let fileURL = try await reader.writeToFile()
print("Wrote file to: \(fileURL)")
print("""
File "\(info.name ?? "unnamed")" received from \(participantIdentity)
Topic: \(info.topic)
Timestamp: \(info.timestamp)
ID: \(info.id)
Size: \(info.size) (only available if the stream was sent with `sendFile`)
""")
}
```
---
**Python**:
```python
import asyncio
# Store active tasks to prevent garbage collection
_active_tasks = []
async def async_handle_byte_stream(reader, participant_identity):
info = reader.info
# Read the stream to a file
with open(reader.info["name"], mode="wb") as f:
async for chunk in reader:
f.write(chunk)
f.close()
print(
f'File "{info.name}" received from {participant_identity}\n'
f' Topic: {info.topic}\n'
f' Timestamp: {info.timestamp}\n'
f' ID: {info.id}\n'
f' Size: {info.size}' # Optional, only available if the stream was sent with `send_file`
)
def handle_byte_stream(reader, participant_identity):
task = asyncio.create_task(async_handle_byte_stream(reader, participant_identity))
_active_tasks.append(task)
task.add_done_callback(lambda t: _active_tasks.remove(t))
room.register_byte_stream_handler(
"my-topic",
handle_byte_stream
)
```
---
**Rust**:
The Rust API differs slightly from the other SDKs. Instead of registering a topic handler, you handle the `ByteStreamOpened` room event and take the reader from the event if you wish to handle the stream.
```rust
while let Some(event) = room.subscribe().recv().await {
match event {
RoomEvent::ByteStreamOpened { reader, topic, participant_identity } => {
if topic != "my-topic" { continue };
let Some(mut reader) = reader.take() else { continue };
let info = reader.info();
// Option 1: Process the stream incrementally as a Stream
// using `TryStreamExt` from the `futures_util` crate
while let Some(chunk) = reader.try_next().await? {
println!("Next chunk: {:?}", chunk);
}
// Option 2: Get the entire file after the stream completes
let data = reader.read_all().await?;
// Option 3: Write the stream to a local file on disk as it arrives
let file_path = reader.write_to_file().await?;
println!("Wrote file to: {}", file_path.display());
println!("File '{}' received from {}", info.name, participant_identity);
println!(" Topic: {}", info.topic);
println!(" Timestamp: {}", info.timestamp);
println!(" ID: {}", info.id);
println!(" Size: {:?}", info.total_length); // Only available when sent with `send_file`
}
_ => {}
}
}
```
---
**Node.js**:
```typescript
room.registerByteStreamHandler('my-topic', (reader, participantInfo) => {
const info = reader.info;
// Option 1: Process the stream incrementally using a for-await loop.
for await (const chunk of reader) {
// Collect these however you want.
console.log(`Next chunk: ${chunk}`);
}
// Option 2: Get the entire file after the stream completes.
const result = new Blob(await reader.readAll(), { type: info.mimeType });
console.log(
`File "${info.name}" received from ${participantInfo.identity}\n` +
` Topic: ${info.topic}\n` +
` Timestamp: ${info.timestamp}\n` +
` ID: ${info.id}\n` +
` Size: ${info.size}` // Optional, only available if the stream was sent with `sendFile`
);
});
```
---
**Go**:
```go
room.RegisterByteStreamHandler(
"my-topic",
func(reader livekit.ByteStreamReader, participantIdentity livekit.ParticipantIdentity) {
fmt.Printf("Byte stream received from %s\n", participantIdentity)
// Option 1: Process the stream incrementally
res := []byte{}
for {
chunk := make([]byte, 1024)
n, err := reader.Read(chunk)
res = append(res, chunk[:n]...)
if err != nil {
if err == io.EOF {
break
} else {
fmt.Printf("failed to read byte stream: %v\n", err)
break
}
}
}
// Similar to Read, there is ReadByte(), ReadBytes(delim byte)
// Option 2: Get the entire stream after it completes
data := reader.ReadAll()
fmt.Printf("received data: %v\n", data)
},
)
```
---
**Android**:
```kotlin
room.registerByteStreamHandler("my-topic") { reader, info ->
myCoroutineScope.launch {
val info = reader.info
Log.i("Datastream", "info stuff")
// Option 1: process incrementally
reader.flow.collect { chunk ->
Log.i("Datastream", "Next chunk received: ${chunk.size} bytes")
}
// Option 2
val data = reader.readAll()
val dataSize = data.fold(0) { sum, next -> sum + next.size }
Log.i("DataStream", "Received data: total $dataSize bytes")
}
}
```
---
**Flutter**:
```dart
// for incoming text streams
room.registerTextStreamHandler('my-topic',
(TextStreamReader reader, String participantIdentity) async {
var text = await reader.readAll();
print('Received text: $text');
});
// for receiving files
room.registerByteStreamHandler('my-topic',
(ByteStreamReader reader, String participantIdentity) async {
// Get the entire file after the stream completes.
var file = await reader.readAll();
// Write a file to local path
var writeFile = File('path/to/copy-${reader.info!.name}');
// Merge all chunks to content
var content = file.expand((element) => element).toList();
// Write content to the file.
writeFile.writeAsBytesSync(content);
});
```
## Stream properties
These are all of the properties available on a text stream, and can be set from the send/stream methods or read from the handler.
| Property | Description | Type |
| `id` | Unique identifier for this stream. | string |
| `topic` | Topic name used to route the stream to the appropriate handler. | string |
| `timestamp` | When the stream was created. | number |
| `mimeType` | The MIME type of the stream data. Auto-detected for files, otherwise defaults to `application/octet-stream`. | string |
| `name` | The name of the file being sent. | string |
| `size` | Total expected size in bytes, if known. | number |
| `attributes` | Additional attributes as needed for your application. | string dict |
| `destinationIdentities` | Identities of the participants to send the stream to. If empty, will be sent to all. | array |
## Concurrency
Multiple streams can be written or read concurrently. If you call `sendFile` or `streamBytes` multiple times on the same topic, the recipient's handler will be invoked multiple times, once for each stream. These invocations will occur in the same order as the streams were opened by the sender, and the stream readers will be closed in the same order in which the streams were closed by the sender.
## Joining mid-stream
Participants who join a room after a stream has been initiated will not receive any of it. Only participants connected at the time the stream is opened are eligible to receive it.
## Chunk sizes
The processes for writing and reading streams are optimized separately. This means the number and size of chunks sent may not match the number and size of those received. However, the full data received is guaranteed to be complete and in order. Chunks are generally smaller than 15kB.
> ℹ️ **Note**
>
> Streams are a simple and powerful way to send data, but if you need precise control over individual packet behavior, the lower-level [data packets](https://docs.livekit.io/transport/data/packets.md) API may be more appropriate.
---
This document was rendered at 2026-02-03T03:25:18.884Z.
For the latest version of this document, see [https://docs.livekit.io/transport/data/byte-streams.md](https://docs.livekit.io/transport/data/byte-streams.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/tts/plugins/cartesia.md
# Source: https://docs.livekit.io/agents/models/tts/inference/cartesia.md
# Source: https://docs.livekit.io/agents/models/stt/plugins/cartesia.md
# Source: https://docs.livekit.io/agents/models/stt/inference/cartesia.md
LiveKit docs › Models › STT › Inference › Cartesia
---
# Cartesia STT
> Reference for Cartesia STT in LiveKit Inference.
## Overview
LiveKit Inference offers transcription powered by Cartesia. Pricing information is available on the [pricing page](https://livekit.io/pricing/inference#stt).
| Model name | Model ID | Languages |
| -------- | -------- | --------- |
| Ink Whisper | `cartesia/ink-whisper` | `en`, `zh`, `de`, `es`, `ru`, `ko`, `fr`, `ja`, `pt`, `tr`, `pl`, `ca`, `nl`, `ar`, `sv`, `it`, `id`, `vi`, `he`, `hi`, `uk`, `el`, `ms`, `cs`, `ro`, `da`, `hu`, `ta`, `no`, `th`, `ur`, `hr`, `bg`, `lt`, `la`, `mi`, `ml`, `cy`, `sk`, `te`, `fa`, `fi`, `lv`, `bn`, `sr`, `az`, `sl`, `kn`, `et`, `mk`, `br`, `eu`, `is`, `hy`, `ne`, `mn`, `bs`, `kk`, `sq`, `sw`, `gl`, `mr`, `pa`, `si`, `km`, `sn`, `yo`, `so`, `af`, `oc`, `ka`, `be`, `tg`, `sd`, `gu`, `am`, `yi`, `lo`, `uz`, `fo`, `ht`, `ps`, `tk`, `nn`, `mt`, `sa`, `lb`, `my`, `bo`, `tl`, `mg`, `as`, `tt`, `haw`, `ln`, `ha`, `ba`, `jw`, `su`, `yue` |
## Usage
To use Cartesia, pass a descriptor with the model and language to the `stt` argument in your `AgentSession`:
**Python**:
```python
from livekit.agents import AgentSession
session = AgentSession(
stt="cartesia/ink-whisper:en",
# ... tts, stt, vad, turn_detection, etc.
)
```
---
**Node.js**:
```typescript
import { AgentSession } from '@livekit/agents';
session = new AgentSession({
stt: "cartesia/ink-whisper:en",
// ... tts, stt, vad, turn_detection, etc.
});
```
### Parameters
To customize additional parameters, use the `STT` class from the `inference` module:
**Python**:
```python
from livekit.agents import AgentSession, inference
session = AgentSession(
stt=inference.STT(
model="cartesia/ink-whisper",
language="en"
),
# ... tts, stt, vad, turn_detection, etc.
)
```
---
**Node.js**:
```typescript
import { AgentSession, inference } from '@livekit/agents';
session = new AgentSession({
stt: new inference.STT({
model: "cartesia/ink-whisper",
language: "en"
}),
// ... tts, stt, vad, turn_detection, etc.
});
```
- **`model`** _(string)_: The model to use for the STT.
- **`language`** _(string)_ (optional): Language code for the transcription. If not set, the provider default applies.
- **`extra_kwargs`** _(dict)_ (optional): Additional parameters to pass to the Cartesia STT API, including `min_volume`, and `max_silence_duration_secs`. See the provider's [documentation](#additional-resources) for more information.
In Node.js this parameter is called `modelOptions`.
## Additional resources
The following links provide more information about Cartesia in LiveKit Inference.
- **[Cartesia Plugin](https://docs.livekit.io/agents/models/stt/plugins/cartesia.md)**: Plugin to use your own Cartesia account instead of LiveKit Inference.
- **[Cartesia TTS models](https://docs.livekit.io/agents/models/tts/inference/cartesia.md)**: Cartesia TTS models in LiveKit Inference.
- **[Cartesia docs](https://cartesia.ai/docs)**: Cartesia's official documentation.
---
This document was rendered at 2026-02-03T03:25:02.466Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/stt/inference/cartesia.md](https://docs.livekit.io/agents/models/stt/inference/cartesia.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/integrations/cerebras.md
# Source: https://docs.livekit.io/agents/models/llm/plugins/cerebras.md
LiveKit docs › Models › LLM › Plugins › Cerebras
---
# Cerebras LLM plugin guide
> How to use the Cerebras inference with LiveKit Agents.
Available in:
- [x] Node.js
- [x] Python
## Overview
This plugin allows you to use [Cerebras](https://www.cerebras.net/) as an LLM provider for your voice agents. Cerebras compatibility is provided by the OpenAI plugin using the Cerebras Chat Completions API.
> 💡 **LiveKit Inference**
>
> Some Cerebras models are also available in LiveKit Inference, with billing and integration handled automatically. See [the docs](https://docs.livekit.io/agents/models/llm.md) for more information.
## Usage
Install the OpenAI plugin to add Cerebras support:
**Python**:
```shell
uv add "livekit-agents[openai]~=1.3"
```
---
**Node.js**:
```shell
pnpm add @livekit/agents-plugin-openai@1.x
```
Set the following environment variable in your `.env` file:
```shell
CEREBRAS_API_KEY=
```
Create a Cerebras LLM using the `with_cerebras` method:
**Python**:
```python
from livekit.plugins import openai
session = AgentSession(
llm=openai.LLM.with_cerebras(
model="llama3.1-8b",
),
# ... tts, stt, vad, turn_detection, etc.
)
```
---
**Node.js**:
```typescript
import * as openai from '@livekit/agents-plugin-openai';
const session = new voice.AgentSession({
llm: openai.LLM.withCerebras({
model: "llama3.1-8b",
}),
// ... tts, stt, vad, turn_detection, etc.
});
```
## Parameters
This section describes some of the available parameters. See the plugin reference links in the [Additional resources](#additional-resources) section for a complete list of all available parameters.
- **`model`** _(str | CerebrasChatModels)_ (optional) - Default: `llama3.1-8b`: Model to use for inference. To learn more, see [supported models](https://inference-docs.cerebras.ai/api-reference/chat-completions#param-model).
- **`temperature`** _(float)_ (optional) - Default: `1.0`: Controls the randomness of the model's output. Higher values, for example 0.8, make the output more random, while lower values, for example 0.2, make it more focused and deterministic.
Valid values are between `0` and `1.5`. To learn more, see the [Cerebras documentation](https://inference-docs.cerebras.ai/api-reference/chat-completions#param-temperature).
- **`parallel_tool_calls`** _(bool)_ (optional): Controls whether the model can make multiple tool calls in parallel. When enabled, the model can make multiple tool calls simultaneously, which can improve performance for complex tasks.
- **`tool_choice`** _(ToolChoice | Literal['auto', 'required', 'none'])_ (optional) - Default: `auto`: Controls how the model uses tools. Set to 'auto' to let the model decide, 'required' to force tool usage, or 'none' to disable tool usage.
## Additional resources
The following links provide more information about the Cerebras LLM integration.
- **[Cerebras docs](https://inference-docs.cerebras.ai/)**: Cerebras inference docs.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Get started with LiveKit Agents and Cerebras.
---
This document was rendered at 2026-02-03T03:25:00.046Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/llm/plugins/cerebras.md](https://docs.livekit.io/agents/models/llm/plugins/cerebras.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/recipes/chain-of-thought.md
LiveKit docs › Advanced LLM › Chain-of-thought agent
---
# Modifying LLM output before TTS
> How to modify LLM output before sending the text to TTS for vocalization.
In this recipe, build an agent that speaks chain-of-thought reasoning aloud while avoiding the vocalization of `` and `` tokens. The steps focus on cleaning up the text just before it's sent to the TTS engine so the agent sounds natural.
## Prerequisites
To complete this guide, you need to create an agent using the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
## Modifying LLM output before TTS
You can modify the LLM output by creating a custom Agent class and overriding the `llm_node` method. Here's how to implement an agent that removes `` tags from the output:
```python
import logging
from pathlib import Path
from dotenv import load_dotenv
from livekit.agents import JobContext, WorkerOptions, cli
from livekit.agents.voice import Agent, AgentSession
from livekit.plugins import silero
load_dotenv()
logger = logging.getLogger("replacing-llm-output")
logger.setLevel(logging.INFO)
class ChainOfThoughtAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""
You are a helpful agent that thinks through problems step by step.
When reasoning through a complex question, wrap your thinking in tags.
After you've thought through the problem, provide your final answer.
""",
stt=deepgram.STT(),
llm=openai.LLM.with_groq(model="deepseek-r1-distill-llama-70b"),
tts=openai.TTS(),
vad=silero.VAD.load()
)
async def on_enter(self):
self.session.generate_reply()
async def llm_node(
self, chat_ctx, tools, model_settings=None
):
async def process_stream():
async with self.llm.chat(chat_ctx=chat_ctx, tools=tools, tool_choice=None) as stream:
async for chunk in stream:
if chunk is None:
continue
content = getattr(chunk.delta, 'content', None) if hasattr(chunk, 'delta') else str(chunk)
if content is None:
yield chunk
continue
processed_content = content.replace("", "").replace("", "Okay, I'm ready to respond.")
if processed_content != content:
if hasattr(chunk, 'delta') and hasattr(chunk.delta, 'content'):
chunk.delta.content = processed_content
else:
chunk = processed_content
yield chunk
```
## Setting up the agent session
Create an entrypoint function to initialize and run the agent:
```python
async def entrypoint(ctx: JobContext):
session = AgentSession()
await session.start(
agent=ChainOfThoughtAgent(),
room=ctx.room
)
if __name__ == "__main__":
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
```
## How it works
1. The LLM generates text with chain-of-thought reasoning wrapped in `...` tags
2. The overridden `llm_node` method intercepts the LLM output stream
3. For each chunk of text:- The method checks if there's content to process
- It replaces `` tags with empty string and `` tags with "Okay, I'm ready to respond."
- The modified content is then passed on to the TTS engine
4. The TTS engine only speaks the processed text
This approach gives you fine-grained control over how the agent processes and speaks LLM responses, allowing for more sophisticated conversational behaviors.
---
This document was rendered at 2026-02-03T03:25:29.447Z.
For the latest version of this document, see [https://docs.livekit.io/recipes/chain-of-thought.md](https://docs.livekit.io/recipes/chain-of-thought.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/recipes/changing_language.md
LiveKit docs › Voice Processing › Change Language
---
# ElevenLabs Change Language
> Shows how to use the ElevenLabs TTS model to change the language of the agent.
This example demonstrates how to build a multilingual voice agent that can switch between languages mid-call by updating ElevenLabs TTS and Deepgram STT on the fly. The agent greets callers in English, switches to Spanish, French, German, or Italian when asked, and replies with a native greeting in the new language.
## Prerequisites
- Add a `.env` in this directory with your LiveKit and provider credentials:```
LIVEKIT_URL=your_livekit_url
LIVEKIT_API_KEY=your_api_key
LIVEKIT_API_SECRET=your_api_secret
DEEPGRAM_API_KEY=your_deepgram_key
ELEVENLABS_API_KEY=your_elevenlabs_key
```
- Install dependencies:```bash
pip install python-dotenv "livekit-agents[silero,deepgram,elevenlabs]"
```
## Load environment, logging, and define an AgentServer
Start by importing the necessary modules, loading your environment, and configuring logging for the agent.
```python
import logging
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, AgentServer, cli, inference, function_tool
from livekit.plugins import deepgram, elevenlabs, silero
load_dotenv()
logger = logging.getLogger("language-switcher")
logger.setLevel(logging.INFO)
server = AgentServer()
```
## Prewarm VAD and define the language-switcher agent
Preload VAD once per process to reduce connection latency. Configure the RTC session with Deepgram STT, ElevenLabs TTS, and an inference LLM.
```python
import logging
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, AgentServer, cli, inference, function_tool
from livekit.plugins import deepgram, elevenlabs, silero
load_dotenv()
logger = logging.getLogger("language-switcher")
logger.setLevel(logging.INFO)
server = AgentServer()
```
```python
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
class LanguageSwitcherAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""
You are a helpful assistant communicating through voice.
You can switch to a different language if asked.
Don't use any unpronounceable characters.
"""
)
self.current_language = "en"
self.language_names = {
"en": "English",
"es": "Spanish",
"fr": "French",
"de": "German",
"it": "Italian",
}
self.deepgram_language_codes = {
"en": "en",
"es": "es",
"fr": "fr-CA",
"de": "de",
"it": "it",
}
self.greetings = {
"en": "Hello! I'm now speaking in English. How can I help you today?",
"es": "¡Hola! Ahora estoy hablando en español. ¿Cómo puedo ayudarte hoy?",
"fr": "Bonjour! Je parle maintenant en français. Comment puis-je vous aider aujourd'hui?",
"de": "Hallo! Ich spreche jetzt Deutsch. Wie kann ich Ihnen heute helfen?",
"it": "Ciao! Ora sto parlando in italiano. Come posso aiutarti oggi?",
}
async def on_enter(self):
await self.session.say(
"Hi there! I can speak in multiple languages including Spanish, French, German, and Italian. "
"Just ask me to switch to any of these languages. How can I help you today?"
)
@server.rtc_session()
async def entrypoint(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
session = AgentSession(
stt=deepgram.STT(model="nova-2-general", language="en"),
llm=inference.LLM(model="openai/gpt-4o"),
tts=elevenlabs.TTS(model="eleven_turbo_v2_5", language="en"),
vad=ctx.proc.userdata["vad"],
preemptive_generation=True,
)
await session.start(agent=LanguageSwitcherAgent(), room=ctx.room)
await ctx.connect()
```
## Add the function tools to switch languages
Next we'll add a helper to swap STT/TTS languages, and function tools that let the LLM trigger language changes.
```python
import logging
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, AgentServer, cli, inference, function_tool
from livekit.plugins import deepgram, elevenlabs, silero
load_dotenv()
logger = logging.getLogger("language-switcher")
logger.setLevel(logging.INFO)
server = AgentServer()
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
class LanguageSwitcherAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""
You are a helpful assistant communicating through voice.
You can switch to a different language if asked.
Don't use any unpronounceable characters.
"""
)
self.current_language = "en"
self.language_names = {
"en": "English",
"es": "Spanish",
"fr": "French",
"de": "German",
"it": "Italian",
}
self.deepgram_language_codes = {
"en": "en",
"es": "es",
"fr": "fr-CA",
"de": "de",
"it": "it",
}
self.greetings = {
"en": "Hello! I'm now speaking in English. How can I help you today?",
"es": "¡Hola! Ahora estoy hablando en español. ¿Cómo puedo ayudarte hoy?",
"fr": "Bonjour! Je parle maintenant en français. Comment puis-je vous aider aujourd'hui?",
"de": "Hallo! Ich spreche jetzt Deutsch. Wie kann ich Ihnen heute helfen?",
"it": "Ciao! Ora sto parlando in italiano. Come posso aiutarti oggi?",
}
async def on_enter(self):
await self.session.say(
"Hi there! I can speak in multiple languages including Spanish, French, German, and Italian. "
"Just ask me to switch to any of these languages. How can I help you today?"
)
```
```python
async def _switch_language(self, language_code: str) -> None:
"""Helper method to switch the language"""
if language_code == self.current_language:
await self.session.say(f"I'm already speaking in {self.language_names[language_code]}.")
return
if self.session.tts is not None:
self.session.tts.update_options(language=language_code)
if self.session.stt is not None:
deepgram_language = self.deepgram_language_codes.get(language_code, language_code)
self.session.stt.update_options(language=deepgram_language)
self.current_language = language_code
await self.session.say(self.greetings[language_code])
@function_tool
async def switch_to_english(self):
"""Switch to speaking English"""
await self._switch_language("en")
@function_tool
async def switch_to_spanish(self):
"""Switch to speaking Spanish"""
await self._switch_language("es")
@function_tool
async def switch_to_french(self):
"""Switch to speaking French"""
await self._switch_language("fr")
@function_tool
async def switch_to_german(self):
"""Switch to speaking German"""
await self._switch_language("de")
@function_tool
async def switch_to_italian(self):
"""Switch to speaking Italian"""
await self._switch_language("it")
```
```python
@server.rtc_session()
async def entrypoint(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
session = AgentSession(
stt=deepgram.STT(model="nova-2-general", language="en"),
llm=inference.LLM(model="openai/gpt-4o"),
tts=elevenlabs.TTS(model="eleven_turbo_v2_5", language="en"),
vad=ctx.proc.userdata["vad"],
preemptive_generation=True,
)
await session.start(agent=LanguageSwitcherAgent(), room=ctx.room)
await ctx.connect()
```
## Run the server
Use the CLI runner to start the agent server so it can respond to language-change requests.
```python
import logging
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, AgentServer, cli, inference, function_tool
from livekit.plugins import deepgram, elevenlabs, silero
load_dotenv()
logger = logging.getLogger("language-switcher")
logger.setLevel(logging.INFO)
server = AgentServer()
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
class LanguageSwitcherAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""
You are a helpful assistant communicating through voice.
You can switch to a different language if asked.
Don't use any unpronounceable characters.
"""
)
self.current_language = "en"
self.language_names = {
"en": "English",
"es": "Spanish",
"fr": "French",
"de": "German",
"it": "Italian",
}
self.deepgram_language_codes = {
"en": "en",
"es": "es",
"fr": "fr-CA",
"de": "de",
"it": "it",
}
self.greetings = {
"en": "Hello! I'm now speaking in English. How can I help you today?",
"es": "¡Hola! Ahora estoy hablando en español. ¿Cómo puedo ayudarte hoy?",
"fr": "Bonjour! Je parle maintenant en français. Comment puis-je vous aider aujourd'hui?",
"de": "Hallo! Ich spreche jetzt Deutsch. Wie kann ich Ihnen heute helfen?",
"it": "Ciao! Ora sto parlando in italiano. Come posso aiutarti oggi?",
}
async def on_enter(self):
await self.session.say(
"Hi there! I can speak in multiple languages including Spanish, French, German, and Italian. "
"Just ask me to switch to any of these languages. How can I help you today?"
)
async def _switch_language(self, language_code: str) -> None:
"""Helper method to switch the language"""
if language_code == self.current_language:
await self.session.say(f"I'm already speaking in {self.language_names[language_code]}.")
return
if self.session.tts is not None:
self.session.tts.update_options(language=language_code)
if self.session.stt is not None:
deepgram_language = self.deepgram_language_codes.get(language_code, language_code)
self.session.stt.update_options(language=deepgram_language)
self.current_language = language_code
await self.session.say(self.greetings[language_code])
@function_tool
async def switch_to_english(self):
"""Switch to speaking English"""
await self._switch_language("en")
@function_tool
async def switch_to_spanish(self):
"""Switch to speaking Spanish"""
await self._switch_language("es")
@function_tool
async def switch_to_french(self):
"""Switch to speaking French"""
await self._switch_language("fr")
@function_tool
async def switch_to_german(self):
"""Switch to speaking German"""
await self._switch_language("de")
@function_tool
async def switch_to_italian(self):
"""Switch to speaking Italian"""
await self._switch_language("it")
@server.rtc_session()
async def entrypoint(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
session = AgentSession(
stt=deepgram.STT(model="nova-2-general", language="en"),
llm=inference.LLM(model="openai/gpt-4o"),
tts=elevenlabs.TTS(model="eleven_turbo_v2_5", language="en"),
vad=ctx.proc.userdata["vad"],
preemptive_generation=True,
)
await session.start(agent=LanguageSwitcherAgent(), room=ctx.room)
await ctx.connect()
```
```python
if __name__ == "__main__":
cli.run_app(server)
```
## Run it
```bash
python elevenlabs_change_language.py console
```
Try saying:
- "Switch to Spanish"
- "Can you speak French?"
- "Let's talk in German"
- "Change to Italian"
## Supported languages
| Language | Code | Deepgram Code | Example Phrase |
| English | en | en | "Hello! How can I help you?" |
| Spanish | es | es | "¡Hola! ¿Cómo puedo ayudarte?" |
| French | fr | fr-CA | "Bonjour! Comment puis-je vous aider?" |
| German | de | de | "Hallo! Wie kann ich Ihnen helfen?" |
| Italian | it | it | "Ciao! Come posso aiutarti?" |
## How it works
1. The agent greets in English and waits for a language change request.
2. A function tool routes to `_switch_language()`, which updates both TTS and STT via `update_options()`.
3. The agent tracks the current language to avoid redundant switches.
4. A native greeting confirms the change, and the rest of the conversation stays in the selected language until switched again.
## Full example
```python
import logging
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, AgentServer, cli, inference, function_tool
from livekit.plugins import deepgram, elevenlabs, silero
load_dotenv()
logger = logging.getLogger("language-switcher")
logger.setLevel(logging.INFO)
server = AgentServer()
class LanguageSwitcherAgent(Agent):
def __init__(self) -> None:
super().__init__(
instructions="""
You are a helpful assistant communicating through voice.
You can switch to a different language if asked.
Don't use any unpronounceable characters.
"""
)
self.current_language = "en"
self.language_names = {
"en": "English",
"es": "Spanish",
"fr": "French",
"de": "German",
"it": "Italian",
}
self.deepgram_language_codes = {
"en": "en",
"es": "es",
"fr": "fr-CA",
"de": "de",
"it": "it",
}
self.greetings = {
"en": "Hello! I'm now speaking in English. How can I help you today?",
"es": "¡Hola! Ahora estoy hablando en español. ¿Cómo puedo ayudarte hoy?",
"fr": "Bonjour! Je parle maintenant en français. Comment puis-je vous aider aujourd'hui?",
"de": "Hallo! Ich spreche jetzt Deutsch. Wie kann ich Ihnen heute helfen?",
"it": "Ciao! Ora sto parlando in italiano. Come posso aiutarti oggi?",
}
async def on_enter(self):
await self.session.say(
"Hi there! I can speak in multiple languages including Spanish, French, German, and Italian. "
"Just ask me to switch to any of these languages. How can I help you today?"
)
async def _switch_language(self, language_code: str) -> None:
"""Helper method to switch the language"""
if language_code == self.current_language:
await self.session.say(f"I'm already speaking in {self.language_names[language_code]}.")
return
if self.session.tts is not None:
self.session.tts.update_options(language=language_code)
if self.session.stt is not None:
deepgram_language = self.deepgram_language_codes.get(language_code, language_code)
self.session.stt.update_options(language=deepgram_language)
self.current_language = language_code
await self.session.say(self.greetings[language_code])
@function_tool
async def switch_to_english(self):
"""Switch to speaking English"""
await self._switch_language("en")
@function_tool
async def switch_to_spanish(self):
"""Switch to speaking Spanish"""
await self._switch_language("es")
@function_tool
async def switch_to_french(self):
"""Switch to speaking French"""
await self._switch_language("fr")
@function_tool
async def switch_to_german(self):
"""Switch to speaking German"""
await self._switch_language("de")
@function_tool
async def switch_to_italian(self):
"""Switch to speaking Italian"""
await self._switch_language("it")
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
@server.rtc_session()
async def entrypoint(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
session = AgentSession(
stt=deepgram.STT(model="nova-2-general", language="en"),
llm=inference.LLM(model="openai/gpt-4o"),
tts=elevenlabs.TTS(model="eleven_turbo_v2_5", language="en"),
vad=ctx.proc.userdata["vad"],
preemptive_generation=True,
)
await session.start(agent=LanguageSwitcherAgent(), room=ctx.room)
await ctx.connect()
if __name__ == "__main__":
cli.run_app(server)
```
## Example conversation
```
Agent: "Hi there! I can speak in multiple languages..."
User: "Can you speak Spanish?"
Agent: "¡Hola! Ahora estoy hablando en español. ¿Cómo puedo ayudarte hoy?"
User: "¿Cuál es el clima?"
Agent: [Responds in Spanish about the weather]
User: "Now switch to French"
Agent: "Bonjour! Je parle maintenant en français. Comment puis-je vous aider aujourd'hui?"
```
---
This document was rendered at 2026-02-03T03:25:27.757Z.
For the latest version of this document, see [https://docs.livekit.io/recipes/changing_language.md](https://docs.livekit.io/recipes/changing_language.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/intro/basics/cli.md
LiveKit docs › Understanding LiveKit › LiveKit CLI › Overview
---
# CLI overview
> Command-line tools for managing LiveKit Cloud projects, creating applications, and streamlining your development workflow.
## Overview
The LiveKit CLI (`lk`) provides command-line tools for managing LiveKit Cloud projects, creating applications from templates, and streamlining your development workflow.
The CLI integrates with LiveKit Cloud, allowing you to authenticate, manage projects, and deploy applications directly from your terminal. It also works with self-hosted LiveKit servers for local development and testing.
## CLI components
Use the LiveKit CLI to manage projects and create applications:
| Component | Description | Use cases |
| **Setup** | Install the CLI, authenticate with LiveKit Cloud, and test your setup with example applications. | Getting started, initial setup, and testing your LiveKit deployment. |
| **Project management** | Use the CLI to add, list, and manage projects on LiveKit Cloud or self-hosted servers. | Managing multiple projects, switching between environments, and configuring project settings. |
| **App templates** | Create applications from prebuilt templates for Python, React, Android, Swift, Flutter, and more. | Bootstrapping new projects, prototyping applications, and starting with best practices. |
## In this section
Learn how to use the LiveKit CLI:
- **[Setup](https://docs.livekit.io/intro/basics/cli/start.md)**: Install the CLI, authenticate with LiveKit Cloud, and test your setup.
- **[Project management](https://docs.livekit.io/intro/basics/cli/projects.md)**: Add, list, and manage LiveKit projects using the CLI.
- **[App templates](https://docs.livekit.io/intro/basics/cli/templates.md)**: Create applications from prebuilt templates for various frameworks and platforms.
---
This document was rendered at 2026-02-03T03:24:51.615Z.
For the latest version of this document, see [https://docs.livekit.io/intro/basics/cli.md](https://docs.livekit.io/intro/basics/cli.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/reference/internals/client-protocol.md
LiveKit docs › Internals › Signaling Protocol
---
# Client Protocol
> This is an overview of the core protocol LiveKit uses to communicate with clients. It's primarily oriented towards those building new SDKs or developers interested in contributing to LiveKit.
> ℹ️ **Note**
>
> Using LiveKit in your app does not require you to understand the underlying protocol. This is one of our design goals.
## Basics
LiveKit clients use a WebSocket to communicate with the server over Protocol Buffers. Client could establish up to two WebRTC PeerConnections with the SFUs, used for publishing and receiving streams, respectively.
By default, the subscriber PeerConnection will always be open upon connection. The publisher PeerConnection will be established only when the client is ready to publish.

### Protobufs
LiveKit uses Protocol Buffers for all of its communications. Communication happens asynchronously: one side may send a message to the other at any time, without the expectation of an immediate response. LiveKit protobufs reside in the [livekit/protocol repo](https://github.com/livekit/protocol).
As a convention, a client always sends a `SignalRequest` and the server replies with a `SignalResponse`.
### Dedicated PeerConnections
For each client connected to the server, we use up to two separate `PeerConnection` objects. One for publishing tracks to the server, and the other for receiving subscribed tracks.
Using separate peer connections simplifies the negotiation process and eliminates negotiation [Glares](https://www.ietf.org/proceedings/82/slides/rtcweb-10.pdf). The side sending tracks to the other will be the one that initiates the offer.
## Joining a room
1. client initiates WebSocket connection to `/rtc`
2. server sends a `JoinResponse`, which includes room information, the current participant's data, and information about other participants in the room
3. server initiates the subscriber `PeerConnection`, sends `offer` to client- if `AutoSubscribe` is enabled, this offer will contain existing tracks in the room.
- the offer will include two data channels as part of the connection
4. client and server will exchange ICE candidates via `trickle`
5. client accepts the subscriber connection, sends an `answer`
6. ICE connectivity is established
7. server notifies other participants of the new participant
### WebSocket Parameters
Websocket endpoint `/rtc` is the initial step that the client connects to. It takes in several parameters to give the server information about the client and its capabilities:
- access_token: an encoded JWT access token
- reconnect: true if client is trying to resume to an existing connection. when this is set, server will attempt to perform a ICE restart after connection is established.
- auto_subscribe: true by default. If true, server will automatically subscribe client to all tracks in the room
- sdk: indicates the SDK it's using. (js, ios, android, etc)
- protocol: indicates the protocol version. this document descriibes the latest protocol version: 9
- version: version of the client SDK
## Publishing
To publish a track, a client must first notify the server of its intent and send up any client-defined metadata about the track.
1. client sends a `AddTrackRequest` with track metadata
2. server sends back a `TrackPublishedResponse`
3. client adds a transceiver to the `PeerConnection`, along with the media track
4. client initiates `offer`, sends to server
5. server answers the offer and starts receiving the track
6. if server subscribes other participants to the track
## Receiving tracks
LiveKit server sends down track metadata to all participants in a room as soon as it's published, then it adds the track to each client's subscriber `PeerConnection`.
## Server events
The client must also be ready to act upon other changes in the room. The server will notify clients of:
- `ParticipantUpdate`: when other participants join or leave, or if there are changes to their tracks
- `LeaveRequest`: when the participant should immediately disconnect
- `SpeakersChanged`: when the active speakers in the room changes
For all server events, clients should handle them in an idempotent way. For example, it's possible to receive multiple ParticipantUpdates with identical metadata.
### SpeakersChanged
Server will send down a list of `SpeakerInfo` that has changed from the last update. Clients are responsible for applying the deltas and firing the appropriate events.
## Client-initiated control
### Mute/unmute local tracks
WebRTC doesn't natively support muting tracks. When a track is disabled, it will continue to periodically send "empty" packets. With LiveKit (and SFUs, in general), we want a discrete mute event in order to notify other participants of the change and to optimize network consumption by suppressing empty packets.
To mute a track, set `MediaStreamTrack.enabled` to false, and subsequently send a `MuteTrackRequest` to the server with that track's `sid`.
### Changing quality of streams
For a particular client, `UpdateTrackSettings` informs the server whether a subscribed track should be temporarily paused, or if the server should send down a stream of differing quality. This is especially useful for larger rooms, when the client wants to optimize how much data it's receiving at once. For example, offscreen clients could have their streams temporarily paused.
### Subscription control
Clients also have the ability to control which tracks they're subscribed to. An `UpdateSubscription` message allows the client to subscribe or unsubscribe to published tracks.
---
This document was rendered at 2026-02-03T03:25:26.543Z.
For the latest version of this document, see [https://docs.livekit.io/reference/internals/client-protocol.md](https://docs.livekit.io/reference/internals/client-protocol.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/home/client.md
LiveKit docs › LiveKit SDKs › Overview
---
# LiveKit SDKs Overview
> A guide to LiveKit's client and server SDKs for building realtime applications.
## Overview
LiveKit provides a comprehensive ecosystem of SDKs for building realtime applications, including **realtime SDKs** for building user-facing applications, and **server-side SDKs** for backend operations and media processing. The SDKs are designed to work together, and support multiple platforms and languages.
## Realtime SDKs
Realtime SDKs let you build applications that connect to LiveKit rooms and participate in realtime communication. These SDKs handle WebRTC connections, media capture, and room management.
### Web and mobile platforms
These are the primary client platforms used for building realtime applications. Each SDK is optimized for its target platform and provides native integration capabilities.
- **[JavaScript SDK](https://github.com/livekit/client-sdk-js)**: JavaScript/TypeScript SDK for web browsers. Supports all major browsers and provides React hooks for easy integration.
- **[iOS/macOS/visionOS](https://github.com/livekit/client-sdk-swift)**: Native Swift SDK for Apple platforms including iOS, macOS, and visionOS. Optimized for Apple's ecosystem.
- **[Android](https://github.com/livekit/client-sdk-android)**: Native Kotlin SDK for Android applications. Provides comprehensive media handling and room management.
- **[Flutter](https://github.com/livekit/client-sdk-flutter)**: Cross-platform SDK for Flutter applications. Write once, run on iOS, Android, web, and desktop.
- **[React Native](https://github.com/livekit/client-sdk-react-native)**: React Native SDK for building cross-platform mobile applications with JavaScript/TypeScript.
- **[Unity](https://github.com/livekit/client-sdk-unity)**: Unity SDK for game development and virtual reality applications. Supports both native and WebGL builds.
### Additional client platforms
LiveKit also supports specialized platforms and use cases beyond the main web and mobile platforms:
- **[Rust SDK](https://github.com/livekit/rust-sdks)**: For systems programming and embedded applications.
- **[Unity WebGL](https://github.com/livekit/client-sdk-unity-web)**: For web-based Unity applications.
- **[ESP32](https://github.com/livekit/client-sdk-esp32)**: For IoT and embedded devices.
## Server-side SDKs
Server-side SDKs provide backend integration capabilities, enabling you to create programmatic participants, manage rooms, and process media streams. They can also generate access tokens, call server APIs, and receive webhooks.
The Go SDK additionally offers client capabilities, allowing you to build automations that act like end users.
### Core server SDKs
- **[Node.js](https://github.com/livekit/node-sdks)**: JavaScript SDK for Node.js applications. Includes room management, participant control, and webhook handling.
- **[Python](https://github.com/livekit/python-sdks)**: Python SDK for backend applications. Provides comprehensive media processing and room management capabilities.
- **[Golang](https://github.com/livekit/server-sdk-go)**: Go SDK for high-performance server applications. Optimized for scalability and low latency. Includes client capabilities.
- **[Ruby](https://github.com/livekit/server-sdk-ruby)**: Ruby SDK for Ruby on Rails and other Ruby applications. Full-featured server integration.
- **[Java/Kotlin](https://github.com/livekit/server-sdk-kotlin)**: Java and Kotlin SDK for JVM-based applications. Enterprise-ready with comprehensive features.
- **[Rust](https://github.com/livekit/rust-sdks)**: Rust SDK for systems programming and high-performance applications. Memory-safe and fast.
### Community SDKs
- **[PHP](https://github.com/agence104/livekit-server-sdk-php)**: Community-maintained SDK for PHP applications.
- **[.NET](https://github.com/pabloFuente/livekit-server-sdk-dotnet)**: Community-maintained SDK for .NET applications.
## UI Components
LiveKit provides pre-built UI components to accelerate development:
- **[React Components](https://github.com/livekit/components-js)**: React components for video, audio, and chat interfaces. Drop-in components for rapid development.
- **[Android Compose](https://github.com/livekit/components-android)**: Jetpack Compose components for Android applications. Modern UI components for Android development.
- **[SwiftUI](https://github.com/livekit/components-swift)**: SwiftUI components for iOS and macOS applications. Native UI components for Apple platforms.
- **[Flutter](https://github.com/livekit/components-flutter)**: Flutter widgets for cross-platform applications. Reusable UI components for Flutter apps.
## Agents Framework
LiveKit provides the Agents Framework for building AI agents and programmatic participants:
- **[Agents docs](https://docs.livekit.io/agents.md)**: Learn how to build voice AI agents using the Agents Framework.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Voice AI agent quickstart guide. The fastest way to get an agent up and running.
- **[Agents Framework](https://github.com/livekit/agents)**: Python framework for building AI agents and programmatic participants. Production-ready with comprehensive AI integrations.
- **[AgentsJS](https://github.com/livekit/agents-js)**: JavaScript/TypeScript framework for building AI agents. Modern architecture with TypeScript support.
## Telephony Integration
LiveKit's SIP integration enables your applications to connect with traditional phone systems and telephony infrastructure. Server-side SDKs include SIP capabilities for building telephony applications. To learn more, see [SIP](https://docs.livekit.io/sip.md).
## Key features across SDKs
LiveKit SDKs provide a consistent set of features across all platforms, ensuring that your applications work reliably regardless of the target platform. These core capabilities are designed to handle the complexities of realtime communication while providing a simple, unified API.
### Realtime capabilities
Realtime SDKs focus on connecting users to LiveKit rooms and managing realtime communication. These capabilities enable applications to capture, transmit, and receive media streams with minimal latency.
- **Media capture**: Camera, microphone, and screen sharing.
- **Room management**: Join, leave, and manage room participants.
- **Track handling**: Subscribe to and publish audio and video tracks.
- **Data channels**: Realtime messaging between participants.
- **Connection management**: Automatic reconnection and quality adaptation.
### Server-side capabilities
Server-side SDKs provide the infrastructure and control needed to manage LiveKit rooms and participants. These capabilities enable backend applications to orchestrate realtime sessions and process media streams.
- **Room control**: Create, manage, and monitor rooms.
- **Participant management**: Control participant permissions and behavior.
- **Media processing**: Subscribe to and process media streams.
- **Webhook handling**: Respond to room and participant events.
- **Recording**: Capture and store room sessions.
### Cross-platform consistency
All SDKs provide consistent APIs and features across platforms:
- **Unified room model**: Same room concepts across all platforms.
- **Consistent track handling**: Standardized audio and video track management.
- **Shared data APIs**: Common data channel and messaging patterns.
- **Quality adaptation**: Automatic quality adjustment based on network conditions.
## Getting started
To get started with LiveKit SDKs:
1. **Choose your platform**: Select the appropriate client and server SDKs for your use case.
2. **Set up LiveKit**: Deploy LiveKit server or use [LiveKit Cloud](https://livekit.io/cloud).
3. **Build your app**: Use the SDKs to create your realtime application.
4. **Add UI components**: Integrate pre-built components for faster development.
5. **Deploy and scale**: Use LiveKit's production-ready infrastructure.
To get started with LiveKit Agents, see the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
---
This document was rendered at 2025-11-18T23:54:50.000Z.
For the latest version of this document, see [https://docs.livekit.io/home/client.md](https://docs.livekit.io/home/client.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/intro/cloud.md
LiveKit docs › Understanding LiveKit › LiveKit Cloud
---
# LiveKit Cloud
> An end-to-end platform for building, deploying, and operating AI agent applications.
## Overview
LiveKit Cloud is a fully managed, globally distributed platform for building, hosting, and operating AI agent applications at scale.
While LiveKit's open-source server provides the realtime media foundation, LiveKit Cloud extends beyond managed infrastructure. It combines realtime audio, video, and data streaming with agent development tools, managed agent hosting, built-in inference, native telephony, and production-grade observability in a single, cohesive platform.
## What LiveKit Cloud includes
**Realtime communication core**: A fully managed, globally distributed mesh of LiveKit servers that powers low-latency audio, video, and data streaming for realtime applications.
**Agent Builder**: Design, test, and iterate on AI agents using a purpose-built development experience. Agent Builder streamlines prompt design, tool configuration, and interaction flows.
**Managed agent hosting**: Deploy and run agents directly on LiveKit Cloud without managing servers or orchestration. LiveKit handles scaling, lifecycle management, isolation, and upgrades.
**Built-in inference**: LiveKit Inference lets you run supported AI models directly within the LiveKit Cloud environment without requiring API keys.
**Native telephony**: LiveKit Phone Numbers lets you provision phone numbers and connect PSTN calls directly into LiveKit rooms without setting up trunks.
**Observability and operations**: Production-grade analytics, logs, and quality metrics are built into the LiveKit Cloud dashboard, giving visibility into agent behavior, media quality, usage, and performance across your deployment.
- **[Dashboard](https://cloud.livekit.io)**: Sign up for LiveKit Cloud to manage projects, configure agents and telephony, and view detailed analytics.
- **[Pricing](https://livekit.io/pricing)**: View LiveKit Cloud pricing plans and choose the right option for your application's needs.
### Why choose LiveKit Cloud?
- **End-to-end platform**: Build, deploy, and operate AI agents, realtime media, inference, and telephony in one system.
- **Zero operational overhead**: No need to manage servers, scaling, or infrastructure.
- **Global edge network**: Users connect to the closest region for minimal latency.
- **Elastic, unlimited scale**: Support for rooms with unlimited participants using LiveKit's global mesh architecture.
- **Enterprise-grade reliability**: 99.99% uptime guarantee with redundant infrastructure.
- **Comprehensive analytics**: Monitor usage, performance, and quality metrics through the LiveKit Cloud dashboard.
- **Seamless developer experience**: Use the same APIs and SDKs as open source, with additional cloud-native capabilities.
### Open source compatible, platform complete
LiveKit Cloud runs the same open-source LiveKit server available on [GitHub](https://github.com/livekit/livekit) and supports the same APIs and SDKs. This means:
- You can start on open source and migrate to LiveKit Cloud without rewriting application code.
- You can move from LiveKit Cloud to self-hosted if your requirements change.
- Your client and agent code remains portable—the connection endpoint is the primary difference.
What does differ is everything around the server: agent tooling, hosting, inference, telephony, global scaling, and observability, all of which are native features of LiveKit Cloud.
### Comparing LiveKit Cloud to self-hosted
When building with LiveKit, you can run the open-source server yourself or use LiveKit Cloud as a fully managed, end-to-end platform:
| | Self-hosted | LiveKit Cloud |
| **Realtime media (audio, video, data)** | Full support | Full support |
| **Egress (recording, streaming)** | Full support | Full support |
| **Ingress (RTMP, WHIP, SRT ingest)** | Full support | Full support |
| **SIP & telephony** | Full support | Full support including native telephony support for fully managed LiveKit Phone Numbers |
| **Agents framework** | Full support | Full support, including managed agent hosting. |
| **Agent Builder** | N/A | Included |
| **Built-in inference** | N/A | Included |
| **Who manages it** | You | LiveKit |
| **Architecture** | Single-home SFU | Global mesh SFU |
| **Connection model** | Single server per room | Each user connects to the nearest edge. |
| **Max users per room** | Up to ~3,000 | No limit |
| **Analytics & telemetry** | Custom / external. | LiveKit Cloud dashboard |
| **Uptime guarantees** | N/A | 99.99% |
## LiveKit Cloud administration
For information about LiveKit Cloud architecture, administration, and configuration, see the [Administration](https://docs.livekit.io/deploy/admin.md) section.
## Next steps
Ready to deploy your agents? Get started with the [Agent deployment guide](https://docs.livekit.io/deploy/agents.md).
---
This document was rendered at 2026-02-03T03:24:52.329Z.
For the latest version of this document, see [https://docs.livekit.io/intro/cloud.md](https://docs.livekit.io/intro/cloud.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/stt/plugins/clova.md
LiveKit docs › Models › STT › Plugins › Clova
---
# CLOVA STT plugin guide
> How to use the Clova STT plugin for LiveKit Agents.
Available in:
- [ ] Node.js
- [x] Python
## Overview
This plugin allows you to use [CLOVA Speech Recognition](https://guide.ncloud-docs.com/docs/en/csr-overview) as an STT provider for your voice agents.
## Quick reference
This section provides a brief overview of the CLOVA STT plugin. For more information, see [Additional resources](#additional-resources).
### Installation
Install the plugin from PyPI:
```shell
uv add "livekit-agents[clova]~=1.3"
```
### Authentication
The CLOVA plugin requires the following keys, which may set as environment variables or passed to the constructor.
```shell
CLOVA_STT_SECRET_KEY=
CLOVA_STT_INVOKE_URL=
```
### Usage
Create a CLOVA STT to use within an `AgentSession` or as a standalone transcription service. For example, you can use this STT in the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
```python
from livekit.plugins import clova
session = AgentSession(
stt = clova.STT(
word_boost=["LiveKit"],
),
# ... llm, tts, etc.
)
```
### Parameters
This section describes some of the available parameters. See the [plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/clova/index.html.md#livekit.plugins.clova.STT) for a complete list of all available parameters.
- **`language`** _(ClovaSttLanguages)_ (optional) - Default: `en-US`: Speech recognition language. Clova supports English, Korean, Japanese, and Chinese. Valid values are `ko-KR`, `en-US`, `enko`, `ja`, `zh-cn`, `zh-tw`.
## Additional resources
The following resources provide more information about using CLOVA with LiveKit Agents.
- **[Python package](https://pypi.org/project/livekit-plugins-clova/)**: The `livekit-plugins-clova` package on PyPI.
- **[Plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/clova/index.html.md#livekit.plugins.clova.STT)**: Reference for the CLOVA STT plugin.
- **[GitHub repo](https://github.com/livekit/agents/tree/main/livekit-plugins/livekit-plugins-clova)**: View the source or contribute to the LiveKit CLOVA STT plugin.
- **[CLOVA docs](https://guide.ncloud-docs.com/docs/en/csr-overview)**: CLOVA's full docs site.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Get started with LiveKit Agents and CLOVA.
---
This document was rendered at 2026-02-03T03:25:03.126Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/stt/plugins/clova.md](https://docs.livekit.io/agents/models/stt/plugins/clova.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/telephony/features/transfers/cold.md
LiveKit docs › Features › Transfers › Call forwarding
---
# Call forwarding
> Transfer calls to another number or SIP endpoint using SIP REFER.
A _cold transfer_ refers to forwarding a caller to another phone number or SIP endpoint. Performing a cold transfer closes the caller’s LiveKit session.
For transfers that include an AI agent to provide context, see the [Agent-assisted transfer](https://docs.livekit.io/telephony/features/transfers/warm.md) guide.
## How it works
To transfer a caller out of a LiveKit room to another phone number, use the following steps:
1. Call the `TransferSIPParticipant` API.
2. LiveKit sends a SIP REFER through your trunk, instructing the provider to connect the caller to the new number or SIP endpoint.
3. The caller leaves the LiveKit room, ending the session.
## Transferring a SIP participant using SIP REFER
REFER is a SIP method that allows you to move an active session to another endpoint (that is, transfer a call). For LiveKit telephony apps, you can use the [`TransferSIPParticipant`](https://docs.livekit.io/reference/telephony/sip-api.md#transfersipparticipant) server API to transfer a caller to another phone number or SIP endpoint.
In order to successfully transfer calls, you must configure your provider trunks to allow call transfers.
### Enable call transfers for your Twilio SIP trunk
Enable call transfer and PSTN transfers for your Twilio SIP trunk. To learn more, see Twilio's [Call Transfer via SIP REFER](https://www.twilio.com/docs/sip-trunking/call-transfer) documentation.
When you transfer a call, you have the option to set the caller ID to display the phone number of the transferee (the caller) or the transferor (the phone number associated with your LiveKit trunk).
**CLI**:
The following command enables call transfers and sets the caller ID to display the number of the transferee:
> ℹ️ **Note**
>
> - To list trunks, execute `twilio api trunking v1 trunks list`.
> - To set the caller ID to the transferor, set `transfer-caller-id` to `from-transferor`.
```shell
twilio api trunking v1 trunks update --sid \
--transfer-mode enable-all \
--transfer-caller-id from-transferee
```
---
**Console**:
1. Sign in to the [Twilio console](https://console.twilio.com).
2. Navigate to **Elastic SIP Trunking** » **Manage** » **Trunks**, and select a trunk.
3. In the **Features** » **Call Transfer (SIP REFER)** section, select **Enabled**.
4. In the **Caller ID for Transfer Target** field, select an option.
5. Select **Enable PSTN Transfer**.
6. Save your changes.
### Usage
Set up the following environment variables:
```shell
export LIVEKIT_URL=%{wsURL}%
export LIVEKIT_API_KEY=%{apiKey}%
export LIVEKIT_API_SECRET=%{apiSecret}%
```
**Node.js**:
This example uses the LiveKit URL, API key, and secret set as environment variables.
```typescript
import { SipClient } from 'livekit-server-sdk';
// ...
async function transferParticipant(participant) {
console.log("transfer participant initiated");
const sipTransferOptions = {
playDialtone: false
};
const sipClient = new SipClient(process.env.LIVEKIT_URL,
process.env.LIVEKIT_API_KEY,
process.env.LIVEKIT_API_SECRET);
const transferTo = "tel:+15105550100";
try {
await sipClient.transferSipParticipant('open-room', participant.identity, transferTo, sipTransferOptions);
console.log("SIP participant transferred successfully");
} catch (error) {
if (error instanceof TwirpError && error.metadata != null) {
console.error("SIP error code: ", error.metadata?.['sip_status_code']);
console.error("SIP error message: ", error.metadata?.['sip_status']);
} else {
console.error("Error transferring SIP participant: ", error);
}
}
}
```
---
**Python**:
```python
import asyncio
import logging
import os
from livekit import api
from livekit.protocol.sip import TransferSIPParticipantRequest
logger = logging.getLogger("transfer-logger")
logger.setLevel(logging.INFO)
async def transfer_call(participant_identity: str, room_name: str) -> None:
async with api.LiveKitAPI() as livekit_api:
transfer_to = 'tel:+14155550100'
try:
# Create transfer request
transfer_request = TransferSIPParticipantRequest(
participant_identity=participant_identity,
room_name=room_name,
transfer_to=transfer_to,
play_dialtone=False
)
logger.debug(f"Transfer request: {transfer_request}")
# Transfer caller
await livekit_api.sip.transfer_sip_participant(transfer_request)
print("SIP participant transferred successfully")
except Exception as error:
# Check if it's a Twirp error with metadata
if hasattr(error, 'metadata') and error.metadata:
print(f"SIP error code: {error.metadata.get('sip_status_code')}")
print(f"SIP error message: {error.metadata.get('sip_status')}")
else:
print(f"Error transferring SIP participant:")
print(f"{error.status} - {error.code} - {error.message}")
```
For a full example using a voice agent, DTMF, and SIP REFER, see the [phone assistant example](https://github.com/ShayneP/phone-assistant).
---
**Ruby**:
```ruby
require 'livekit'
room_name = 'open-room'
participant_identity = 'participant_identity'
def transferParticipant(room_name, participant_identity)
sip_service = LiveKit::SIPServiceClient.new(
ENV['LIVEKIT_URL'],
api_key: ENV['LIVEKIT_API_KEY'],
api_secret: ENV['LIVEKIT_API_SECRET']
)
transfer_to = 'tel:+14155550100'
response = sip_service.transfer_sip_participant(
room_name,
participant_identity,
transfer_to,
play_dialtone: false
)
if response.error then
puts "Error: #{response.error}"
else
puts "SIP participant transferred successfully"
end
end
```
---
**Go**:
```go
import (
"context"
"fmt"
"os"
"github.com/livekit/protocol/livekit"
lksdk "github.com/livekit/server-sdk-go/v2"
)
func transferParticipant(ctx context.Context, participantIdentity string) {
fmt.Println("Starting SIP participant transfer...")
roomName := "open-room"
transferTo := "tel:+14155550100"
// Create a transfer request
transferRequest := &livekit.TransferSIPParticipantRequest{
RoomName: roomName,
ParticipantIdentity: participantIdentity,
TransferTo: transferTo,
PlayDialtone: false,
}
fmt.Println("Creating SIP client...")
sipClient := lksdk.NewSIPClient(os.Getenv("LIVEKIT_URL"),
os.Getenv("LIVEKIT_API_KEY"),
os.Getenv("LIVEKIT_API_SECRET"))
// Execute transfer request
fmt.Println("Executing transfer request...")
_, err := sipClient.TransferSIPParticipant(ctx, transferRequest)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("SIP participant transferred successfully")
}
```
---
**CLI**:
```shell
lk sip participant transfer --room \
--identity \
--to "
```
Where `` is a valid SIP endpoint or telephone number. The following examples are valid formats:
- `tel:+15105550100`
- `sip:+15105550100@sip.telnyx.com`
- `sip:+15105550100@my-livekit-demo.pstn.twilio.com`
---
This document was rendered at 2026-02-03T03:25:11.478Z.
For the latest version of this document, see [https://docs.livekit.io/telephony/features/transfers/cold.md](https://docs.livekit.io/telephony/features/transfers/cold.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/intro/community.md
LiveKit docs › Get Started › Community resources
---
# Community resources
> Join an active community of LiveKit developers.
## Overview
LiveKit's community offers a variety of resources to help you build your next voice, video, or physical AI agent.
Not sure where to start? Check out the following links.
### GitHub
LiveKit is an open source project that empowers developers to build real-time voice, video, and physical AI agents. The LiveKit GitHub repositories contain the source code for LiveKit and examples. You can contribute to them by submitting pull requests.
- **[LiveKit](https://github.com/livekit)**: Core LiveKit repositories.
- **[LiveKit Examples](https://github.com/livekit-examples)**: Getting started resources like starter templates and agents examples.
### Slack
LiveKit maintains a free Slack community as an active forum to ask questions, get feedback, and meet others building with LiveKit.
- **[Join LiveKit Slack](https://livekit.io/join-slack)**: Join the LiveKit community on Slack to ask questions, get feedback, and meet other developers.
### Social media
Check out the following social media channels for the latest news and updates.
- **[YouTube](https://www.youtube.com/@livekit_io)**: Watch LiveKit videos and tutorials on YouTube.
- **[LinkedIn](https://www.linkedin.com/company/livekitco/)**: Follow LiveKit on LinkedIn for company updates and news.
- **[X (Twitter)](https://x.com/livekit)**: Follow LiveKit on X for the latest updates and announcements.
- **[@davidzh](https://x.com/davidzh)**: Follow LiveKit co-founder and CTO David Zhao on X.
- **[@dsa](https://x.com/dsa)**: Follow LiveKit co-founder and CEO Russ d'Sa on X.
### Subreddit
The LiveKit team moderates an official subreddit for community questions, discussion, and feedback.
- **[LiveKit Subreddit](https://www.reddit.com/r/livekit/)**: Join the LiveKit community on Reddit to discuss LiveKit and get help from the community.
### Events
LiveKit regularly hosts events, both virtual and in-person, for developers to meet and hear from the LiveKit team and community. Most in-person events are in San Francisco, CA.
We host recurring event series, including Voice Mode, where you can learn about the latest features and best practices for building voice AI agents.
- **[LiveKit Events](https://luma.com/user/LiveKit_Events)**: View our event calendar to see upcoming LiveKit-hosted events and events where LiveKit team members are speaking.
---
This document was rendered at 2026-02-03T03:24:51.301Z.
For the latest version of this document, see [https://docs.livekit.io/intro/community.md](https://docs.livekit.io/intro/community.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/recipes/company-directory.md
LiveKit docs › Telephony › Company Directory
---
# Company directory phone assistant
> Build a phone assistant that can transfer calls to different departments using SIP REFER.
In this recipe, build a phone assistant that transfers callers to different departments via SIP REFER. This guide focuses on how to set up DTMF handling and how to manage the actual call transfers to Billing, Technical Support, or Customer Service.
## Prerequisites
To complete this guide, you need the following prerequisites:
- Create an agent using the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
- Set up LiveKit SIP to [accept inbound calls](https://docs.livekit.io/telephony/accepting-calls/workflow-setup.md#setup-for-accepting-calls)
## Setting up the environment
First, create an environment file with the necessary credentials and phone numbers:
```python
# Initialize environment variables
# The .env.local file should look like:
# OPENAI_API_KEY=your-key-here
# BILLING_PHONE_NUMBER=+12345678901
# TECH_SUPPORT_PHONE_NUMBER=+12345678901
# CUSTOMER_SERVICE_PHONE_NUMBER=+12345678901
# LIVEKIT_URL=wss://your-url-goes-here.livekit.cloud
# LIVEKIT_API_KEY=your-key-here
# LIVEKIT_API_SECRET=your-secret-here
from dotenv import load_dotenv
load_dotenv(dotenv_path=".env.local")
```
## Implementing the phone assistant
Create a custom Agent class that extends the base `Agent` class:
```python
from __future__ import annotations
import asyncio
import logging
import os
from dataclasses import dataclass
from typing import Annotated, Optional
from livekit import rtc, api
from livekit.agents import JobContext, WorkerOptions
from livekit.agents.llm import function_tool
from livekit.agents.voice import Agent, AgentSession, RunContext
from livekit.protocol import sip as proto_sip
from livekit.plugins import openai, silero
from pydantic import Field
logger = logging.getLogger("phone-assistant")
logger.setLevel(logging.INFO)
@dataclass
class UserData:
"""Store user data and state for the phone assistant."""
selected_department: Optional[str] = None
livekit_api: Optional[api.LiveKitAPI] = None
ctx: Optional[JobContext] = None
RunContext_T = RunContext[UserData]
class PhoneAssistant(Agent):
"""
A voice-enabled phone assistant that handles voice interactions.
You can transfer the call to a department based on the DTMF digit pressed by the user.
"""
def __init__(self) -> None:
"""
Initialize the PhoneAssistant with customized instructions.
"""
instructions = (
"You are a friendly assistant providing support. "
"Please inform users they can:\n"
"- Press 1 for Billing\n"
"- Press 2 for Technical Support\n"
"- Press 3 for Customer Service"
)
super().__init__(instructions=instructions)
async def on_enter(self) -> None:
"""Called when the agent is first activated."""
logger.info("PhoneAssistant activated")
greeting = (
"Hi, thanks for calling Vandelay Industries — global leader in fine latex goods! "
"You can press 1 for Billing, 2 for Technical Support, "
"or 3 for Customer Service. You can also just talk to me, since I'm a LiveKit agent."
)
await self.session.generate_reply(user_input=greeting)
```
## Implementing transfer functionality
Add methods to handle transfers for different departments:
```python
@function_tool()
async def transfer_to_billing(self, context: RunContext_T) -> str:
"""Transfer the call to the billing department."""
room = context.userdata.ctx.room
identity = room.local_participant.identity
transfer_number = f"tel:{os.getenv('BILLING_PHONE_NUMBER')}"
dept_name = "Billing"
context.userdata.selected_department = dept_name
await self._handle_transfer(identity, transfer_number, dept_name)
return f"Transferring to {dept_name} department."
@function_tool()
async def transfer_to_tech_support(self, context: RunContext_T) -> str:
"""Transfer the call to the technical support department."""
room = context.userdata.ctx.room
identity = room.local_participant.identity
transfer_number = f"tel:{os.getenv('TECH_SUPPORT_PHONE_NUMBER')}"
dept_name = "Tech Support"
context.userdata.selected_department = dept_name
await self._handle_transfer(identity, transfer_number, dept_name)
return f"Transferring to {dept_name} department."
@function_tool()
async def transfer_to_customer_service(self, context: RunContext_T) -> str:
"""Transfer the call to the customer service department."""
room = context.userdata.ctx.room
identity = room.local_participant.identity
transfer_number = f"tel:{os.getenv('CUSTOMER_SERVICE_PHONE_NUMBER')}"
dept_name = "Customer Service"
context.userdata.selected_department = dept_name
await self._handle_transfer(identity, transfer_number, dept_name)
return f"Transferring to {dept_name} department."
async def _handle_transfer(self, identity: str, transfer_number: str, department: str) -> None:
"""
Handle the transfer process with department-specific messaging.
Args:
identity (str): The participant's identity
transfer_number (str): The number to transfer to
department (str): The name of the department
"""
await self.session.generate_reply(user_input=f"Transferring you to our {department} department in a moment. Please hold.")
await asyncio.sleep(6)
await self.transfer_call(identity, transfer_number)
```
## Handling SIP call transfers
Implement the actual call transfer logic using SIP REFER:
```python
async def transfer_call(self, participant_identity: str, transfer_to: str) -> None:
"""
Transfer the SIP call to another number.
Args:
participant_identity (str): The identity of the participant.
transfer_to (str): The phone number to transfer the call to.
"""
logger.info(f"Transferring call for participant {participant_identity} to {transfer_to}")
try:
userdata = self.session.userdata
if not userdata.livekit_api:
livekit_url = os.getenv('LIVEKIT_URL')
api_key = os.getenv('LIVEKIT_API_KEY')
api_secret = os.getenv('LIVEKIT_API_SECRET')
userdata.livekit_api = api.LiveKitAPI(
url=livekit_url,
api_key=api_key,
api_secret=api_secret
)
transfer_request = proto_sip.TransferSIPParticipantRequest(
participant_identity=participant_identity,
room_name=userdata.ctx.room.name,
transfer_to=transfer_to,
play_dialtone=True
)
await userdata.livekit_api.sip.transfer_sip_participant(transfer_request)
except Exception as e:
logger.error(f"Failed to transfer call: {e}", exc_info=True)
await self.session.generate_reply(user_input="I'm sorry, I couldn't transfer your call. Is there something else I can help with?")
```
## Setting up DTMF handling
Set up handlers to listen for DTMF tones and act on them:
```python
def setup_dtmf_handlers(room: rtc.Room, phone_assistant: PhoneAssistant):
"""
Setup DTMF event handlers for the room.
Args:
room: The LiveKit room
phone_assistant: The phone assistant agent
"""
async def _async_handle_dtmf(dtmf_event: rtc.SipDTMF):
"""Asynchronous logic for handling DTMF tones."""
await phone_assistant.session.interrupt()
logger.info("Interrupted agent due to DTMF")
code = dtmf_event.code
digit = dtmf_event.digit
identity = dtmf_event.participant.identity
department_numbers = {
"1": ("BILLING_PHONE_NUMBER", "Billing"),
"2": ("TECH_SUPPORT_PHONE_NUMBER", "Tech Support"),
"3": ("CUSTOMER_SERVICE_PHONE_NUMBER", "Customer Service")
}
if digit in department_numbers:
env_var, dept_name = department_numbers[digit]
transfer_number = f"tel:{os.getenv(env_var)}"
userdata = phone_assistant.session.userdata
userdata.selected_department = dept_name
await phone_assistant._handle_transfer(identity, transfer_number, dept_name)
else:
await phone_assistant.session.generate_reply(user_input="I'm sorry, please choose one of the options I mentioned earlier.")
@room.on("sip_dtmf_received")
def handle_dtmf(dtmf_event: rtc.SipDTMF):
"""
Synchronous handler for DTMF signals that schedules the async logic.
Args:
dtmf_event (rtc.SipDTMF): The DTMF event data.
"""
asyncio.create_task(_async_handle_dtmf(dtmf_event))
```
## Starting the agent
Finally, implement the entrypoint to start the agent:
```python
async def entrypoint(ctx: JobContext) -> None:
"""
The main entry point for the phone assistant application.
Args:
ctx (JobContext): The context for the job.
"""
userdata = UserData(ctx=ctx)
session = AgentSession(
userdata=userdata,
llm=openai.realtime.RealtimeModel(voice="sage"),
vad=silero.VAD.load(),
max_tool_steps=3
)
phone_assistant = PhoneAssistant()
setup_dtmf_handlers(ctx.room, phone_assistant)
await session.start(
room=ctx.room,
agent=phone_assistant
)
disconnect_event = asyncio.Event()
@ctx.room.on("disconnected")
def on_room_disconnect(*args):
disconnect_event.set()
try:
await disconnect_event.wait()
finally:
if userdata.livekit_api:
await userdata.livekit_api.aclose()
userdata.livekit_api = None
if __name__ == "__main__":
from livekit.agents import cli
cli.run_app(WorkerOptions(entrypoint_fnc=entrypoint))
```
## How it works
1. When a call is received, the agent answers and provides instructions to the caller.
2. The caller can press 1, 2, or 3 to select a department:- 1 for Billing
- 2 for Technical Support
- 3 for Customer Service
3. When a DTMF tone is detected, the agent:- Interrupts the current conversation
- Notifies the caller they are being transferred
- Initiates a SIP REFER to transfer the call to the selected department
4. If the caller presses a different key, they are prompted to select a valid option.
The agent also supports regular voice conversations, so callers can ask questions directly before being transferred!
For the complete code, see the [phone assistant repository](https://github.com/livekit-examples/phone-assistant).
---
This document was rendered at 2026-02-03T03:25:28.478Z.
For the latest version of this document, see [https://docs.livekit.io/recipes/company-directory.md](https://docs.livekit.io/recipes/company-directory.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/frontends/components.md
LiveKit docs › UI Components › Overview
---
# UI components overview
> An overview of UI components for LiveKit frontends.

## Overview
LiveKit provides prebuilt UI components for React, Shadcn, SwiftUI, Android, and Flutter to simplify frontend development for AI agent apps. These components abstract away complexity by managing state synchronization, track rendering, and room interactions, allowing developers to focus on building agent features rather than infrastructure.
> ℹ️ **Note**
>
> This section provides an overview of UI components for agent frontends. For complete component documentation, API references, and usage examples, see the [UI Components](https://docs.livekit.io/reference.md#ui-components) section in Reference.
## Component libraries
LiveKit offers UI component libraries for popular frontend frameworks:
| Framework | Description | Use cases |
| **Agents UI** | The fastest way to build web based, multi-modal, agentic experiences with LiveKit's platform primitives. | React web application featuring voice and avatar powered AI assistants **built with [Shadcn](https://ui.shadcn.com).** |
| **React components** | Low-level React components and hooks for building realtime audio and video applications with LiveKit's platform primitives. | React web applications featuring video conferencing, and realtime collaboration interfaces. |
| **Swift components** | SwiftUI components for iOS, macOS, visionOS, and tvOS applications with native platform integration. | Native iOS apps, macOS desktop applications, and Apple platform video conferencing experiences. |
| **Android components** | Jetpack Compose components for Android applications with Material Design integration. | Native Android apps, mobile video conferencing, and Android-based realtime communication. |
| **Flutter components** | Flutter widgets for cross-platform mobile and desktop applications. | Cross-platform mobile apps, desktop applications, and multi-platform realtime experiences. |
## In this section
For detailed component documentation, API references, and usage examples, see the [UI components](https://docs.livekit.io/reference.md#ui-components) reference section.
- **[Agents UI](https://docs.livekit.io/frontends/components/agents-ui.md)**: The fastest way to build web based, multi-modal, agentic experiences on top of LiveKit's platform primitives
- **[UI components reference](https://docs.livekit.io/reference.md#ui-components)**: Complete component documentation, API references, and usage examples for React, Swift, Android, and Flutter.
---
This document was rendered at 2026-02-03T03:25:08.713Z.
For the latest version of this document, see [https://docs.livekit.io/frontends/components.md](https://docs.livekit.io/frontends/components.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/media/ingress-egress/egress/composite-recording.md
LiveKit docs › Media › Stream export & import › Egress › RoomComposite & web egress
---
# RoomComposite & web egress
> LiveKit web-based recorder gives you flexible compositing options.
## Composite recording
Composite recordings use a web-based recorder to capture a composited view of a room, including all participants, interactions, and any customized UI elements from the application.
There are two options for composite recording:
- **RoomComposite**: A composite recording tied to a room's lifecycle. When all of the participants leave the room, the recording would stop automatically.
- **Web**: A standalone composite recording can be started and stopped independently of a room's lifecycle. Web Egress can be used to record any web-based content, even if it's not part of a LiveKit room.
## RoomComposite egress
One common requirement when recording a room is to capture all of the participants and interactions that take place. This can be challenging in a multi-user application, where different users may be joining, leaving, or turning their cameras on and off. You may also want the recording to look as close to the actual application experience as possible, capturing the richness and interactivity of your application.
A RoomComposite egress uses a web app to create the composited view, rendering the output with an instance of headless Chromium. In most cases, your existing LiveKit application can be used as a compositing template with few modifications.
### Default layouts
We provide a few default compositing layouts that works out of the box. They'll be used by default if a custom template URL is not passed in. These templates are deployed alongside and served by the Egress service ([source](https://github.com/livekit/egress/tree/main/template-default)).
While it's a great starting point, you can easily [create your own layout]/transport/media/ingress-egress/egress/custom-template/) using standard web technologies that you are already familiar with.
| Layout | Preview |
| **grid** | ![undefined]() |
| **speaker** | ![undefined]() |
| **single-speaker** | ![undefined]() |
Additionally, you can use a `-light` suffix to change background color to white. i.e. `grid-light`.
### Output options
Composite recordings can output to a wide variety of formats and destinations. The options are described in detail in [Output options](https://docs.livekit.io/transport/media/ingress-egress/egress/outputs.md).
### Audio-only composite
If your application is audio-only, you can export a mixed audio file containing audio from all participants in the room. To start an audio-only composite, pass `audio_only=true` when starting an Egress.
When `audio_only` is true, you can also specify the [audio mixing mode](https://docs.livekit.io/reference/other/egress/api.md#audiomixing) to use. Use dual channel recording to separate the agent's audio from the other participants' audio, or alternate dual channel recording to alternate between left and right channels for each new audio track. By default, all users are mixed together.
## Web egress
Web egress allows you to record or stream any website. Similar to RoomComposite egress, it uses headless Chromium to render output. Unlike RoomComposite egress, you can supply any URL, and the lifecycle of web egress is not attached to a LiveKit room.
## Examples
For examples on using composite recordings, see [Egress examples](https://docs.livekit.io/reference/other/egress/examples.md).
---
This document was rendered at 2026-02-03T03:25:17.170Z.
For the latest version of this document, see [https://docs.livekit.io/transport/media/ingress-egress/egress/composite-recording.md](https://docs.livekit.io/transport/media/ingress-egress/egress/composite-recording.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/home/ingress/configure-streaming-software.md
LiveKit docs › Stream import › Encoder configuration
---
# Encoder configuration
> How to configure streaming software to work with LiveKit Ingress.
The `IngressInfo` object returned by most Ingress APIs contains a full list of the ingress parameters. In particular, the `url` and `stream_key` fields provide the settings required to configure encoders to send media to the Ingress service. Refer to the documentation of any RTMP or WHIP-capable streaming software for more information about how to provide these parameters. Two common examples are OBS and FFmpeg:
## OBS
The [OBS Project](https://obsproject.com/) releases OBS Studio, a powerful cross platform broadcasting software that can be fully configured through a graphical user interface, and capable of sending complex video compositions to LiveKit WebRTC via Ingress. In order to configure OBS for LiveKit, in the main window, select the `Settings` option, and then the `Stream` tab. In the window, select the `Custom...` Service and enter the URL from the `StreamInfo` in the `Server` field, and the stream key in the `Stream Key` field.

## FFmpeg
[FFmpeg](https://ffmpeg.org/) is a powerful media processing command-line tool that can be used to stream media to LiveKit Ingress. The following command can be used for that purpose:
```shell
% ffmpeg -re -i -c:v libx254 -b:v 3M -preset veryfast -profile high -c:a libfdk_aac -b:a 128k -f flv "/"
```
For instance:
```shell
% ffmpeg -re -i my_file.mp4 -c:v libx264 -b:v 3M -preset veryfast -profile:v high -c:a libfdk_aac -b:a 128k -f flv rtmps://my-project.livekit.cloud/x/1234567890ab
```
Refer to the [FFmpeg documentation](https://ffmpeg.org/ffmpeg.html) for a list of the supported inputs, and how to use them.
## GStreamer
[GStreamer](https://gstreamer.freedesktop.org/) is multi platform multimedia framework that can be used either directly using command line tools provided as part of the distribution, or integrated in other applications using their API. GStreamer supports streaming media to LiveKit Ingress both over RTMP and WHIP.
For RTMP, the following sample command and pipeline definition can be used:
```shell
% gst-launch-1.0 flvmux name=mux ! rtmp2sink location="/" audiotestsrc wave=sine-table ! faac ! mux. videotestsrc is-live=true ! video/x-raw,width=1280,height=720 ! x264enc speed-preset=3 tune=zerolatency ! mux.
```
WHIP requires the following GStreamer plugins to be installed:
- nicesink
- webrtcbin
- whipsink
Some these plugins are distributed as part of [libnice](https://libnice.freedesktop.org) or the [Rust GStreamer plugins package](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs) and may not always be present. This can be verified using the `gst-inspect-1.0` command. LiveKit provides a Docker image based on Ubuntu that includes all the required GStreamer plugins at [livekit/gstreamer:1.22.8-prod-rs](https://hub.docker.com/layers/livekit/gstreamer/1.22.8-prod-rs/images/sha256-1a4d7ef428875550400430a57acf0759f1cb02771dbac2501b2d3fbe2f1ce74e?context=explore).
```shell
gst-launch-1.0 audiotestsrc wave=sine-table ! opusenc ! rtpopuspay ! 'application/x-rtp,media=audio,encoding-name=OPUS,payload=96,clock-rate=48000,encoding-params=(string)2' ! whip.sink_0 videotestsrc is-live=true ! video/x-raw,width=1280,height=720 ! x264enc speed-preset=3 tune=zerolatency ! rtph264pay ! 'application/x-rtp,media=video,encoding-name=H264,payload=97,clock-rate=90000' ! whip.sink_1 whipsink name=whip whip-endpoint="/"
```
These 2 sample command lines use the `audiotestsrc` and `videotestsrc` sources to generate test audio and video pattern. These can be replaced with other GStreamer sources to stream any media supported by GStreamer.
---
This document was rendered at 2025-11-18T23:54:56.712Z.
For the latest version of this document, see [https://docs.livekit.io/home/ingress/configure-streaming-software.md](https://docs.livekit.io/home/ingress/configure-streaming-software.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/sip/quickstarts/configuring-plivo-trunk.md
LiveKit docs › Provider-specific guides › Plivo
---
# Create and configure a Plivo SIP trunk
> Step-by-step instructions for creating inbound and outbound SIP trunks using Plivo.
Connect [Plivo's](https://plivo.com) SIP trunking with LiveKit for inbound and outbound calls.
## Prerequisites
The following are required to complete the steps in this guide:
- [Plivo account](https://console.plivo.com/)
- [LiveKit Cloud project](https://cloud.livekit.io/projects/p_/settings/project)
## Inbound calling
To accept inbound calls with Plivo and LiveKit, complete the steps in the following sections.
### Create a SIP trunk
Create an inbound trunk in Plivo, setting your LiveKit SIP endpoint as the primary URI.
1. Sign in to the [Plivo Console](https://console.plivo.com/).
2. Navigate to **Zentrunk** → [**Inbound Trunks**](https://console.plivo.com/zentrunk/inbound-trunks/).
3. Select **Create New Inbound Trunk** and provide a descriptive name for your trunk.
4. For **Primary URI**, select **Add New URI** and enter your LiveKit [SIP endpoint](https://docs.livekit.io/sip/quickstarts/configuring-sip-trunk.md#sip-endpoint). Include `;transport=tcp` in the URI. For example, `vjnxecm0tjk.sip.livekit.cloud;transport=tcp`.
If you're signed in to LiveKit Cloud, your SIP endpoint is automatically included in the following example:
```shell
%{regionalEndpointSubdomain}%.sip.livekit.cloud;transport=tcp
```
> ℹ️ **Secure trunking**
>
> If you're setting up [secure trunking](https://docs.livekit.io/sip/secure-trunking.md), use `;transport=tls` instead of `;transport=tcp`.
5. Select **Create Trunk**.
### Connect your phone number
Connect your Plivo phone number to the inbound trunk.
1. Navigate to **Phone Numbers** → [**Your Numbers**](https://console.plivo.com/active-phone-numbers/).
2. Select the phone number to connect to the trunk.
3. In the **Number Configuration** section → **Application Type**, select **Zentrunk**.
4. For **Trunk**, select the trunk you created in the previous step.
5. Select **Update**.
### Configure LiveKit to accept calls
Set up an [inbound trunk](https://docs.livekit.io/sip/trunk-inbound.md) and [dispatch rule](https://docs.livekit.io/sip/dispatch-rule.md) in LiveKit to accepts calls to your Plivo phone number.
### Test incoming calls
Start your LiveKit agent and call your Plivo phone number. Your agent should answer the call. If you don't have an agent, see the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md) to create one.
### Troubleshooting
For help troubleshooting inbound calls, check the following logs:
- First check the [Plivo logs](https://console.plivo.com/zentrunk/logs/calls/).
- Then check the [call logs](https://cloud.livekit.io/projects/p_/telephony) in your LiveKit Cloud dashboard.
## Outbound calling
To make outbound calls with LiveKit and Plivo and LiveKit, complete the steps in the following sections.
### Create an outbound trunk in Plivo
Set up an outbound trunk with username and password authentication in Plivo.
1. Sign in to the [Plivo Console](https://console.plivo.com/).
2. Navigate to **Zentrunk** → [**Outbound Trunks**](https://console.plivo.com/zentrunk/outbound-trunks/).
3. Select **Create New Outbound Trunk** and provide a descriptive name for your trunk.
4. In the **Trunk Authentication** section → **Credentials List**, select **Add New Credentials List**.
5. Add a username and strong password for outbound call authentication. Make sure these values match the username and password you use for your LiveKit outbound trunk.
6. For **Secure Trunking**, select **Enabled** (recommended).
> 💡 **Secure trunking**
>
> If you enable secure trunking in Plivo, you must also enable secure trunking in LiveKit. To learn more, see [Secure trunking](https://docs.livekit.io/sip/secure-trunking.md).
7. Select **Create Trunk** to complete your outbound trunk configuration.
Copy the **Termination SIP Domain** for the next step.
### Configure LiveKit to make outbound calls
Create an [outbound trunk](https://docs.livekit.io/sip/trunk-outbound.md) in LiveKit using the **Termination SIP Domain**, and username and password from the previous section.
### Place an outbound call
Test your configuration by placing an outbound call with LiveKit using the `CreateSIPParticipant` API. To learn more, see [Creating a SIP participant](https://docs.livekit.io/sip/outbound-calls.md#creating-a-sip-participant).
### Troubleshooting
If the call fails to connect, check the following common issues:
- Verify your SIP URI. It must include `;transport=tcp`.
- Verify your Plivo phone number is associated with the correct trunk.
For outbound calls, check the following logs:
- First check the [call logs](https://cloud.livekit.io/projects/p_/telephony) in your LiveKit Cloud dashboard.
- Then check the [Plivo logs](https://console.plivo.com/zentrunk/logs/calls/).
For error codes, see the [Plivo hangup codes](https://www.plivo.com/docs/voice/troubleshooting/hangup-causes) reference.
## Regional restrictions
If your calls are made from a Plivo India phone number, or you're dialing numbers in India, you must enable [region pinning](https://docs.livekit.io/sip/cloud.md#region-pinning) for your LiveKit project. This restricts calls to India to comply with local telephony regulations. Your calls will fail to connect if region pinning is not enabled.
For other countries, select the region closest to the location of your call traffic for optimal performance.
## Next steps
The following guides provide next steps for building your LiveKit telephony app.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: A quickstart guide to build a voice AI agent to answer incoming calls.
- **[Agents telephony integration](https://docs.livekit.io/agents/start/telephony.md)**: Learn how to receive and make calls with a voice AI agent
- **[Call forwarding using SIP REFER](https://docs.livekit.io/sip/transfer-cold.md)**: How to forward calls to another number or SIP endpoint with SIP REFER.
- **[Agent-assisted warm transfer](https://docs.livekit.io/sip/transfer-warm.md)**: A comprehensive guide to transferring calls using an AI agent to provide context.
- **[Secure trunking for SIP calls](https://docs.livekit.io/sip/secure-trunking.md)**: How to enable secure trunking for LiveKit SIP.
- **[Region pinning for SIP](https://docs.livekit.io/sip/cloud.md)**: Use region pinning to restrict calls to a specific region.
---
This document was rendered at 2025-11-18T23:55:19.712Z.
For the latest version of this document, see [https://docs.livekit.io/sip/quickstarts/configuring-plivo-trunk.md](https://docs.livekit.io/sip/quickstarts/configuring-plivo-trunk.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/sip/quickstarts/configuring-sip-trunk.md
LiveKit docs › Getting started › SIP trunk setup
---
# SIP trunk setup
> Guide to setting up SIP trunks for inbound and outbound calls with LiveKit.
## Overview
LiveKit is compatible with any SIP trunking provider. This guide provides general instructions for setting up a SIP trunk with an external provider and then associating it with your LiveKit Cloud project.
## External provider setup
The usual steps to create a SIP trunk are as follows:
1. Create a SIP trunk with your provider.
2. Add authentication or limit trunk usage by phone numbers or IP addresses.
3. Purchase a phone number and associate it with your SIP trunk.
4. Add your [LiveKit SIP endpoint](#sip-endpoint) to the SIP trunk.
### SIP endpoint
Depending on your SIP trunking provider, you might need to use a _SIP endpoint_ to configure inbound calls instead of your SIP URI. The SIP endpoint is your LiveKit SIP URI without the `sip:` prefix. You can find your SIP URI on the [**Project settings**](https://cloud.livekit.io/projects/p_/settings/project) page.
For example, if your SIP URI is `sip:vjnxecm0tjk.sip.livekit.cloud`, your SIP endpoint is `vjnxecm0tjk.sip.livekit.cloud`.
> ℹ️ **Region-based endpoints**
>
> To restrict calls to a specific region, replace your global LiveKit SIP endpoint with a [region-based endpoint](https://docs.livekit.io/sip/cloud.md#region-pinning).
## Provider-specific instructions
For step-by-step instructions for Telnyx, Twilio, or Plivo, Wavix, see the following quickstarts:
- **[Twilio Setup](https://docs.livekit.io/sip/quickstarts/configuring-twilio-trunk.md)**: Step-by-step instructions for setting up a SIP trunk with Twilio.
- **[Telnyx Setup](https://docs.livekit.io/sip/quickstarts/configuring-telnyx-trunk.md)**: Step-by-step instructions for setting up a SIP trunk with Telnyx.
- **[Plivo Setup](https://docs.livekit.io/sip/quickstarts/configuring-plivo-trunk.md)**: Step-by-step instructions for setting up a SIP trunk with Plivo.
- **[Wavix Setup](https://docs.livekit.io/sip/quickstarts/configuring-wavix-trunk.md)**: Step-by-step instructions for setting up a SIP trunk with Wavix.
## LiveKit setup
Now you are ready to configure your LiveKit Cloud project to use the SIP trunk.
The following steps are common to all SIP trunking providers.
> ℹ️ **LiveKit CLI**
>
> These examples use the [LiveKit Cloud](https://cloud.livekit.io/). For additional examples and full documentation, see the linked documentation for each component.
### Inbound trunk setup
An [inbound trunk](https://docs.livekit.io/sip/trunk-inbound.md) allows you to accept incoming phone calls.
Create an inbound trunk using the LiveKit Cloud dashboard.
1. Sign in to the **Telephony** → [**Configuration**](https://cloud.livekit.io/projects/p_/telephony/config) page.
2. Select **Create new** → **Trunk**.
3. Select the **JSON editor** tab.
4. Select **Inbound** for **Trunk direction**.
5. Copy and paste the following text into the editor, replacing the phone number with the number you purchased from your SIP trunk provider:
```json
{
"name": "My inbound trunk",
"numbers": ["+15105550123"]
}
```
6. Select **Create**.
### Create a dispatch rule
You must set up at least one [dispatch rule](https://docs.livekit.io/sip/dispatch-rule.md) to accept incoming calls into a LiveKit room.
This example creates a dispatch rule that puts each caller into a randomly generated unique room using the name prefix `call-`. For many applications, this is the only configuration you need.
1. Sign to the **Telephony** → [**Configuration**](https://cloud.livekit.io/projects/p_/telephony/config) page.
2. Select **Create new** → **Dispatch rule**.
3. Select the **JSON editor** tab.
4. Copy and paste the following text into the editor:
```json
{
"name": "My dispatch rule",
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
}
}
```
5. Select **Create**.
After you create an inbound trunk and dispatch rule, you can create an agent to answer incoming calls. To learn more, see the resources in the [Next steps](#next-steps) section.
### Create an outbound trunk
Create an [outbound trunk](https://docs.livekit.io/sip/trunk-outbound.md) to make outgoing phone calls with LiveKit.
This example creates an username and password authenticated outbound trunk with the phone number `+15105550123` and the trunk domain name `my-trunk-domain-name`.
1. Sign in to the **Telephony** → [**Configuration**](https://cloud.livekit.io/projects/p_/telephony/config) page.
2. Select **Create new** → **Trunk**.
3. Select the **JSON editor** tab.
4. Select **Outbound** for **Trunk direction**.
5. Copy and paste the following text into the editor:
```json
{
"name": "My outbound trunk",
"address": "",
"numbers": [
"+15105550123"
],
"authUsername": "",
"authPassword": ""
}
```
6. Select **Create**.
Now you are ready to [place outgoing calls](https://docs.livekit.io/sip/outbound-calls.md).
## Next steps
See the following guides to continue building your telephony app.
- **[Telephony agents](https://docs.livekit.io/agents/start/telephony.md)**: Building telephony-based voice AI apps with LiveKit Agents.
- **[Make outbound calls](https://docs.livekit.io/sip/outbound-calls.md)**: Detailed instructions for making outbound calls.
## Additional documentation
See the following documentation for more details on the topics covered in this guide.
- **[Inbound trunk](https://docs.livekit.io/sip/trunk-inbound.md)**: Detailed instructions for setting up inbound trunks.
- **[Dispatch rule](https://docs.livekit.io/sip/dispatch-rule.md)**: Detailed instructions for setting up dispatch rules.
- **[Outbound trunk](https://docs.livekit.io/sip/trunk-outbound.md)**: Detailed instructions for setting up outbound trunks.
---
This document was rendered at 2025-11-18T23:55:19.187Z.
For the latest version of this document, see [https://docs.livekit.io/sip/quickstarts/configuring-sip-trunk.md](https://docs.livekit.io/sip/quickstarts/configuring-sip-trunk.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/sip/quickstarts/configuring-telnyx-trunk.md
LiveKit docs › Provider-specific guides › Telnyx
---
# Create and configure Telnyx SIP trunk
> Step-by-step instructions for creating inbound and outbound SIP trunks using Telnyx.
> ℹ️ **Note**
>
> If you're using LiveKit Cloud as your SIP server and you're signed in, your SIP endpoint is automatically included in the code blocks where appropriate.
## Creating a Telnyx SIP trunk using the API
You can use `curl` command to make calls to the Telnyx API V2. The commands in the steps below use the example phone number, `+15105550100`. To use the Telnyx console, see [Creating a SIP trunk using the Telnyx UI](#creating-a-sip-trunk-using-the-telnyx-ui).
### Prerequisite
Purchase a [Telnyx phone number](https://telnyx.com/products/phone-numbers).
### Step 1: Create an environment variable for API key
If you don't have a key a Telnyx API V2 key, see the [Telnyx guide to create one](https://support.telnyx.com/en/articles/4305158-api-keys-and-how-to-use-them).
```shell
export TELNYX_API_KEY=""
```
### Step 2: Create an FQDN connection
The following inbound and outbound commands include the required configuration settings if you plan on using only an inbound or outbound trunk for your LiveKit telephony app. However, by default, an [FQDN connection](https://developers.telnyx.com/api/connections/create-fqdn-connection) creates both an inbound and outbound trunk.
1. Creating an FQDN connection. Depending on your use case, select **Inbound**, **Outbound**, or **Inbound and outbound** to accept calls, make calls, or both:
**Inbound**:
Set the caller's number format to `+E.164` for inbound calls (this identifies the caller's number with a leading `+`):
```shell
curl -L 'https://api.telnyx.com/v2/fqdn_connections' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-d '{
"active": true,
"anchorsite_override": "Latency",
"connection_name": "My LiveKit trunk",
"inbound": {
"ani_number_format": "+E.164",
"dnis_number_format": "+e164"
}
}'
```
---
**Outbound**:
For outbound trunks, complete the following items:
- Create a voice profile for outbound calls.
- Configure credential authentication with a username and password.
1. Creating a [voice profile](https://developers.telnyx.com/api/outbound-voice-profiles/create-voice-profile):
```shell
curl -L 'https://api.telnyx.com/v2/outbound_voice_profiles' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-d '{
"name": "My LiveKit outbound voice profile",
"traffic_type": "conversational",
"service_plan": "global"
}'
```
2. Creating an outbound FQDN connection:
```shell
curl -L 'https://api.telnyx.com/v2/fqdn_connections' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-d '{
"active": true,
"anchorsite_override": "Latency",
"connection_name": "My LiveKit trunk",
"user_name": "",
"password": "",
"outbound": {
"outbound_voice_profile_id": ""
}
}'
```
---
**Inbound and Outbound**:
To configure an FQDN trunk for both inbound and outbound calls:
- Create a voice profile for outbound calls.
- Set the caller's number format to `+E.164`.
- Configure credential authentication with a username and password.
1. Create a [voice profile](https://developers.telnyx.com/api/outbound-voice-profiles/create-voice-profile)
```shell
curl -L 'https://api.telnyx.com/v2/outbound_voice_profiles' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-d '{
"name": "My LiveKit outbound voice profile",
"traffic_type": "conversational",
"service_plan": "global"
}'
```
2. Create an inbound and outbound FQDN connection
```shell
curl -L 'https://api.telnyx.com/v2/fqdn_connections' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-d '{
"active": true,
"anchorsite_override": "Latency",
"connection_name": "My LiveKit trunk",
"user_name": "",
"password": "",
"inbound": {
"ani_number_format": "+E.164",
"dnis_number_format": "+e164"
},
"outbound": {
"outbound_voice_profile_id": ""
}
}'
```
2. Copy the FQDN connection ID from the output:
```json
{
"data": {
"id":"",
...
}
}
```
3. Create an FQDN with your [LiveKit SIP endpoint](https://docs.livekit.io/sip/quickstarts/configuring-sip-trunk.md#sip-endpoint) and your FQDN connection ID:
```shell
curl -L 'https://api.telnyx.com/v2/fqdns' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-d '{
"connection_id": "",
"fqdn": "%{sipHost}%",
"port": 5060,
"dns_record_type": "a"
}'
```
> ℹ️ **Region-based endpoints**
>
> To restrict calls to a specific region, replace your global LiveKit SIP endpoint with a [region-based endpoint](https://docs.livekit.io/sip/cloud.md#region-pinning).
### Step 3: Associate phone number and trunk
1. Get the phone number ID for phone number `5105550100`:
```shell
curl -L -g 'https://api.telnyx.com/v2/phone_numbers?filter[phone_number]=5105550100' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $TELNYX_API_KEY"
```
Copy the phone number ID from the output:
```json
{
"meta": {
"total_pages": 1,
"total_results": 1,
"page_number": 1,
"page_size": 100
},
"data": [
{
"id": "",
...
}
]
}
```
2. Add the FQDN connection to the phone number:
```shell
curl -L -X PATCH 'https://api.telnyx.com/v2/phone_numbers/' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
-H "Authorization: Bearer $TELNYX_API_KEY" \
-d '{
"id": "",
"connection_id": ""
}'
```
## Creating a SIP trunk using the Telnyx UI
1. Sign in to the [Telnyx portal](https://portal.telnyx.com/).
2. [Purchase a phone number](https://portal.telnyx.com/#/numbers/buy-numbers).
3. Navigate to **Voice** » [**SIP Trunking**](https://portal.telnyx.com/#/voice/connections).
4. Create a SIP connection:- For inbound calls:
- Select [FQDN](https://developers.telnyx.com/docs/voice/sip-trunking/quickstart#option-3-fqdn-authentication) and save.
- Select **Add FQDN** and enter your [LiveKit SIP endpoint](https://docs.livekit.io/sip/quickstarts/configuring-sip-trunk.md#sip-endpoint) into the **FQDN** field.
For example, `vjnxecm0tjk.sip.livekit.cloud`.
> ℹ️ **Region-based endpoints**
>
> To restrict calls to a specific region, replace your global LiveKit SIP endpoint with a [region-based endpoint](https://docs.livekit.io/sip/cloud.md#region-pinning).
- Select the **Inbound** tab. In the **Destination Number Format** field, select `+E.164`.
- In the **SIP Transport Protocol** field, select either **TCP** or **UDP**.
- In the **SIP Region** field, select your region.
- For outbound calls:
- Select the **Outbound** tab.
- In the **Outbound Voice Profile** field, select or create an outbound voice profile.
- Select the **Settings** tab
- Configure [FQDN Authentication](https://developers.telnyx.com/docs/voice/sip-trunking/quickstart#option-3-fqdn-authentication):- Select the **Settings** tab.
- In the **Authentication & Routing Configuration** section, select **Outbound Calls Authentication**.
- In the **Authentication Method** field, select **Credentials** and enter a username and password.
- Select the **Numbers** tab and assign the purchased number to the SIP trunk.
## Next steps
Head back to the main setup documentation to finish connecting your SIP trunk to LiveKit.
- **[SIP trunk setup](https://docs.livekit.io/sip/quickstarts/configuring-sip-trunk.md#livekit-setup)**: Configure your Telnyx trunk in LiveKit.
---
This document was rendered at 2025-11-18T23:55:19.549Z.
For the latest version of this document, see [https://docs.livekit.io/sip/quickstarts/configuring-telnyx-trunk.md](https://docs.livekit.io/sip/quickstarts/configuring-telnyx-trunk.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/sip/quickstarts/configuring-twilio-trunk.md
LiveKit docs › Provider-specific guides › Twilio
---
# Create and configure a Twilio SIP trunk
> Step-by-step instructions for creating inbound and outbound SIP trunks using Twilio.
> ℹ️ **Note**
>
> If you're using LiveKit Cloud as your SIP server and you're signed in, your SIP URI is automatically included in the code blocks where appropriate.
Use the following steps to configure inbound and outbound SIP trunks using [Twilio](https://twilio.com).
## Creating a SIP trunk for inbound and outbound calls
Create a Twilio SIP trunk for incoming or outgoing calls, or both, using the following steps. To use the Twilio console, see [Configure a SIP trunk using the Twilio UI](#configure-a-sip-trunk-using-the-twilio-ui).
> ℹ️ **Note**
>
> For inbound calls, you can use TwiML for Programmable Voice instead of setting up Elastic SIP Trunking. To learn more, see [Inbound calls with Twilio Voice](https://docs.livekit.io/sip/accepting-calls-twilio-voice.md).
### Prerequisites
- [Purchase phone number](https://help.twilio.com/articles/223135247-How-to-Search-for-and-Buy-a-Twilio-Phone-Number-from-Console).
- [Install the Twilio CLI](https://www.twilio.com/docs/twilio-cli/getting-started/install).
- Create a [Twilio profile](https://www.twilio.com/docs/twilio-cli/general-usage/profiles) to use the CLI.
### Step 1. Create a SIP trunk
The domain name for your SIP trunk must end in `pstn.twilio.com`. For example to create a trunk named `My test trunk` with the domain name `my-test-trunk.pstn.twilio.com`, run the following command:
```shell
twilio api trunking v1 trunks create \
--friendly-name "My test trunk" \
--domain-name "my-test-trunk.pstn.twilio.com"
```
The output includes the trunk SID. Copy it for use in the following steps.
### Step 2: Configure your trunk
Configure the trunk for inbound calls or outbound calls or both. To create a SIP trunk for both inbound and outbound calls, follow the steps in both tabs:
**Inbound**:
For inbound trunks, configure an [origination URI](https://www.twilio.com/docs/sip-trunking#origination). If you're using LiveKit Cloud and are signed in, your SIP URI is automatically included in the following command:
```shell
twilio api trunking v1 trunks origination-urls create \
--trunk-sid \
--friendly-name "LiveKit SIP URI" \
--sip-url "sip:%{sipHost}%" \
--weight 1 --priority 1 --enabled
```
> ℹ️ **Region-based endpoints**
>
> To restrict calls to a specific region, replace your global LiveKit SIP endpoint with a [region-based endpoint](https://docs.livekit.io/sip/cloud.md#region-pinning).
---
**Outbound**:
For outbound trunks, configure username and password authentication using a credentials list. Complete the following steps using the Twilio console.
**Step 1: Create a credential list**
1. Sign in to the [Twilio console](https://console.twilio.com).
2. Select **Voice** » **Credential lists**.
3. Create a new credential list with the username and password of your choice.
**Step 2: Associate the credential list with your SIP trunk**
1. Select **Elastic SIP Trunking** » **Manage** » **Trunks** and select the outbound trunk created in the previous steps.
2. Select **Termination** » **Authentication** » **Credential Lists** and select the credential list you just created.
3. Select **Save**.
### Step 3: Associate phone number and trunk
The Twilio trunk SID and phone number SID are included in the output of previous steps. If you didn't copy the SIDs, you can list them using the following commands:
- To list phone numbers: `twilio phone-numbers list`
- To list trunks: `twilio api trunking v1 trunks list`
```shell
twilio api trunking v1 trunks phone-numbers create \
--trunk-sid \
--phone-number-sid
```
## Configure a SIP trunk using the Twilio UI
1. Sign in to the [Twilio console](https://console.twilio.com/).
2. [Purchase a phone number](https://help.twilio.com/articles/223135247-How-to-Search-for-and-Buy-a-Twilio-Phone-Number-from-Console).
3. [Create SIP Trunk](https://www.twilio.com/docs/sip-trunking#create-a-trunk) on Twilio:
- Select **Elastic SIP Trunking** » **Manage** » **Trunks**.
- Create a SIP trunk.
> 💡 **Tip**
>
> Using your Twilio API key, you can skip the next two steps by using [this snippet](https://gist.github.com/ShayneP/51eabe243f9e7126929ea7e9db1dc683) to set your origination and termination URLs automatically.
4. For inbound calls:
- Navigate to **Voice** » **Manage** » **Origination connection policy**, and create an **Origination Connection Policy**
- Select the policy you just created and set the [Origination SIP URI](https://www.twilio.com/docs/sip-trunking#origination) to your LiveKit SIP URI (available on your [**Project settings**](https://cloud.livekit.io/projects/p_/settings/project) page). For example, `sip:vjnxecm0tjk.sip.livekit.cloud`.
> ℹ️ **Region-based endpoints**
>
> To restrict calls to a specific region, replace your global LiveKit SIP endpoint with a [region-based endpoint](https://docs.livekit.io/sip/cloud.md#region-pinning).
5. For outbound calls, configure termination and authentication:
- Navigate to **Elastic SIP Trunking** » **Manage** » **Trunks**.
- Copy the [Termination SIP URI](https://www.twilio.com/docs/sip-trunking#termination-uri) to use when you create an [outbound trunk](https://docs.livekit.io/sip/trunk-outbound.md) for LiveKit.
- Configure [Authentication](https://www.twilio.com/docs/sip-trunking#authentication):
1. Select **Elastic SIP Trunking** » **Manage** » **Credential lists** and create a new credential list with a username and password of your choice.
2. Associate your trunk with the credential list:
- Select **Elastic SIP Trunking** » **Manage** » **Trunks** and select the outbound trunk created in the previous steps.
- Select **Termination** » *_Authentication_ » **Credential Lists** and select the credential list you just created.
## Next steps
Head back to the main setup documentation to finish connecting your SIP trunk to LiveKit.
- **[SIP trunk setup](https://docs.livekit.io/sip/quickstarts/configuring-sip-trunk.md#livekit-setup)**: Configure your Twilio trunk in LiveKit.
---
This document was rendered at 2025-11-18T23:55:19.387Z.
For the latest version of this document, see [https://docs.livekit.io/sip/quickstarts/configuring-twilio-trunk.md](https://docs.livekit.io/sip/quickstarts/configuring-twilio-trunk.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/sip/quickstarts/configuring-wavix-trunk.md
LiveKit docs › Provider-specific guides › Wavix
---
# Using Wavix to accept and make calls
> Step-by-step instructions for configuring inbound and outbound calls using Wavix and LiveKit.
## Prerequisites
The following are required to complete the steps in this guide:
- A [Wavix account](https://app.wavix.com) account.
- A [purchased phone number](https://wavix.com) from Wavix.
- A project on [LiveKit Cloud](https://cloud.livekit.io/).
## Accepting inbound calls
Complete the following steps to accept inbound calls with Wavix and LiveKit.
### Step 1: Configure inbound call routing in Wavix
To receive calls with Wavix and LiveKit, you need to set up inbound call routing.
For this step, you need your LiveKit [SIP endpoint](https://docs.livekit.io/sip/quickstarts/configuring-sip-trunk.md#sip-endpoint). This is your LiveKit SIP URI without the `sip:` prefix. You can find your SIP URI on your [Project settings](https://cloud.livekit.io/projects/p_/settings/project) page.
1. Sign in to your [Wavix account](https://app.wavix.com).
2. Select **Numbers & trunks** → **My numbers**.
3. Select the more (**⋮**) menu and choose **Edit number**.
4. For **Destination** → **Configure inbound call routing**, select **SIP URI**.
Enter the destination in the format: `[did]@[LiveKit SIP endpoint]`, for example: `[did]@vjnxecm0tjk.sip.livekit.cloud`.
> ℹ️ **Note**
>
> The `[did]` placeholder in the destination string is automatically replaced with your Wavix phone number.
5. Select **Save**.
### Step 2: Create an inbound trunk in LiveKit
An [inbound trunk](https://docs.livekit.io/sip/trunk-inbound.md) allows you to accept incoming phone calls to your Wavix phone number. To create an inbound trunk in LiveKit, use the following steps:
1. Sign in to [LiveKit Cloud](https://cloud.livekit.io/).
2. Select **Telephony** → [**Configuration**](https://cloud.livekit.io/projects/p_/telephony/config).
3. Select the **+Create new** button → **Trunk**.
4. For **Trunk direction**, select **Inbound**.
5. Enter a comma-separated list of Wavix numbers to associate with the trunk.
6. Select **Create**.
### Step 3: Create a dispatch rule in LiveKit
In addition to an inbound trunk, you need a [dispatch rule](https://docs.livekit.io/sip/dispatch-rule.md) to determine how callers are dispatched to LiveKit rooms.
Create a dispatch rule using the following steps:
1. Navigate to the **Telephony** → **Configuration** page.
2. Select the **+Create new** button → **Dispatch rule**.
3. Complete the **Rule name** and **Room name** fields.
4. Select **Match trunks** and select the inbound trunk you created in the previous step.
> ℹ️ **Additional options**
>
> - Selecting trunks to match a dispatch rule is optional. By default, a dispatch rule applies to all inbound calls for your LiveKit project.
> - The default **Rule type** is **Direct**. This means all callers are placed in the same room. For alternative rule types, see [SIP dispatch rule](https://docs.livekit.io/sip/dispatch-rule.md).
### Test inbound calls
After you complete the setup steps, start a voice AI agent and call your Wavix phone number. Your agent should answer the call. If you don't have an agent, see the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md) to create one.
## Making outbound calls
Complete the following steps to make outbound calls using LiveKit and Wavix.
### Step 1: Create a SIP trunk in Wavix
Create a Wavix SIP trunk for outgoing calls, using the following steps.
1. Sign in to your [Wavix account](https://app.wavix.com).
2. Navigate to **Numbers & Trunks** → **Trunks**.
3. Select the **Create new** button.
4. Enter a **SIP trunk name**.
5. In the **Caller ID** section, select one of the phone numbers you purchased.
6. Under **Authentication Method**, select **Digest** and complete the **Password** fields.
7. Select **Next**.
8. Optionally, configure additional limits:- **Max outbound call duration**
- **Max number of simultaneous calls via the SIP trunk**
- **Max call cost**
9. Select **Save**.
After the SIP trunk is successfully created, it appears in your account's SIP trunks list. Note the 5-digit SIP trunk ID that is generated automatically. Your SIP trunk ID is needed for the next step when you create an outbound trunk in LiveKit.
### Step 2: Configure outbound calls
For outbound calls, you need to create an outbound trunk in LiveKit using the Wavix SIP trunk credentials:
1. Sign in to [LiveKit Cloud](https://cloud.livekit.io/).
2. Select **Telephony** → [**Configuration**](https://cloud.livekit.io/projects/p_/telephony/config).
3. Select the **+ Create new** button → **Trunk**.
4. For **Trunk direction**, select **Outbound**.
5. Configure the outbound trunk with the following settings:- **Address**: Use the Wavix SIP gateway (e.g., `.wavix.net`)
- **Numbers**: Enter your Wavix phone number.
- Select **Optional settings** and complete the following fields:- **Username**: Your 5-digit SIP trunk ID from Wavix.
- **Password**: The SIP trunk password you set in Wavix.
- Select **Create**.
> 💡 **Tip**
>
> Choose the primary gateway closest to your location. A full list of Wavix regional gateways is available at the bottom of your [Wavix trunks page](https://app.wavix.com/trunks).
## Transfer calls
Wavix supports cold call transfers using the SIP REFER command. To transfer a call, you need two Wavix numbers—one for the incoming call and one to transfer calls to.
To transfer an active LiveKit call, use the `TransferSIPParticipant` server API. The following is a Node.js example. To learn more and for additional examples, see [Call forwarding](https://docs.livekit.io/sip/transfer-cold.md).
```typescript
import { SipClient } from 'livekit-server-sdk';
async function transferParticipant(participant) {
console.log("transfer participant initiated");
const sipTransferOptions = {
playDialtone: false
};
const sipClient = new SipClient(process.env.LIVEKIT_URL,
process.env.LIVEKIT_API_KEY,
process.env.LIVEKIT_API_SECRET);
const transferTo = "sip:+19495550100@us.wavix.net";
await sipClient.transferSipParticipant('open-room', participant.identity,
transferTo, sipTransferOptions);
console.log('transfer participant');
}
```
Replace the `transferTo` value with your Wavix number using the format: `sip:+[YOUR_WAVIX_NUMBER]@[WAVIX_SIP_GATEWAY]`.
## Enable call encryption
You can choose to encrypt call media for enhanced security. Contact Wavix support to enable encryption for your Wavix numbers or trunks. After enabling encryption, see [Secure trunking](https://docs.livekit.io/sip/secure-trunking.md) to configure encryption for LiveKit trunks.
## Troubleshooting outbound calls
The following tables lists common issues with outbound calls.
| Issue | Cause |
| 603 Declined response | This might occur when calling a destination with a per-minute rate higher than the Max call rate set for your account. Contact Wavix support to request a change to your max call rate. |
| Registration issues | Check the registration status of your SIP trunk. |
| Wrong number format | Make sure you dial the full international number ([E.164](https://www.itu.int/rec/t-rec-e.164) format): For example, `+19085550100` (US), `+44946001218` (UK). Strip prefixes like `0`, `00`, or `011` before the dialed number. |
For additional troubleshooting help, see the [SIP troubleshooting guide](https://docs.livekit.io/sip/troubleshooting.md).
## Next steps
The following guides provide next steps for building your telephony app.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: A quickstart guide to build a voice AI agent to answer incoming calls.
- **[Agents telephony integration](https://docs.livekit.io/agents/start/telephony.md)**: Learn how to receive and make calls with a voice AI agent
- **[Call forwarding using SIP REFER](https://docs.livekit.io/sip/transfer-cold.md)**: How to forward calls to another number or SIP endpoint with SIP REFER.
- **[Agent-assisted warm transfer](https://docs.livekit.io/sip/transfer-warm.md)**: A comprehensive guide to transferring calls using an AI agent to provide context.
- **[Secure trunking for SIP calls](https://docs.livekit.io/sip/secure-trunking.md)**: How to enable secure trunking for LiveKit SIP.
- **[Region pinning for SIP](https://docs.livekit.io/sip/cloud.md)**: Use region pinning to restrict calls to a specific region.
---
This document was rendered at 2025-11-18T23:55:19.864Z.
For the latest version of this document, see [https://docs.livekit.io/sip/quickstarts/configuring-wavix-trunk.md](https://docs.livekit.io/sip/quickstarts/configuring-wavix-trunk.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/intro/basics/connect.md
LiveKit docs › Understanding LiveKit › Connecting to LiveKit
---
# Connecting to LiveKit
> Learn how to connect to LiveKit using realtime SDKs.
## Overview
You connect to LiveKit through a `Room` object. A [room](https://docs.livekit.io/intro/basics/rooms-participants-tracks/rooms.md) is a core concept that represents an active LiveKit session. Your app joins a room—either one it creates or an existing one—as a participant.
Participants can be users, AI agents, devices, or other programs. There's no fixed limit on how many participants a room can have. Each participant can publish audio, video, and data, and can selectively subscribe to tracks published by others.
LiveKit SDKs provide a unified API for joining rooms, managing participants, and handling media tracks and data channels.
## Install the LiveKit SDK
LiveKit includes open source SDKs for every major platform including JavaScript, Swift, Android, React Native, Flutter, and Unity.
**JavaScript**:
Install the LiveKit SDK and optional React Components library:
```shell
npm install livekit-client @livekit/components-react @livekit/components-styles --save
```
The SDK is also available using `yarn` or `pnpm`.
For more details, see the dedicated quickstart for [React](https://docs.livekit.io/transport/sdk-platforms/react.md).
---
**Swift**:
Add the Swift SDK and the optional Swift Components library to your project using Swift Package Manager. The package URLs are:
- [https://github.com/livekit/client-sdk-swift](https://github.com/livekit/client-sdk-swift)
- [https://github.com/livekit/components-swift](https://github.com/livekit/components-swift)
See [Adding package dependencies to your app](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) for more details.
You must also declare camera and microphone permissions, if needed in your `Info.plist` file:
```xml
...
NSCameraUsageDescription$(PRODUCT_NAME) uses your cameraNSMicrophoneUsageDescription$(PRODUCT_NAME) uses your microphone
...
```
For more details, see the [Swift quickstart](https://docs.livekit.io/transport/sdk-platforms/swift.md).
---
**Android**:
The LiveKit SDK and components library are available as Maven packages.
```groovy
dependencies {
implementation "io.livekit:livekit-android:2.+"
implementation "io.livekit:livekit-android-compose-components:1.+"
}
```
See the [Android SDK releases page](https://github.com/livekit/client-sdk-android/releases) for information on the latest version of the SDK.
You must add JitPack as one of your repositories. In your `settings.gradle` file, add the following:
```groovy
dependencyResolutionManagement {
repositories {
//...
maven { url 'https://jitpack.io' }
}
}
```
---
**React Native**:
Install the React Native SDK with NPM:
```shell
npm install @livekit/react-native @livekit/react-native-webrtc livekit-client
```
Check out the dedicated quickstart for [Expo](https://docs.livekit.io/transport/sdk-platforms/expo.md) or [React Native](https://docs.livekit.io/transport/sdk-platforms/react-native.md) for more details.
---
**Flutter**:
Install the latest version of the Flutter SDK and components library.
```shell
flutter pub add livekit_client livekit_components
```
You must declare camera and microphone permissions in your app. See the [Flutter quickstart](https://docs.livekit.io/transport/sdk-platforms/flutter.md) for more details.
If your SDK isn't listed above, check out the full list of [platform-specific quickstarts](https://docs.livekit.io/transport/sdk-platforms.md) and [SDK reference docs](https://docs.livekit.io/reference.md) for more details.
LiveKit also has SDKs for realtime backend apps in Python, Node.js, Go, Rust, Ruby, and Kotlin. These are designed to be used with the [Agents framework](https://docs.livekit.io/agents.md) for realtime AI applications. For a full list of these SDKs, see [Server APIs](https://docs.livekit.io/reference.md#server-apis).
## Connect to a room
A room is created automatically when the first participant joins, and is automatically closed when the last participant leaves. Rooms are identified by name, which can be any unique string.
You must use a participant identity when you connect to a room. This identity can be any string, but must be unique to each participant.
Connecting to a room requires two parameters:
- `wsUrl`: The WebSocket URL of your LiveKit server.
> ℹ️ **Find your project URL**
>
> LiveKit Cloud users can find their **Project URL** on the [Project Settings page](https://cloud.livekit.io/projects/p_/settings/project).
>
> Self-hosted users who followed [this guide](https://docs.livekit.io/transport/self-hosting/local.md) can use `ws://localhost:7880` during development.
- `token`: A unique [access token](https://docs.livekit.io/frontends/authentication/tokens.md) which each participant must use to connect.
The token encodes the room name, the participant's identity, and their permissions. For help generating tokens, see [these guides](https://docs.livekit.io/frontends/authentication/tokens.md).
**JavaScript**:
```js
const room = new Room();
await room.connect(wsUrl, token);
```
---
**React**:
```js
const tokenSource = TokenSource.literal({ serverUrl: wsUrl, participantToken: token });
const session = useSession(tokenSource);
```
---
**Swift**:
```swift
RoomScope(url: wsURL, token: token, connect: true, enableCamera: true) {
// your components here
}
```
---
**Android**:
```kotlin
RoomScope(
url = wsURL,
token = token,
audio = true,
video = true,
connect = true,
) {
// your components here
}
```
---
**React Native**:
```js
```
---
**Flutter**:
```dart
final room = Room();
await room.connect(wsUrl, token);
```
After successfully connecting, the `Room` object contains two key attributes:
- `localParticipant`: An object that represents the current user.
- `remoteParticipants`: A map containing other participants in the room, keyed by their identity.
After a participant is connected, they can [publish](https://docs.livekit.io/transport/media/publish.md) and [subscribe](https://docs.livekit.io/transport/media/subscribe.md) to realtime media tracks, or [exchange data](https://docs.livekit.io/transport/data.md) with other participants.
LiveKit also emits a number of events on the `Room` object, such as when new participants join or tracks are published. For details, see [Handling Events](https://docs.livekit.io/intro/basics/rooms-participants-tracks/webhooks-events.md).
## Disconnect from a room
Call `Room.disconnect()` to leave the room. If you terminate the application without calling `disconnect()`, your participant disappears after 15 seconds.
> ℹ️ **Note**
>
> On some platforms, including JavaScript and Swift, `Room.disconnect` is called automatically when the application exits.
### Automatic disconnection
Participants might get disconnected from a room due to server-initiated actions. This can happen if the room is closed using the [DeleteRoom](https://docs.livekit.io/intro/basics/rooms-participants-tracks/rooms.md#delete-a-room) API or if a participant is removed with the [RemoveParticipant](https://docs.livekit.io/intro/basics/rooms-participants-tracks/participants.md#removeparticipant) API.
In such cases, a `Disconnected` event is emitted, providing a reason for the disconnection. Common [disconnection reasons](https://github.com/livekit/protocol/blob/main/protobufs/livekit_models.proto#L333) include:
- DUPLICATE_IDENTITY: Disconnected because another participant with the same identity joined the room.
- ROOM_DELETED: The room was closed via the `DeleteRoom` API.
- PARTICIPANT_REMOVED: Removed from the room using the `RemoveParticipant` API.
- JOIN_FAILURE: Failure to connect to the room, possibly due to network issues.
- ROOM_CLOSED: The room was closed because all [participants](https://docs.livekit.io/intro/basics/rooms-participants-tracks/participants.md#types-of-participants) left.
## Connection reliability
LiveKit enables reliable connectivity in a wide variety of network conditions. It tries the following WebRTC connection types in descending order:
1. ICE over UDP: ideal connection type, used in majority of conditions
2. TURN with UDP (3478): used when ICE/UDP is unreachable
3. ICE over TCP: used when network disallows UDP (i.e. over VPN or corporate firewalls)
4. TURN with TLS: used when firewall only allows outbound TLS connections
**Cloud**:
LiveKit Cloud supports all of the above connection types. TURN servers with TLS are provided and maintained by LiveKit Cloud.
---
**Self-hosted**:
ICE over UDP and TCP works out of the box, while TURN requires additional configurations and your own SSL certificate.
### Network changes and reconnection
With WiFi and cellular networks, users might run into network changes that cause the connection to the server to be interrupted. This can include switching from WiFi to cellular or going through areas with poor connection.
When this happens, LiveKit attempts to resume the connection automatically. It reconnects to the signaling WebSocket and initiates an [ICE restart](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Session_lifetime#ice_restart) for the WebRTC connection. This process usually results in minimal or no disruption for the user. However, if media delivery over the previous connection fails, users might notice a temporary pause in video, lasting a few seconds, until the new connection is established.
In scenarios where an ICE restart is not feasible or unsuccessful, LiveKit executes a full reconnection. Because full reconnections take more time and might be more disruptive, a `Reconnecting` event is triggered. This allows your application to respond, possibly by displaying a UI element, during the reconnection process.
This sequence executes as follows:
1. `ParticipantDisconnected` event is emitted for other participants in the room.
2. If there are tracks unpublished, a `LocalTrackUnpublished` event is emitted for them.
3. A `Reconnecting` event is emitted.
4. Performs a full reconnect.
5. A `Reconnected` event is emitted.
6. For everyone currently in the room, you receive a `ParticipantConnected` event.
7. Local tracks are republished, emitting `LocalTrackPublished` events.
A full reconnection sequence is identical to having everyone leave the room, then coming back (that is, rejoining the room).
## Additional resources
The following topics provide more information on LiveKit rooms and connections.
- **[Managing rooms](https://docs.livekit.io/intro/basics/rooms-participants-tracks/rooms.md)**: Learn how to manage rooms using a room service client.
- **[Managing participants](https://docs.livekit.io/intro/basics/rooms-participants-tracks/participants.md)**: Learn how to manage participants using a room service client.
- **[Room service API](https://docs.livekit.io/reference/other/roomservice-api.md)**: Learn how to manage rooms using the room service API.
---
This document was rendered at 2026-02-03T03:24:49.273Z.
For the latest version of this document, see [https://docs.livekit.io/intro/basics/connect.md](https://docs.livekit.io/intro/basics/connect.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/recipes/context_variables.md
LiveKit docs › Advanced LLM › Context Variables
---
# Context Variables
> Shows how to give an agent context about the user using simple variables.
This example shows how to personalize an agent's instructions with user-specific variables. The example injects name, age, and city into the prompt before the session starts.
## Prerequisites
- Add a `.env` in this directory with your LiveKit credentials:```
LIVEKIT_URL=your_livekit_url
LIVEKIT_API_KEY=your_api_key
LIVEKIT_API_SECRET=your_api_secret
```
- Install dependencies:```bash
pip install "livekit-agents[silero]" python-dotenv
```
## Load environment, logging, and define an AgentServer
Start by loading your environment variables and setting up logging. Define an `AgentServer` which wraps your application and handles the worker lifecycle.
```python
import logging
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, AgentServer, cli, inference
from livekit.plugins import silero
load_dotenv()
logger = logging.getLogger("context-variables")
logger.setLevel(logging.INFO)
server = AgentServer()
```
## Prewarm VAD for faster connections
Preload the VAD model once per process using the `setup_fnc`.
```python
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
```
## Create an agent that accepts context
Build a lightweight agent that formats its instructions with values from a dictionary. If context is passed, the prompt is customized before the agent starts.
```python
class ContextAgent(Agent):
def __init__(self, context_vars=None) -> None:
instructions = """
You are a helpful agent. The user's name is {name}.
They are {age} years old and live in {city}.
"""
if context_vars:
instructions = instructions.format(**context_vars)
super().__init__(instructions=instructions)
async def on_enter(self):
self.session.generate_reply()
```
## Define the RTC session entrypoint
Create the context variables dictionary with user-specific data, then pass it to the agent when starting the session.
```python
@server.rtc_session()
async def entrypoint(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
context_variables = {
"name": "Shayne",
"age": 35,
"city": "Toronto"
}
session = AgentSession(
stt=inference.STT(model="deepgram/nova-3-general"),
llm=inference.LLM(model="openai/gpt-4.1-mini"),
tts=inference.TTS(model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"),
vad=ctx.proc.userdata["vad"],
preemptive_generation=True,
)
await session.start(agent=ContextAgent(context_vars=context_variables), room=ctx.room)
await ctx.connect()
```
## Run the server
```python
if __name__ == "__main__":
cli.run_app(server)
```
## Run it
```bash
python context_variables.py console
```
## How it works
1. Load environment variables and set up logging.
2. Format the agent's instructions with user-specific context variables.
3. Generate an immediate greeting using the personalized prompt when the agent enters.
## Full example
```python
import logging
from dotenv import load_dotenv
from livekit.agents import JobContext, JobProcess, Agent, AgentSession, AgentServer, cli, inference
from livekit.plugins import silero
load_dotenv()
logger = logging.getLogger("context-variables")
logger.setLevel(logging.INFO)
class ContextAgent(Agent):
def __init__(self, context_vars=None) -> None:
instructions = """
You are a helpful agent. The user's name is {name}.
They are {age} years old and live in {city}.
"""
if context_vars:
instructions = instructions.format(**context_vars)
super().__init__(instructions=instructions)
async def on_enter(self):
self.session.generate_reply()
server = AgentServer()
def prewarm(proc: JobProcess):
proc.userdata["vad"] = silero.VAD.load()
server.setup_fnc = prewarm
@server.rtc_session()
async def entrypoint(ctx: JobContext):
ctx.log_context_fields = {"room": ctx.room.name}
context_variables = {
"name": "Shayne",
"age": 35,
"city": "Toronto"
}
session = AgentSession(
stt=inference.STT(model="deepgram/nova-3-general"),
llm=inference.LLM(model="openai/gpt-4.1-mini"),
tts=inference.TTS(model="cartesia/sonic-3", voice="9626c31c-bec5-4cca-baa8-f8ba9e84c8bc"),
vad=ctx.proc.userdata["vad"],
preemptive_generation=True,
)
await session.start(agent=ContextAgent(context_vars=context_variables), room=ctx.room)
await ctx.connect()
if __name__ == "__main__":
cli.run_app(server)
```
---
This document was rendered at 2026-02-03T03:25:29.784Z.
For the latest version of this document, see [https://docs.livekit.io/recipes/context_variables.md](https://docs.livekit.io/recipes/context_variables.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/media/ingress-egress/egress/custom-template.md
LiveKit docs › Media › Stream export & import › Egress › Custom recording templates
---
# Custom recording templates
> Create your own recording layout to use with Room Composite Egress.
## Overview
LiveKit [RoomComposite egress](https://docs.livekit.io/transport/media/ingress-egress/egress/composite-recording.md#roomcomposite-egress) enables recording of all participants' tracks in a room. This document explains its functionality and customization options.
## Built-in LiveKit recording view
The recording feature in LiveKit is built on a web-based architecture, using a headless Chrome instance to render and capture output. The default view is built using LiveKit's [React Components](https://docs.livekit.io/reference/components/react.md). There are a handful of configuration options available including:
- [layout](https://docs.livekit.io/transport/media/ingress-egress/egress/composite-recording.md#default-layouts) to control how the participants are arranged in the view. (You can set or change the layout using either [`StartRoomCompositeEgress()`](https://docs.livekit.io/reference/other/egress/api.md#startroomcompositeegress) or [`UpdateLayout()`](https://docs.livekit.io/reference/other/egress/api.md#updatelayout).)
- [Encoding options](https://docs.livekit.io/reference/other/egress/api.md#EncodingOptions) to control the quality of the audio and/or video captured
For more advanced customization, LiveKit supports configuring the URL of the web application that will generate the page to be recorded, allowing full customization of the recording view.
## Building a custom recording view
While you can use any web framework, it's often easiest to start with the built-in React-based application and modify it to meet your requirements. The source code can be found in the [`template-default` folder](https://github.com/livekit/egress/tree/main/template-default/src) of the [LiveKit egress repository](https://github.com/livekit/egress). The main files include:
- [`Room.tsx`](https://github.com/livekit/egress/blob/main/template-default/src/Room.tsx): the main component that renders the recording view
- [`SpeakerLayout.tsx`](https://github.com/livekit/egress/blob/main/template-default/src/SpeakerLayout.tsx), [`SingleSpeakerLayout.tsx`](https://github.com/livekit/egress/blob/main/template-default/src/SingleSpeakerLayout.tsx): components used for the `speaker` and `single-speaker` layouts
- [`App.tsx`](https://github.com/livekit/egress/blob/main/template-default/src/App.tsx), [`index.tsx`](https://github.com/livekit/egress/blob/main/template-default/src/index.tsx): the main entry points for the application
- [`App.css`](https://github.com/livekit/egress/blob/main/template-default/src/App.css), [`index.css`](https://github.com/livekit/egress/blob/main/template-default/src/index.css): the CSS files for the application
> ℹ️ **Note**
>
> The built-in `Room.tsx` component uses the [template SDK](https://github.com/livekit/egress/tree/main/template-sdk/src/index.ts), for common tasks like:
>
> - Retrieving query string arguments (Example: [App.tsx](https://github.com/livekit/egress/blob/c665a4346fcc91f0a7a54289c8f897853dd3fc4f/template-default/src/App.tsx#L27-L30))
> - Starting a recording (Example: [Room.tsx](https://github.com/livekit/egress/blob/c665a4346fcc91f0a7a54289c8f897853dd3fc4f/template-default/src/Room.tsx#L81-L86))
> - Ending a recording (Example: [EgressHelper.setRoom()](https://github.com/livekit/egress/blob/ea1daaed50eb506d7586fb15198cd21506ecd457/template-sdk/src/index.ts#L67))
>
> If you are not using `Room.tsx` as a starting point, be sure to leverage the template SDK to handle these and other common tasks.
### Building your application
Make a copy of the above files and modify tnem to meet your requirements.
#### Example: Move non-speaking participants to the right side of the speaker view
By default the `Speaker` view shows the non-speaking participants on the left and the speaker on the right. Change this so the speaker is on the left and the non-speaking participants are on the right.
1. Copy the default components and CSS files into a new location
2. Modify `SpeakerLayout.tsx` to move the `FocusLayout` above `CarouselLayout` so it looks like this:
```tsx
return (
);
```
3. Modify `App.css` to fix the `grid-template-columns` value (reverse the values). It should look like this:
```css
.lk-focus-layout {
height: 100%;
grid-template-columns: 5fr 1fr;
}
```
### Deploying your application
Once your app is ready for testing or deployment, you'll need to host it on a web server. There are several options, such as [Vercel](https://vercel.com/).
### Testing your application
The [`egress test-egress-template`](https://github.com/livekit/livekit-cli?tab=readme-ov-file#testing-egress-templates) subcommand in the [LiveKit CLI](https://github.com/livekit/livekit-cli) makes testing easy.
The `egress test-egress-template` subcommand:
- Creates a room
- Adds the desired number of virtual publishers who will publish simulated video streams
- Opens a browser instance to your app URL with the correct parameters
Once you have your application deployed, you can use this command to test it out.
#### Usage
```shell
export LIVEKIT_API_SECRET=SECRET
export LIVEKIT_API_KEY=KEY
export LIVEKIT_URL=YOUR_LIVEKIT_URL
lk egress test-template \
--base-url YOUR_WEB_SERVER_URL \
--room ROOM_NAME \
--layout LAYOUT \
--publishers PUBLISHER_COUNT
```
This command launches a browser and opens: `YOUR_WEB_SERVER_URL?url=&token=&layout=LAYOUT`
#### Example
```shell
export LIVEKIT_API_SECRET=SECRET
export LIVEKIT_API_KEY=KEY
export LIVEKIT_URL=YOUR_LIVEKIT_URL
lk egress test-template \
--base-url http://localhost:3000/lk-recording-view \
--room my-room \
--layout grid \
--publishers 3
```
This command launches a browser and opens: `http://localhost:3000/lk-recording-view?url=wss%3A%2F%2Ftest-1234567890.livekit.cloud&token=&layout=grid`
### Using the custom recording view in production
Set the `custom_base_url` parameter on the `StartRoomCompositeEgress()` API to the URL where your custom recording application is deployed.
For additional authentication, most customers attach URL parameters to the `custom_base_url`. For example: `https://your-template-url.example.com/?yourparam={auth_info}` (and set this as your `custom_base_url`).
## Recording process
Recordings follow this workflow:
1. The `Egress.StartRoomCompositeEgress()` API is invoked
2. LiveKit assigns an available egress instance to handle the request
3. The egress recorder creates necessary connection & authentication details
4. A URL for the rendering web page is constructed with these parameters:- `url`: URL of LiveKit Server
- `token`: Access token for joining the room as a recorder
- `layout`: Desired layout passed to `StartRoomCompositeEgress()`
5. The egress recorder launches a headless Chrome instance with the constructed URL
6. The recorder waits for the web page to log `START_RECORDING` to the console
7. The recording begins
8. The recorder waits for the web page to log `END_RECORDING` to the console
9. The recording is terminated
---
This document was rendered at 2026-02-03T03:25:17.979Z.
For the latest version of this document, see [https://docs.livekit.io/transport/media/ingress-egress/egress/custom-template.md](https://docs.livekit.io/transport/media/ingress-egress/egress/custom-template.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/frontends/authentication/tokens/custom.md
LiveKit docs › Authentication › Tokens › Custom token generation
---
# Custom token generation
> Use a pre-existing token generation mechanism with LiveKit SDKs.
## Overview
If you already have a way of generating LiveKit tokens and don't want to use [sandbox token generation](https://docs.livekit.io/frontends/authentication/tokens/sandbox-token-server.md) or [endpoint token generation](https://docs.livekit.io/frontends/authentication/tokens/endpoint.md), you can use a custom `TokenSource` to get token caching and automatic refreshing.
### Caching tokens
`TokenSource.custom` will refetch cached tokens when it expires, or when the input parameters passed into the `fetch` method changes.
If you'd like to avoid the automatic caching behavior or handle it manually, see [`TokenSource.literal`](https://github.com/livekit/client-sdk-js?tab=readme-ov-file#tokensourceliteral).
## Use a custom TokenSource
This example shows how to use a custom `TokenSource` to connect to a LiveKit room.
**JavaScript**:
```typescript
import { Room, TokenSource } from 'livekit-client';
const LIVEKIT_URL = "%{wsURL}%";
// Create the TokenSource
const tokenSource = TokenSource.custom(async (options) => {
// Run your custom token generation logic, using values in `options` as inputs
// ie, something like:
const participantToken = await customTokenGenerationFunction(options.roomName, options.participantName, options.agentName, /* etc */);
return { serverUrl: LIVEKIT_URL, participantToken };
});
// Generate a new token (cached and automatically refreshed as needed)
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';
const LIVEKIT_URL = "%{wsURL}%";
// Create the TokenSource
//
// If your TokenSource.custom relies on other dependencies other than `options`, be
// sure to wrap it in a `useMemo` so that the reference stays stable.
const tokenSource = TokenSource.custom(async (options) => {
// Run your custom token generation logic, using values in `options` as inputs
// ie, something like:
const participantToken = await customTokenGenerationFunction(options.roomName, options.participantName, options.agentName, /* etc */);
return { serverUrl: LIVEKIT_URL, participantToken };
});
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 (
)
}
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 (
)
})}
>
)
}
```
---
**Swift**:
```swift
import LiveKitComponents
let LIVEKIT_URL = "%{wsURL}%"
public struct MyTokenSource: TokenSourceConfigurable {}
public extension MyTokenSource {
func fetch(_ options: TokenRequestOptions) async throws -> TokenSourceResponse {
// Run your custom token generation logic, using values in `options` as inputs
// ie, something like:
let participantToken = await customTokenGenerationFunction(options.roomName, options.participantName, options.agentName, /* etc */)
return TokenSourceResponse(serverURL: LIVEKIT_URL, participantToken: participantToken)
}
}
@main
struct SessionApp: App {
let session = Session(tokenSource: MyTokenSource())
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 LIVEKIT_URL = "%{wsURL}%"
val tokenSource = remember {
TokenSource.fromCustom { options ->
// Run your custom token generation logic, using values in `options` as inputs
// ie, something like:
var participantToken = customTokenGenerationFunction(options.roomName, options.participantName, options.agentName, /* etc */)
return@fromCustom Result.success(TokenSourceResponse(LIVEKIT_URL, participantToken))
}
}
val session = rememberSession(
tokenSource = tokenSource
)
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"
}
)
}
// Agent provides state information about the agent participant.
val agent = rememberAgent()
Text(agent.agentState.name)
// SessionMessages handles all transcriptions and chat messages
val sessionMessages = rememberSessionMessages()
LazyColumn {
items(items = sessionMessages.messages) { message ->
Text(message.message)
}
}
val messageState = rememberTextFieldState()
TextField(state = messageState)
Button(onClick = {
coroutineScope.launch {
sessionMessages.send(messageState.text.toString())
messageState.clearText()
}
}) {
Text("Send")
}
}
}
```
---
**Flutter**:
```dart
import 'package:livekit_client/livekit_client.dart' as sdk;
final LIVEKIT_URL = "%{wsURL}%";
final tokenSource = sdk.CustomTokenSource((options) async {
// Run your custom token generation logic, using values in `options` as inputs
// ie, something like:
final participantToken = await customTokenGenerationFunction(options.roomName, options.participantName, options.agentName, /* etc */);
return TokenSourceResponse(serverUrl: LIVEKIT_URL, participantToken: participantToken);
});
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';
const LIVEKIT_URL = "%{wsURL}%";
// Create the TokenSource
//
// If your TokenSource.custom relies on other dependencies other than `options`, be
// sure to wrap it in a `useMemo` so that the reference stays stable.
const tokenSource = TokenSource.custom(async (options) => {
// Run your custom token generation logic, using values in `options` as inputs
// ie, something like:
const participantToken = await customTokenGenerationFunction(options.roomName, options.participantName, options.agentName, /* etc */);
return { serverUrl: LIVEKIT_URL, participantToken };
});
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 (
{/* render the rest of your application here */}
)
}
```
---
This document was rendered at 2026-02-03T03:25:09.643Z.
For the latest version of this document, see [https://docs.livekit.io/frontends/authentication/tokens/custom.md](https://docs.livekit.io/frontends/authentication/tokens/custom.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/deploy/observability/data.md
# Source: https://docs.livekit.io/transport/data.md
LiveKit docs › Data › Overview
---
# Data overview
> An overview of realtime text and data features for LiveKit.
## Overview
LiveKit provides realtime data exchange between participants using text streams, byte streams, remote procedure calls (RPCs), and data packets. Exchange text messages, files, images, and custom data, or execute methods on other participants in the room.
## Realtime data components
Send and receive data between participants using streams, RPCs, or low-level data packets.
| Component | Description | Use cases |
| **Sending text** | Use text streams to send any amount of text between participants, with automatic chunking and topic-based routing. | Chat messages, streamed LLM responses, and realtime text communication. |
| **Sending files & bytes** | Use byte streams to transfer files, images, or any other binary data between participants with progress tracking. | File sharing, image transfer, and binary data exchange. |
| **Remote method calls** | Execute custom methods on other participants in the room and await a response, enabling app-specific coordination and data access. | Tool calls from AI agents, UI manipulation, and coordinated state management. |
| **Data packets** | Low-level API for sending individual packets with reliable or lossy delivery, providing advanced control over packet behavior. | High-frequency updates, custom protocols, and scenarios requiring precise packet control. |
| **State synchronization** | Synchronize participant attributes and room metadata across all participants in realtime. | User presence, room configuration, and shared state management. |
## In this section
Learn how to exchange data between participants.
- **[Sending text](https://docs.livekit.io/transport/data/text-streams.md)**: Use text streams to send and receive text data, such as LLM responses or chat messages.
- **[Sending files & bytes](https://docs.livekit.io/transport/data/byte-streams.md)**: Use byte streams to transfer files, images, or any other binary data.
- **[Remote method calls](https://docs.livekit.io/transport/data/rpc.md)**: Use RPC to execute custom methods on other participants in the room and await a response.
- **[Data packets](https://docs.livekit.io/transport/data/packets.md)**: Low-level API for high frequency or advanced use cases.
- **[State synchronization](https://docs.livekit.io/transport/data/state.md)**: Synchronize participant attributes and room metadata across all participants.
---
This document was rendered at 2026-02-03T03:25:18.565Z.
For the latest version of this document, see [https://docs.livekit.io/transport/data.md](https://docs.livekit.io/transport/data.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/tts/plugins/deepgram.md
# Source: https://docs.livekit.io/agents/models/tts/inference/deepgram.md
# Source: https://docs.livekit.io/agents/models/stt/plugins/deepgram.md
# Source: https://docs.livekit.io/agents/models/stt/inference/deepgram.md
LiveKit docs › Models › STT › Inference › Deepgram
---
# Deepgram STT
> Reference for Deepgram STT in LiveKit Inference.
## Overview
LiveKit Inference offers transcription powered by Deepgram. Pricing information is available on the [pricing page](https://livekit.io/pricing/inference#stt).
| Model name | Model ID | Languages |
| -------- | -------- | --------- |
| Flux | `deepgram/flux-general` | `en` |
| Nova-3 | `deepgram/nova-3` | `en`, `en-US`, `en-AU`, `en-GB`, `en-IN`, `en-NZ`, `de`, `nl`, `sv`, `sv-SE`, `da`, `da-DK`, `es`, `es-419`, `fr`, `fr-CA`, `pt`, `pt-BR`, `pt-PT`, `multi` |
| Nova-3 Medical | `deepgram/nova-3-medical` | `en`, `en-US`, `en-AU`, `en-CA`, `en-GB`, `en-IE`, `en-IN`, `en-NZ` |
| Nova-2 | `deepgram/nova-2` | `multi`, `bg`, `ca`, `zh`, `zh-CN`, `zh-Hans`, `zh-TW`, `zh-Hant`, `zh-HK`, `cs`, `da`, `da-DK`, `nl`, `en`, `en-US`, `en-AU`, `en-GB`, `en-NZ`, `en-IN`, `et`, `fi`, `nl-BE`, `fr`, `fr-CA`, `de`, `de-CH`, `el`, `hi`, `hu`, `id`, `it`, `ja`, `ko`, `ko-KR`, `lv`, `lt`, `ms`, `no`, `pl`, `pt`, `pt-BR`, `pt-PT`, `ro`, `ru`, `sk`, `es`, `es-419`, `sv`, `sv-SE`, `th`, `th-TH`, `tr`, `uk`, `vi` |
| Nova-2 Medical | `deepgram/nova-2-medical` | `en`, `en-US` |
| Nova-2 Conversational AI | `deepgram/nova-2-conversationalai` | `en`, `en-US` |
| Nova-2 Phonecall | `deepgram/nova-2-phonecall` | `en`, `en-US` |
## Usage
To use Deepgram, pass a descriptor with the model and language to the `stt` argument in your `AgentSession`:
**Python**:
```python
from livekit.agents import AgentSession
session = AgentSession(
stt="deepgram/flux-general:en",
# ... llm, tts, vad, turn_detection, etc.
)
```
---
**Node.js**:
```typescript
import { AgentSession } from '@livekit/agents';
session = new AgentSession({
stt="deepgram/flux-general:en",
// ... llm, tts, vad, turn_detection, etc.
});
```
### Multilingual transcription
Deepgram Nova-3 and Nova-2 models support multilingual transcription. In this mode, the model automatically detects the language of each segment of speech and can accurately transcribe multiple languages in the same audio stream.
Multilingual transcription is billed at a different rate than monolingual transcription. Refer to the [pricing page](https://livekit.io/pricing/inference#stt) for more information.
To enable multilingual transcription on supported models, set the language to `multi`.
### Parameters
To customize additional parameters, including the language to use, use the `STT` class from the `inference` module:
**Python**:
```python
from livekit.agents import AgentSession, inference
session = AgentSession(
stt=inference.STT(
model="deepgram/flux-general",
language="en"
),
# ... llm, tts, vad, turn_detection, etc.
)
```
---
**Node.js**:
```typescript
import { AgentSession, inference } from '@livekit/agents';
session = new AgentSession({
stt: new inference.STT({
model: "deepgram/flux-general",
language: "en"
}),
// ... llm, tts, vad, turn_detection, etc.
});
```
- **`model`** _(string)_: The model to use for the STT. See the [Model Options](https://developers.deepgram.com/docs/model) page for available models.
- **`language`** _(string)_ (optional): Language code for the transcription. If not set, the provider default applies. Set it to `multi` with supported models for multilingual transcription.
- **`extra_kwargs`** _(dict)_ (optional): Additional parameters to pass to the Deepgram STT API. Supported fields depend on the selected model. See the provider's [documentation](https://developers.deepgram.com/docs/stt/getting-started) for more information.
In Node.js this parameter is called `modelOptions`.
## Integrated regional deployment
LiveKit Inference includes an integrated deployment of Deepgram models in Mumbai, India, delivering significantly lower latency for voice agents serving users in India and surrounding regions. By reducing the round-trip to external API endpoints, this regional deployment improves STT response times, resulting in more responsive and natural-feeling conversations.
### Automatic routing
LiveKit Inference automatically routes requests to the regional deployment when your configuration matches one of the supported models and languages below. No code changes or configuration are required. For other configurations, requests are routed to Deepgram's API.
### Supported configurations
| Model | Supported languages |
| `deepgram/nova-3-general` | English (`en`), Hindi (`hi`), Multilingual (`multi`) |
| `deepgram/nova-2-general` | English (`en`), Hindi (`hi`) |
| `deepgram/flux-general` | English (`en`) |
For example, to use Hindi transcription with Nova-3:
**Python**:
```python
from livekit.agents import AgentSession
session = AgentSession(
stt="deepgram/nova-3-general:hi",
# ... llm, tts, etc.
)
```
---
**Node.js**:
```typescript
import { AgentSession } from '@livekit/agents';
session = new AgentSession({
stt: "deepgram/nova-3-general:hi",
// ... llm, tts, etc.
});
```
## Turn detection
Deepgram Flux includes a custom phrase endpointing model that uses both acoustic and semantic cues. To use this model for [turn detection](https://docs.livekit.io/agents/logic/turns.md), set `turn_detection="stt"` in the `AgentSession` constructor. You should also provide a VAD plugin for responsive interruption handling.
```python
session = AgentSession(
turn_detection="stt",
stt=inference.STT(
model="deepgram/flux-general",
language="en"
),
vad=silero.VAD.load(), # Recommended for responsive interruption handling
# ... llm, tts, etc.
)
```
## Additional resources
The following links provide more information about Deepgram in LiveKit Inference.
- **[Deepgram Plugin](https://docs.livekit.io/agents/models/stt/plugins/deepgram.md)**: Plugin to use your own Deepgram account instead of LiveKit Inference.
- **[Deepgram docs](https://developers.deepgram.com/docs)**: Deepgram service documentation.
---
This document was rendered at 2026-02-03T03:25:02.637Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/stt/inference/deepgram.md](https://docs.livekit.io/agents/models/stt/inference/deepgram.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/llm/plugins/deepseek.md
# Source: https://docs.livekit.io/agents/models/llm/inference/deepseek.md
LiveKit docs › Models › LLM › Inference › DeepSeek
---
# DeepSeek LLM
> Reference for DeepSeek models served via LiveKit Inference.
## Overview
LiveKit Inference offers DeepSeek models through Baseten. Pricing is available on the [pricing page](https://livekit.io/pricing/inference#llm).
| Model name | Model ID | Providers |
| ---------- | -------- | -------- |
| DeepSeek V3 | `deepseek-ai/deepseek-v3` | `baseten` |
| DeepSeek V3.2 | `deepseek-ai/deepseek-v3.2` | `baseten` |
## Usage
To use DeepSeek, pass the model id to the `llm` argument in your `AgentSession`. LiveKit Inference manages the connection to the best available provider automatically.
**Python**:
```python
from livekit.agents import AgentSession
session = AgentSession(
llm="deepseek-ai/deepseek-v3",
# ... tts, stt, vad, turn_detection, etc.
)
```
---
**Node.js**:
```typescript
import { AgentSession } from '@livekit/agents';
session = new AgentSession({
llm: "deepseek-ai/deepseek-v3",
// ... tts, stt, vad, turn_detection, etc.
});
```
### Parameters
To customize additional parameters, including the specific provider to use, use the `LLM` class from the `inference` module.
**Python**:
```python
from livekit.agents import AgentSession, inference
session = AgentSession(
llm=inference.LLM(
model="deepseek-ai/deepseek-v3",
provider="baseten",
extra_kwargs={
"max_completion_tokens": 1000
}
),
# ... tts, stt, vad, turn_detection, etc.
)
```
---
**Node.js**:
```typescript
import { AgentSession, inference } from '@livekit/agents';
session = new AgentSession({
llm: new inference.LLM({
model: "deepseek-ai/deepseek-v3",
provider: "baseten",
modelOptions: {
max_completion_tokens: 1000
}
}),
// ... tts, stt, vad, turn_detection, etc.
});
```
- **`model`** _(string)_: The model ID from the [models list](#models).
- **`provider`** _(string)_ (optional): Set a specific provider to use for the LLM. Refer to the [models list](#models) for available providers. If not set, LiveKit Inference uses the best available provider, and bills accordingly.
- **`extra_kwargs`** _(dict)_ (optional): Additional parameters to pass to the provider's Chat Completions API, such as `max_completion_tokens`. See the provider's [documentation](#additional-resources) for more information.
In Node.js this parameter is called `modelOptions`.
## Additional resources
The following links provide more information about DeepSeek in LiveKit Inference.
- **[Baseten Plugin](https://docs.livekit.io/agents/models/llm/plugins/baseten.md)**: Plugin to use your own Baseten account instead of LiveKit Inference.
- **[DeepSeek Plugin](https://docs.livekit.io/agents/models/llm/plugins/deepseek.md)**: Plugin to use DeepSeek's official API instead of LiveKit Inference.
- **[Baseten docs](https://docs.baseten.co/development/model-apis/overview)**: Baseten's official Model API documentation.
---
This document was rendered at 2026-02-03T03:24:58.810Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/llm/inference/deepseek.md](https://docs.livekit.io/agents/models/llm/inference/deepseek.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/deploy.md
LiveKit docs › Get Started › Introduction
---
# Introduction
> Deploy, manage, and monitor your LiveKit applications with a comprehensive suite of tools and flexible hosting options.
## Overview
LiveKit provides tools for deploying, managing, and monitoring your realtime apps in production. Whether you choose the fully managed LiveKit Cloud or deploy to custom environments, you have access to testing frameworks, observability tools, and deployment options that ensure your apps are reliable, scalable, and maintainable.
Deploying with LiveKit means you can focus on building your app while LiveKit handles the complexity of WebRTC infrastructure, scaling, and global distribution. You can test and validate your agents, monitor their behavior in production, and deploy to the infrastructure that best fits your needs.
## Key concepts
Understand these core concepts to deploy and manage effective LiveKit applications.
### Observability
Monitor and analyze your agent's behavior with comprehensive observability tools. Use built-in LiveKit Cloud insights to view transcripts, traces, logs, and audio recordings, or collect custom data with data hooks for integration with external systems.
- **[Observability overview](https://docs.livekit.io/deploy/observability.md)**: Learn how to monitor and analyze your agents with observability tools.
### Agent deployment
Deploy your agents to LiveKit Cloud to run them on LiveKit's global network and infrastructure. LiveKit Cloud provides automatic scaling and load balancing, ensuring capacity for new sessions up to the limits of your plan.
- **[Deploying agents overview](https://docs.livekit.io/deploy/agents.md)**: Learn how to deploy your agents to LiveKit Cloud.
## Getting started
Choose your deployment path to get started:
- **[Deploy agents to LiveKit Cloud](https://docs.livekit.io/deploy/agents.md)**: Deploy your agents to LiveKit Cloud's fully managed infrastructure.
- **[Monitor your agents](https://docs.livekit.io/deploy/observability.md)**: Set up observability to monitor and analyze your agent sessions.
## Additional resources
For complete deployment documentation, API references, and advanced topics, see the [Reference](https://docs.livekit.io/reference.md) section.
- **[Agent CLI reference](https://docs.livekit.io/reference/other/agent-cli.md)**: Complete CLI reference for deploying agents to LiveKit Cloud.
- **[Server APIs](https://docs.livekit.io/reference.md#server-apis)**: API reference for managing LiveKit servers and deployments.
- **[Events and error handling](https://docs.livekit.io/reference/other/events.md)**: Learn about LiveKit events and how to handle errors in your deployments.
---
This document was rendered at 2026-02-03T03:25:21.912Z.
For the latest version of this document, see [https://docs.livekit.io/deploy.md](https://docs.livekit.io/deploy.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/self-hosting/deployment.md
LiveKit docs › Self-hosting › Deployment
---
# Deploying LiveKit
> WebRTC servers can be tricky to deploy because of their use of UDP ports and having to know their own public IP address. This guide will help you get a secure LiveKit deployment up and running.
## Domain, SSL certificates, and load balancer
In order to have a secure LiveKit deployment, you will need a domain as well as a SSL certificate for that domain. This domain will be used as the primary endpoint for LiveKit SDKs, for example: `wss://livekit.yourhost.com`. The SSL certificate must be signed by a trusted certificate authority; self-signed certs do not work here.
You will also need to set up HTTPS/SSL termination with a load balancer or reverse proxy.
If you are using TURN, then a separate TURN domain and SSL cert will be needed, as well.
## Improving connectivity with TURN
Certain corporate firewalls block not only UDP traffic, but non-secure TCP traffic, as well. In those cases, it's helpful to use a TURN server. [Here's](https://bloggeek.me/webrtc-turn/) a good resource if you're interested in reading more about how TURN is used.
The good news is LiveKit includes an embedded TURN server. It's a secure TURN implementation that has integrated authentication with the rest of LiveKit. The authentication layer ensures that only clients that have already established a signal connection could connect to our TURN server.
### TURN/TLS
To firewalls, TLS traffic looks no different from regular HTTPS traffic to websites. Enabling TURN/TLS gives you the broadest coverage in client connectivity, including those behind corporate firewalls. TURN/TLS can be enabled with:
```yaml
turn:
enabled: true
tls_port: 5349
domain: turn.myhost.com
cert_file: /path/to/turn.crt
key_file: /path/to/turn.key
```
LiveKit will perform TLS termination, so you will have to specify the certificates in the config. When running multiple LiveKit instances, you can place a layer 4 load balancer in front of the TCP port.
If you are not using a load balancer, `turn.tls_port` needs to be set to 443, as that will be the port that's advertised to clients.
### TURN/UDP
As QUIC (HTTP/3) gains adoption, some firewalls started allowing UDP traffic to pass through port 443. In those cases, it helps to use TURN/UDP on port 443. UDP is preferred over TCP for WebRTC traffic, as it has better control over congestion and latency. TURN/UDP can be enabled with:
```yaml
turn:
enabled: true
udp_port: 443
```
## Configuration
For production deploys, we recommend using a config file. The config file can be passed in via `--config` flag, or the body of the YAML can be set with a `LIVEKIT_CONFIG` environment variable.
Below is a recommended config for a production deploy. To view other customization options, see [config-sample.yaml](https://github.com/livekit/livekit/blob/master/config-sample.yaml)
```yaml
port: 7880
log_level: info
rtc:
tcp_port: 7881
port_range_start: 50000
port_range_end: 60000
# use_external_ip should be set to true for most cloud environments where
# the host has a public IP address, but is not exposed to the process.
# LiveKit will attempt to use STUN to discover the true IP, and advertise
# that IP with its clients
use_external_ip: true
redis:
# redis is recommended for production deploys
address: my-redis-server.name:6379
keys:
# key-value pairs
# your_api_key:
# When enabled, LiveKit will expose prometheus metrics on :6789/metrics
#prometheus_port: 6789
turn:
enabled: true
# domain must match tls certificate
domain:
# defaults to 3478. If not using a load balancer, must be set to 443.
tls_port: 3478
```
## Resources
The scalability of LiveKit is bound by CPU and bandwidth. We recommend running production setups on 10Gbps ethernet or faster.
When deploying to cloud providers, compute-optimized instance types are the most suitable for LiveKit.
If running in a Dockerized environment, host networking should be used for optimal performance.
---
This document was rendered at 2026-02-03T03:25:20.421Z.
For the latest version of this document, see [https://docs.livekit.io/transport/self-hosting/deployment.md](https://docs.livekit.io/transport/self-hosting/deployment.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/deploy/custom/deployments.md
LiveKit docs › Agent deployment › Self-hosted deployments
---
# Self-hosted deployments
> Guide to running LiveKit agents on your own infrastructure.
## Overview
LiveKit agents are ready to deploy to any container orchestration system such as Kubernetes. The framework uses a worker pool model and job dispatch is automatically balanced by LiveKit server across available agent servers. The agent servers themselves spawn a new sub-process for each job, and that job is where your code and agent participant run.
## Project setup
Deploying to your own infrastructure generally requires a simple `Dockerfile` that builds and runs an agent server, and a deployment platform that scales your agent server pool based on load.
The following starter projects each include a working Dockerfile and CI configuration.
- **[Python Voice Agent](https://github.com/livekit-examples/agent-starter-python)**: A production-ready voice AI starter project for Python.
- **[Node.js Voice Agent](https://github.com/livekit-examples/agent-starter-node)**: A production-ready voice AI starter project for Node.js.
## Where to deploy
LiveKit Agents can be deployed almost anywhere. The LiveKit team and community have found the following deployment platforms to be the easiest and most cost-effective to use.
- **[LiveKit Cloud](https://docs.livekit.io/deploy/agents.md)**: Run your agent on the same network and infrastructure that serves LiveKit Cloud, with builds, deployment, and scaling handled for you.
- **[Kubernetes](https://github.com/livekit-examples/agent-deployment/tree/main/kubernetes)**: Sample configuration for deploying and autoscaling LiveKit Agents on Kubernetes.
- **[Render](https://github.com/livekit-examples/agent-deployment/tree/main/render)**: Sample configuration for deploying and autoscaling LiveKit Agents on Render.
- **[More deployment examples](https://github.com/livekit-examples/agent-deployment)**: Example `Dockerfile` and configuration files for a variety of deployment platforms.
## Networking
Agent servers use a WebSocket connection to register with LiveKit server and accept incoming jobs. This means that agent servers do not need to expose any inbound hosts or ports to the public internet.
You may optionally expose a private health check endpoint for monitoring, but this is not required for normal operation. The default health check server listens on `http://0.0.0.0:8081/`.
## Environment variables
It is best to configure your agent server with environment variables for secrets like API keys. In addition to the LiveKit variables, you are likely to need additional keys for external services your agent depends on.
For instance, an agent built with the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md) needs the following keys at a minimum:
** Filename: `.env`**
```shell
DEEPGRAM_API_KEY=
OPENAI_API_KEY=
CARTESIA_API_KEY=
LIVEKIT_API_KEY=%{apiKey}%
LIVEKIT_API_SECRET=%{apiSecret}%
LIVEKIT_URL=%{wsURL}%
```
> ❗ **Project environments**
>
> It's recommended to use a separate LiveKit instance for staging, production, and development environments. This ensures you can continue working on your agent locally without accidentally processing real user traffic.
>
> In LiveKit Cloud, make a separate project for each environment. Each has a unique URL, API key, and secret.
>
> For self-hosted LiveKit server, use a separate deployment for staging and production and a local server for development.
## Storage
Agent server and job processes have no particular storage requirements beyond the size of the Docker image itself (typically less than 1GB). 10GB of ephemeral storage should be more than enough to account for this and any temporary storage needs your app has.
## Memory and CPU
Memory and CPU requirements vary significantly based on the specific details of your app. For instance, agents that use [enhanced noise cancellation](https://docs.livekit.io/transport/media/enhanced-noise-cancellation.md) or the [LiveKit turn detector](https://docs.livekit.io/agents/logic/turns/turn-detector.md) require more CPU and memory than those that don't. In some cases, the memory requirements might exceed the amount available on a cloud provider's free tier.
LiveKit recommends 4 cores and 8GB per agent server as a starting rule for most voice AI apps. This agent server can handle 10-25 concurrent jobs, depending on the components in use.
> ℹ️ **Real world load test results**
>
> LiveKit ran a load test to evaluate the memory and CPU requirements of a typical voice-to-voice app.
>
> - 30 agents each placed in their own LiveKit Cloud room.
> - 30 simulated user participants, one in each room.
> - Each simulated participant published looping speech audio to the agents.
> - Each agent subscribed to the incoming audio of the user and ran the Silero VAD plugin.
> - Each agent published their own audio (simple looping sine wave).
> - One additional user participant with a corresponding voice AI agent to ensure subjective quality of service.
>
> This test ran all agents on a single 4-Core, 8GB machine. This machine reached peak usage of:
>
> - CPU: ~3.8 cores utilized
> - Memory: ~2.8GB used
## Rollout
Agent servers stop accepting jobs upon `SIGINT` or `SIGTERM`. Any job still running on the agent server continues to run to completion. It's important that you configure a large enough grace period such that your jobs can finish without interrupting the user experience.
Voice AI apps might require a 10+ minute grace period to allow for conversations to finish.
Different deployment platforms have different ways of setting this grace period. In Kubernetes, it's the `terminationGracePeriodSeconds` field in the pod spec.
Consult your deployment platform's documentation for more information.
## Load balancing
LiveKit server includes a built-in balanced job distribution system. This system peforms round-robin distribution with a single-assignment principle that ensures each job is assigned to only one agent server. If an agent server fails to accept the job within a predetermined timeout period, the job is sent to another available agent server instead.
LiveKit Cloud additionally exercises geographic affinity to prioritize matching users and agent servers that are geographically closest to each other. This ensures the lowest possible latency between users and agents.
## Agent server availability
Agent server availability is defined by the `load_fnc` and `load_threshold` parameters in the `AgentServer` constructor. The `load_fnc` must return a value between 0 and 1, indicating how busy the agent server is. `load_threshold` is the load value above which the agent server stops accepting new jobs.
The default `load_fnc` is overall CPU utilization, and the default `load_threshold` is `0.7`.
In a custom deployment, you can override `load_fnc` and `load_threshold` to match the scaling behavior of your environment and application.
## Autoscaling
To handle variable traffic patterns, add an autoscaling strategy to your deployment platform. Your autoscaler should use the same underlying metrics as your `load_fnc` (the default is CPU utilization) but should scale up at a _lower_ threshold than your agent server's `load_threshold`. This ensures continuity of service by adding new agent servers before existing ones go out of service. For example, if your `load_threshold` is `0.7`, you should scale up at `0.5`.
Since voice agents are typically long running tasks (relative to typical web requests), rapid increases in load are more likely to be sustained. In technical terms: spikes are less spikey. For your autoscaling configuration, you should consider _reducing_ cooldown/stabilization periods when scaling up. When scaling down, consider _increasing_ cooldown/stabilization periods because agent servers take time to drain.
For example, if deploying on Kubernetes using a Horizontal Pod Autoscaler, see [stabilizationWindowSeconds](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#default-behavior).
## LiveKit Cloud dashboard
You can use LiveKit Cloud for media transport and agent observability regardless of whether your agents are deployed to a custom environment. See the [Agent observability](https://docs.livekit.io/deploy/observability/insights.md) guide for more information.
## Job crashes
Job crashes are written to agent server logs for monitoring. If a job process crashes, it doesn't affect the agent server or other jobs. If the agent server crashes, all child jobs are terminated.
---
This document was rendered at 2026-02-03T03:25:22.700Z.
For the latest version of this document, see [https://docs.livekit.io/deploy/custom/deployments.md](https://docs.livekit.io/deploy/custom/deployments.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/telephony/accepting-calls/dispatch-rule.md
LiveKit docs › Accepting calls › Dispatch rule
---
# Dispatch rule
> How to create and configure a dispatch rule.
## Overview
A _dispatch rule_ controls how callers are added as SIP participants in rooms. When an inbound call reaches your SIP trunking provider and is connected to LiveKit, the SIP service authenticates the inbound trunk (if applicable) and looks for a matching dispatch rule. It then uses the rule to dispatch SIP participants to rooms.
The dispatch rule can also include room configuration options that specify which agents to dispatch to the room. _Agent dispatch_ is a separate feature that handles how agents are dispatched to rooms. To learn more, see [Agent dispatch](https://docs.livekit.io/agents/server/agent-dispatch.md).
To create a dispatch rule with the SIP service, use the `CreateSIPDispatchRule` API. It returns a `SIPDispatchRuleInfo` object that describes the dispatch rule.
By default, a dispatch rule matches all your trunks and makes a caller's phone number visible to others in the room. You can modify these defaults using dispatch rule options. For a full list of available options, see the [`CreateSIPDispatchRule`](https://docs.livekit.io/reference/telephony/sip-api.md#createsipdispatchrule) API reference.
To learn more about, see the following:
- [SIP overview](https://docs.livekit.io/telephony.md): General concepts and features.
- [SIP API](https://docs.livekit.io/reference/telephony/sip-api.md): API endpoints and types.
## Caller dispatch rule (individual)
An `SIPDispatchRuleIndividual` rule creates a new room for each caller. The name of the created room is the phone number of the caller plus a random suffix. You can optionally add a specific prefix to the room name by using the `roomPrefix` option.
The following examples dispatch callers into individual rooms prefixed with `call-`, and [dispatches an agent](https://docs.livekit.io/agents/server/agent-dispatch.md) named `inbound-agent` to newly created rooms:
**LiveKit CLI**:
```json
{
"dispatch_rule":
{
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
},
"name": "My dispatch rule",
"roomConfig": {
"agents": [{
"agentName": "inbound-agent",
"metadata": "job dispatch metadata"
}]
}
}
}
```
---
**Node.js**:
```typescript
const rule: SipDispatchRuleIndividual = {
roomPrefix: "call-",
type: 'individual',
};
const options: CreateSipDispatchRuleOptions = {
name: 'My dispatch rule',
roomConfig: new RoomConfiguration({
agents: [
new RoomAgentDispatch({
agentName: "inbound-agent",
metadata: 'dispatch metadata',
}),
],
}),
};
const dispatchRule = await sipClient.createSipDispatchRule(rule, options);
console.log("created dispatch rule", dispatchRule);
```
---
**Python**:
```python
from livekit import api
lkapi = api.LiveKitAPI()
# Create a dispatch rule to place each caller in a separate room
rule = api.SIPDispatchRule(
dispatch_rule_individual = api.SIPDispatchRuleIndividual(
room_prefix = 'call-',
)
)
request = api.CreateSIPDispatchRuleRequest(
dispatch_rule = api.SIPDispatchRuleInfo(
rule = rule,
name = 'My dispatch rule',
trunk_ids = [],
room_config=api.RoomConfiguration(
agents=[api.RoomAgentDispatch(
agent_name="inbound-agent",
metadata="job dispatch metadata",
)]
)
)
)
dispatch = await lkapi.sip.create_sip_dispatch_rule(request)
print("created dispatch", dispatch)
await lkapi.aclose()
```
---
**Ruby**:
```ruby
require 'livekit'
sip_service = LiveKit::SIPServiceClient.new(
ENV['LIVEKIT_URL'],
api_key: ENV['LIVEKIT_API_KEY'],
api_secret: ENV['LIVEKIT_API_SECRET']
)
rule = LiveKit::Proto::SIPDispatchRule.new(
dispatch_rule_direct: LiveKit::Proto::SIPDispatchRuleIndividual.new(
room_prefix: "call-",
)
)
resp = sip_service.create_sip_dispatch_rule(
rule,
name: "My dispatch rule",
room_config: LiveKit::Proto::RoomConfiguration.new(
agents: [
LiveKit::Proto::RoomAgentDispatch.new(
agent_name: "inbound-agent",
metadata: "job dispatch metadata",
)
]
)
)
puts resp.data
```
---
**Go**:
```go
func main() {
rule := &livekit.SIPDispatchRule{
Rule: &livekit.SIPDispatchRule_DispatchRuleIndividual{
DispatchRuleIndividual: &livekit.SIPDispatchRuleIndividual{
RoomPrefix: "call-",
},
},
}
request := &livekit.CreateSIPDispatchRuleRequest{
DispatchRule: &livekit.SIPDispatchRuleInfo{
Name: "My dispatch rule",
Rule: rule,
RoomConfig: &livekit.RoomConfiguration{
Agents: []*livekit.RoomAgentDispatch{
{
AgentName: "inbound-agent",
Metadata: "job dispatch metadata",
},
},
},
},
}
sipClient := lksdk.NewSIPClient(os.Getenv("LIVEKIT_URL"),
os.Getenv("LIVEKIT_API_KEY"),
os.Getenv("LIVEKIT_API_SECRET"))
// Execute the request
dispatchRule, err := sipClient.CreateSIPDispatchRule(context.Background(), request)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(dispatchRule)
}
}
```
---
**Kotlin**:
The SIP service client in Kotlin requires the HTTPS URL for the `host` parameter. This is your LIVEKIT_URL with the `wss` scheme replaced with the `https` scheme. For example, `https://.livekit.cloud`.
> ℹ️ **Agent dispatch not supported**
>
> Adding a room configuration to a dispatch rule to enable agent dispatch is not supported in Kotlin.
```kotlin
import io.livekit.server.SipServiceClient
import io.livekit.server.SIPDispatchRuleIndividual
import io.livekit.server.CreateSipDispatchRuleOptions
val sipClient = SipServiceClient.createClient(
host = System.getenv("LIVEKIT_URL").replaceFirst(Regex("^ws"), "http"),
apiKey = System.getenv("LIVEKIT_API_KEY"),
secret = System.getenv("LIVEKIT_API_SECRET")
)
val rule = SIPDispatchRuleIndividual(
roomPrefix = "call-"
)
val response = sipClient.createSipDispatchRule(
rule = rule,
options = CreateSipDispatchRuleOptions(
name = "My dispatch rule"
)
).execute()
if (response.isSuccessful) {
val dispatchRule = response.body()
println("Dispatch rule created: ${dispatchRule}")
}
```
---
**LiveKit Cloud**:
1. Sign in to the **LiveKit Cloud** [dashboard](https://cloud.livekit.io/).
2. Select **Telephony** → [**Dispatch rules**](https://cloud.livekit.io/projects/p_/telephony/dispatch).
3. Select **Create new dispatch rule**.
4. Select the **JSON editor** tab.
> ℹ️ **Note**
>
> You can also use the **Dispatch rule details** tab to create a dispatch rule. However, the JSON editor allows you to configure all available [parameters](https://docs.livekit.io/reference/telephony/sip-api.md#createsipdispatchrule).
5. Copy and paste the following JSON:
```json
{
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
},
"name": "My dispatch rule",
"roomConfig": {
"agents": [{
"agentName": "inbound-agent",
"metadata": "job dispatch metadata"
}]
}
}
```
6. Select **Create**.
> ℹ️ **Note**
>
> When you omit the `trunk_ids` field, the dispatch rule matches calls from all inbound trunks.
## Direct dispatch rule
A direct dispatch rule places all callers into a specified room. You can optionally protect room access by adding a pin in the `pin` field:
In the following examples, all calls are immediately connected to room `open-room` on LiveKit.
**LiveKit CLI**:
1. Create a file named `dispatch-rule.json` and add the following:
```json
{
"dispatch_rule":
{
"rule": {
"dispatchRuleDirect": {
"roomName": "open-room"
}
},
"name": "My dispatch rule"
}
}
```
2. Create the dispatch rule using `lk`:
```shell
lk sip dispatch create dispatch-rule.json
```
---
**Node.js**:
```typescript
import { SipClient } from 'livekit-server-sdk';
const sipClient = new SipClient(process.env.LIVEKIT_URL,
process.env.LIVEKIT_API_KEY,
process.env.LIVEKIT_API_SECRET);
// Name of the room to attach the call to
const roomName = 'open-room';
const dispatchRuleOptions = {
name: 'My dispatch rule',
};
// Dispatch all callers to the same room
const ruleType = {
roomName: roomName,
type: 'direct',
};
const dispatchRule = await sipClient.createSipDispatchRule(
ruleType,
dispatchRuleOptions
);
console.log(dispatchRule);
```
---
**Python**:
```python
import asyncio
from livekit import api
async def main():
livekit_api = api.LiveKitAPI()
# Create a dispatch rule to place all callers in the same room
rule = api.SIPDispatchRule(
dispatch_rule_direct = api.SIPDispatchRuleDirect(
room_name = 'open-room',
)
)
request = api.CreateSIPDispatchRuleRequest(
dispatch_rule = api.SIPDispatchRuleInfo(
rule = rule,
name = 'My dispatch rule',
)
)
try:
dispatchRule = await livekit_api.sip.create_sip_dispatch_rule(request)
print(f"Successfully created {dispatchRule}")
except api.twirp_client.TwirpError as e:
print(f"{e.code} error: {e.message}")
await livekit_api.aclose()
asyncio.run(main())
```
---
**Ruby**:
```ruby
require 'livekit'
name = "My dispatch rule"
room_name = "open-room"
sip_service = LiveKit::SIPServiceClient.new(
ENV['LIVEKIT_URL'],
api_key: ENV['LIVEKIT_API_KEY'],
api_secret: ENV['LIVEKIT_API_SECRET']
)
rule = LiveKit::Proto::SIPDispatchRule.new(
dispatch_rule_direct: LiveKit::Proto::SIPDispatchRuleDirect.new(
room_name: room_name,
)
)
resp = sip_service.create_sip_dispatch_rule(
rule,
name: name,
)
puts resp.data
```
---
**Go**:
```go
package main
import (
"context"
"fmt"
"os"
lksdk "github.com/livekit/server-sdk-go/v2"
"github.com/livekit/protocol/livekit"
)
func main() {
// Specify rule type and options
rule := &livekit.SIPDispatchRule{
Rule: &livekit.SIPDispatchRule_DispatchRuleDirect{
DispatchRuleDirect: &livekit.SIPDispatchRuleDirect{
RoomName: "open-room",
},
},
}
// Create request
request := &livekit.CreateSIPDispatchRuleRequest{
DispatchRule: &livekit.SIPDispatchRuleInfo{
Rule: rule,
Name: "My dispatch rule",
},
}
sipClient := lksdk.NewSIPClient(os.Getenv("LIVEKIT_URL"),
os.Getenv("LIVEKIT_API_KEY"),
os.Getenv("LIVEKIT_API_SECRET"))
// Execute the request
dispatchRule, err := sipClient.CreateSIPDispatchRule(context.Background(), request)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(dispatchRule)
}
}
```
---
**Kotlin**:
> ℹ️ **Agent dispatch not supported**
>
> Adding a room configuration to a dispatch rule to enable agent dispatch is not supported in Kotlin.
```kotlin
import io.livekit.server.SipServiceClient
import io.livekit.server.SIPDispatchRuleDirect
import io.livekit.server.CreateSipDispatchRuleOptions
val sipClient = SipServiceClient.createClient(
host = System.getenv("LIVEKIT_URL").replaceFirst(Regex("^ws"), "http"),
apiKey = System.getenv("LIVEKIT_API_KEY"),
secret = System.getenv("LIVEKIT_API_SECRET")
)
val rule = SIPDispatchRuleDirect(
roomName = "open-room"
)
val response = sipClient.createSipDispatchRule(
rule = rule,
options = CreateSipDispatchRuleOptions(
name = "My dispatch rule"
)
).execute()
if (response.isSuccessful) {
val dispatchRule = response.body()
println("Dispatch rule created: ${dispatchRule}")
}
```
---
**LiveKit Cloud**:
1. Sign in to the **LiveKit Cloud** [dashboard](https://cloud.livekit.io/).
2. Select **Telephony** → [**Dispatch rules**](https://cloud.livekit.io/projects/p_/telephony/dispatch).
3. Select **Create new dispatch rule**.
4. Select the **JSON editor** tab.
> ℹ️ **Note**
>
> You can also use the **Dispatch rule details** tab for this example by selecting **Direct** for **Rule type**.
5. Copy and paste the following JSON:
```json
{
"rule": {
"dispatchRuleDirect": {
"roomName": "open-room"
}
},
"name": "My dispatch rule"
}
```
6. Select **Create**.
### Pin-protected room
Add a `pin` to a room to require callers to enter a pin to connect to a room in LiveKit. The following example requires callers to enter `12345#` on the phone to enter `safe-room`:
```json
{
"dispatch_rule":
{
"trunk_ids": [],
"rule": {
"dispatchRuleDirect": {
"roomName": "safe-room",
"pin": "12345"
}
},
"name": "My dispatch rule"
}
}
```
## Callee dispatch rule
This creates a dispatch rule that puts callers into rooms based on the called number. The name of the room is the called phone number plus an optional prefix (if `roomPrefix` is set). You can optionally add a random suffix for each caller by setting `randomize` to true, making a separate room per caller.
**LiveKit CLI**:
```json
{
"dispatch_rule":
{
"rule": {
"dispatchRuleCallee": {
"roomPrefix": "number-",
"randomize": false
}
},
"name": "My dispatch rule"
}
}
```
---
**Node.js**:
Callee dispatch rules can't be created using Node.js.
---
**Python**:
For an executable example, replace the rule in the [Direct dispatch rule](#direct-dispatch-rule) example with the following rule:
```python
from livekit import api
# Create a dispatch rule to place callers to the same phone number in the same room
rule = api.SIPDispatchRule(
dispatch_rule_callee = api.SIPDispatchRuleCallee(
room_prefix = 'number-',
randomize = False,
)
)
```
---
**Ruby**:
For an executable example, replace the rule in the [Direct dispatch rule](#direct-dispatch-rule) example with the following rule:
```ruby
rule = LiveKit::Proto::SIPDispatchRule.new(
dispatch_rule_callee: LiveKit::Proto::SIPDispatchRuleCallee.new(
room_prefix: 'number-',
randomize: false,
)
)
```
---
**Go**:
For an executable example, replace the rule in the [Direct dispatch rule](#direct-dispatch-rule) example with the following rule:
```go
rule := &livekit.SIPDispatchRule{
Rule: &livekit.SIPDispatchRule_DispatchRuleCallee{
DispatchRuleCallee: &livekit.SIPDispatchRuleCallee{
RoomPrefix: "number-",
Randomize: false,
},
},
}
```
---
**Kotlin**:
Callee dispatch rules can't be created using Kotlin.
---
**LiveKit Cloud**:
1. Sign in to the **LiveKit Cloud** [dashboard](https://cloud.livekit.io/).
2. Select **Telephony** → [**Dispatch rules**](https://cloud.livekit.io/projects/p_/telephony/dispatch).
3. Select **Create new dispatch rule**.
4. Select the **JSON editor** tab.
> ℹ️ **Note**
>
> You can also use the **Dispatch rule details** tab for this example by selecting **Callee** for **Rule type**.
5. Copy and paste the following JSON:
```json
{
"rule": {
"dispatchRuleCallee": {
"roomPrefix": "number-",
"randomize": false
}
},
"name": "My dispatch rule"
}
```
6. Select **Create**.
## Setting custom attributes on inbound SIP participants
LiveKit participants have an `attributes` field that stores key-value pairs. You can add custom attributes for SIP participants in the dispatch rule. These attributes are inherited by all SIP participants created by the dispatch rule.
To learn more, see [SIP participant attributes](https://docs.livekit.io/reference/telephony/sip-participant.md#sip-participant-attributes).
The following examples add two attributes to SIP participants created by this dispatch rule:
**LiveKit CLI**:
```json
{
"dispatch_rule":
{
"attributes": {
"": "",
"": ""
},
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
},
"name": "My dispatch rule"
}
}
```
---
**Node.js**:
For an executable example, replace `dispatchRuleOptions` in the [Direct dispatch rule](#direct-dispatch-rule) example with the following options:
```typescript
const dispatchRuleOptions = {
name: 'My invidividual dispatch rule',
attributes: {
"": "",
"": ""
},
};
```
---
**Python**:
For an executable example, replace `request` in the [Direct dispatch rule](#direct-dispatch-rule) example with the following options:
```python
request = api.CreateSIPDispatchRuleRequest(
dispatch_rule = api.SIPDispatchRuleInfo(
rule = rule,
name = 'My dispatch rule',
attributes = {
"": "",
"": "",
}
)
)
```
---
**Ruby**:
For an executable example, use the [Direct dispatch rule](#direct-dispatch-rule) example with the following options:
```ruby
resp = sip_service.create_sip_dispatch_rule(
rule,
name: name,
attributes: {
"" => "",
"" => "",
},
)
```
---
**Go**:
For an executable example, replace `request` in the [Direct dispatch rule](#direct-dispatch-rule) example with the following code:
```go
// Create a request
request := &livekit.CreateSIPDispatchRuleRequest{
DispatchRule: &livekit.SIPDispatchRuleInfo{
Rule: rule,
Name: "My dispatch rule",
Attributes: map[string]string{
"": "",
"": "",
},
},
}
```
---
**Kotlin**:
Setting participant attributes in dispatch rules is not supported in Kotlin.
---
**LiveKit Cloud**:
1. Sign in to the **LiveKit Cloud** [dashboard](https://cloud.livekit.io/).
2. Select **Telephony** → [**Dispatch rules**](https://cloud.livekit.io/projects/p_/telephony/dispatch).
3. Select **Create new dispatch rule**.
4. Select the **JSON editor** tab.
> ℹ️ **Note**
>
> The `attributes` parameter is only available in the **JSON editor** tab.
5. Copy and paste the following text into the editor:
```json
{
"name": "My dispatchrule",
"attributes": {
"": "",
"": ""
},
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
}
}
```
6. Select **Create**.
## Setting custom metadata on inbound SIP participants
LiveKit participants have a `metadata` field that can store arbitrary data for your application (typically JSON). It can also be set on SIP participants created by a dispatch rule. Specifically, `metadata` set on a dispatch rule will be inherited by all SIP participants created by it.
The following examples add the metadata, `{"is_internal": true}`, to all SIP participants created from an inbound call by this dispatch rule:
**LiveKit CLI**:
```json
{
"dispatch_rule": {
"metadata": "{\"is_internal\": true}",
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
},
"name": "My dispatch rule"
}
}
```
---
**Node.js**:
For an executable example, replace `dispatchRuleOptions` in the [Direct dispatch rule](#direct-dispatch-rule) example with the following options:
```typescript
const dispatchRuleOptions = {
name: 'My invidividual dispatch rule',
metadata: "{\"is_internal\": true}",
};
```
---
**Python**:
For an executable example, replace `request` in the [Direct dispatch rule](#direct-dispatch-rule) example with the following options:
```python
request = api.CreateSIPDispatchRuleRequest(
dispatch_rule = api.SIPDispatchRuleInfo(
rule = rule,
name = 'My dispatch rule',
metadata = "{\"is_internal\": true}",
)
)
```
---
**Ruby**:
For an executable example, use the [Direct dispatch rule](#direct-dispatch-rule) example with the following options:
```ruby
resp = sip_service.create_sip_dispatch_rule(
rule,
name: name,
metadata: "{\"is_internal\": true}",
)
```
---
**Go**:
For an executable example, replace `request` in the [Direct dispatch rule](#direct-dispatch-rule) example with the following options:
```go
// Create a request
request := &livekit.CreateSIPDispatchRuleRequest{
DispatchRule: &livekit.SIPDispatchRuleInfo{
Rule: rule,
Name: "My dispatch rule",
Metadata: "{\"is_internal\": true}",
},
}
```
---
**Kotlin**:
For an executable example, modify the parameters for `CreateSipDispatchRuleOptions` in the [Direct dispatch rule](#direct-dispatch-rule) example to include the `metadata` parameter:
```kotlin
val response = sipClient.createSipDispatchRule(
rule = rule,
options = CreateSipDispatchRuleOptions(
name = "My dispatch rule",
metadata = "{\"is_internal\": true}"
)
).execute()
```
---
**LiveKit Cloud**:
1. Sign in to the **LiveKit Cloud** [dashboard](https://cloud.livekit.io/).
2. Select **Telephony** → [**Dispatch rules**](https://cloud.livekit.io/projects/p_/telephony/dispatch).
3. Select **Create new dispatch rule**.
4. Select the **JSON editor** tab.
> ℹ️ **Note**
>
> The `metadata` parameter is only available in the **JSON editor** tab.
5. Copy and paste the following text into the editor:
```json
{
"name": "My dispatch rule",
"metadata": "{\"is_internal\": true}",
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "call-"
}
}
}
```
6. Select **Create**.
## Update dispatch rule
Use the [`UpdateSIPDispatchRule`](https://docs.livekit.io/reference/telephony/sip-api.md#updatesipdispatchrule) API to update specific fields of a dispatch rule or [replace](#replace-dispatch-rule) a dispatch rule with a new one.
### Update specific fields of a dispatch rule
The `UpdateSIPDispatchRuleFields` API allows you to update specific fields of a dispatch rule without affecting other fields.
**LiveKit CLI**:
Create a file named `dispatch-rule.json` with the following content:
```json
{
"name": "My updated dispatch rule",
"rule": {
"dispatchRuleCallee": {
"roomPrefix": "number-",
"randomize": false,
"pin": "1234"
}
}
}
```
Update the dispatch rule using `lk`. You can update the `trunks` parameter to a comma-separated string of trunks IDs if the rule matches specific trunks.
```shell
lk sip dispatch update --id \
--trunks "[]" \
dispatch-rule.json
```
---
**Node.js**:
```typescript
import { ListUpdate } from '@livekit/protocol';
import { SipClient } from 'livekit-server-sdk';
const sipClient = new SipClient(process.env.LIVEKIT_URL,
process.env.LIVEKIT_API_KEY,
process.env.LIVEKIT_API_SECRET);
const updatedRuleFields = {
name: 'My updated dispatch rule',
trunkIds: new ListUpdate({ add: ["", ""] }), // Add trunk IDs to the dispatch rule
hidePhoneNumber: true,
metadata: "{\"is_internal\": false}",
}
const rule = await sipClient.updateSipDispatchRuleFields (
ruleId,
updatedRuleFields,
);
return rule;
```
---
**Python**:
```python
import asyncio
from livekit import api
from livekit.protocol.models import ListUpdate
async def main():
"""Use the update_sip_dispatch_rule_fields method to update specific fields of a dispatch rule."""
rule_id = ''
livekit_api = api.LiveKitAPI()
dispatchRule = None
try:
dispatchRule = await livekit_api.sip.update_sip_dispatch_rule_fields(
rule_id=rule_id,
trunk_ids=ListUpdate(add=["", ""]), # Add trunk IDs to the dispatch rule
metadata="{\"is_internal\": false}",
attributes={
"": "",
"": "",
}
)
print(f"Successfully updated {dispatchRule}")
except api.twirp_client.TwirpError as e:
print(f"{e.code} error: {e.message}")
await livekit_api.aclose()
return dispatchRule
asyncio.run(main())
```
---
**Ruby**:
The update API is not yet available in the Ruby SDK.
---
**Go**:
```go
package main
import (
"context"
"fmt"
"os"
"github.com/livekit/protocol/livekit"
lksdk "github.com/livekit/server-sdk-go/v2"
)
func main() {
rule_id := ""
// Update dispatch rule
name2 := "My updated dispatch rule"
request := &livekit.UpdateSIPDispatchRuleRequest{
SipDispatchRuleId: rule_id,
Action: &livekit.UpdateSIPDispatchRuleRequest_Update{
Update: &livekit.SIPDispatchRuleUpdate{
Name: &name2,
TrunkIds: &livekit.ListUpdate{
Set: []string{"", ""},
},
},
},
}
sipClient := lksdk.NewSIPClient(os.Getenv("LIVEKIT_URL"),
os.Getenv("LIVEKIT_API_KEY"),
os.Getenv("LIVEKIT_API_SECRET"))
updated, err := sipClient.UpdateSIPDispatchRule(context.Background(), request)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(updated)
}
}
```
---
**Kotlin**:
The following updates the dispatch rule created in the [Direct dispatch rule](#direct-dispatch-rule) example. To update an individual dispatch rule, pass in a `SIPDispatchRuleIndividual` object instead of a `SIPDispatchRuleDirect` object.
```kotlin
import io.livekit.server.SipServiceClient
import io.livekit.server.SIPDispatchRuleDirect
import io.livekit.server.UpdateSipDispatchRuleOptions
val sipClient = SipServiceClient.createClient(
host = System.getenv("LIVEKIT_URL").replaceFirst(Regex("^ws"), "http"),
apiKey = System.getenv("LIVEKIT_API_KEY"),
secret = System.getenv("LIVEKIT_API_SECRET")
)
val response = sipClient.updateSipDispatchRule(
sipDispatchRuleId = ,
options = UpdateSipDispatchRuleOptions(
name = "My updated dispatch rule",
metadata = "{'key1': 'value1', 'key2': 'value2'}",
rule = SipDispatchRuleDirect(
roomName = "new-room"
)
)).execute()
if (response.isSuccessful) {
val dispatchRule = response.body()
println("Dispatch rule updated: ${dispatchRule}")
}
```
---
**LiveKit Cloud**:
Update and replace functions are the same in the LiveKit Cloud dashboard. For an example, see the [replace dispatch rule](#replace-dispatch-rule) section.
### Replace dispatch rule
The `UpdateSIPDispatchRule` API allows you to replace an existing dispatch rule with a new one using the same dispatch rule ID.
**LiveKit CLI**:
The instructions for replacing a dispatch rule are the same as for [updating a dispatch rule](#update-specific-fields-of-a-dispatch-rule).
---
**Node.js**:
```typescript
import { SipClient } from 'livekit-server-sdk';
const sipClient = new SipClient(process.env.LIVEKIT_URL,
process.env.LIVEKIT_API_KEY,
process.env.LIVEKIT_API_SECRET);
async function replaceDispatchRule(ruleId) {
const updatedRuleOptions = {
name: 'My replaced dispatch rule',
trunkIds: ["", ""],
hidePhoneNumber: false,
metadata: "{\"is_internal\": true}",
rule: {
rule: {case: "dispatchRuleIndividual", value: individualRuleType},
}
};
const updatedRule = await sipClient.updateSipDispatchRule(
ruleId,
updatedRuleOptions,
);
return updatedRule;
}
await replaceDispatchRule('');
```
---
**Python**:
```python
import asyncio
from livekit import api
async def main():
"""Use the update_sip_dispatch_rule function to replace a dispatch rule."""
livekit_api = api.LiveKitAPI()
# Dispatch rule ID of rule to replace.
rule_id = ''
# Dispatch rule type.
rule = api.SIPDispatchRule(
dispatch_rule_direct = api.SIPDispatchRuleDirect(
room_name = "caller-room",
pin = '1212'
)
)
ruleInfo = api.SIPDispatchRuleInfo(
rule = rule,
name = 'My replaced dispatch rule',
trunk_ids = ["", ""],
hide_phone_number = True,
metadata = "{\"is_internal\": false}",
attributes = {
"": "",
"": "",
},
)
dispatchRule = None
try:
dispatchRule = await livekit_api.sip.update_sip_dispatch_rule(
rule_id,
ruleInfo
)
print(f"Successfully replaced {dispatchRule}")
except api.twirp_client.TwirpError as e:
print(f"{e.code} error: {e.message}")
await livekit_api.aclose()
return dispatchRule
asyncio.run(main())
```
---
**Ruby**:
The update API is not yet available in the Ruby SDK.
---
**Go**:
```go
package main
import (
"context"
"fmt"
"os"
"github.com/livekit/protocol/livekit"
lksdk "github.com/livekit/server-sdk-go/v2"
)
func main() {
rule_id := ""
// Replace dispatch rule
rule := &livekit.SIPDispatchRuleInfo{
Name: "My replaced dispatch rule",
TrunkIds: []string{"", ""},
Rule: &livekit.SIPDispatchRule{
Rule: &livekit.SIPDispatchRule_DispatchRuleDirect{
DispatchRuleDirect: &livekit.SIPDispatchRuleDirect{
RoomName: "my-room",
},
},
},
}
request := &livekit.UpdateSIPDispatchRuleRequest{
SipDispatchRuleId: rule_id,
Action: &livekit.UpdateSIPDispatchRuleRequest_Replace{
Replace: rule,
},
}
sipClient := lksdk.NewSIPClient(os.Getenv("LIVEKIT_URL"),
os.Getenv("LIVEKIT_API_KEY"),
os.Getenv("LIVEKIT_API_SECRET"))
updated, err := sipClient.UpdateSIPDispatchRule(context.Background(), request)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(updated)
}
}
```
---
**Kotlin**:
Replacing a dispatch rule is not supported in Kotlin.
---
**LiveKit Cloud**:
1. Sign in to the **LiveKit Cloud** [dashboard](https://cloud.livekit.io/).
2. Select **Telephony** → [**Dispatch rules**](https://cloud.livekit.io/projects/p_/telephony/dispatch).
3. Navigate to the **Dispatch rules** section and find the dispatch rule you want to update.
4. Select the more (**⋮**) menu → select **Edit**.
5. Select the **JSON editor** tab and copy and paste the following text into the editor:
```json
{
"name": "My replaced dispatch rule",
"rule": {
"dispatchRuleIndividual": {
"roomPrefix": "caller-room"
}
},
"trunkIds": ["", ""],
"hidePhoneNumber": false,
"metadata": "{\"is_internal\": true}",
"attributes": {
"": "",
"": "",
}
}
```
6. Select **Update**.
## List dispatch rules
Use the [`ListSIPDispatchRule`](https://docs.livekit.io/reference/telephony/sip-api.md#listsipdispatchrule) API to list all dispatch rules.
**LiveKit CLI**:
```shell
lk sip dispatch list
```
---
**Node.js**:
```typescript
import { SipClient } from 'livekit-server-sdk';
const sipClient = new SipClient(process.env.LIVEKIT_URL,
process.env.LIVEKIT_API_KEY,
process.env.LIVEKIT_API_SECRET);
const rules = await sipClient.listSipDispatchRule();
console.log(rules);
```
---
**Python**:
```python
import asyncio
from livekit import api
async def main():
livekit_api = api.LiveKitAPI()
rules = await livekit_api.sip.list_sip_dispatch_rule(
api.ListSIPDispatchRuleRequest()
)
print(f"{rules}")
await livekit_api.aclose()
asyncio.run(main())
```
---
**Ruby**:
```ruby
require 'livekit'
sip_service = LiveKit::SIPServiceClient.new(
ENV['LIVEKIT_URL'],
api_key: ENV['LIVEKIT_API_KEY'],
api_secret: ENV['LIVEKIT_API_SECRET']
)
resp = sip_service.list_sip_dispatch_rule()
puts resp.data
```
---
**Go**:
```go
package main
import (
"context"
"fmt"
"os"
lksdk "github.com/livekit/server-sdk-go/v2"
"github.com/livekit/protocol/livekit"
)
func main() {
sipClient := lksdk.NewSIPClient(os.Getenv("LIVEKIT_URL"),
os.Getenv("LIVEKIT_API_KEY"),
os.Getenv("LIVEKIT_API_SECRET"))
// List dispatch rules
dispatchRules, err := sipClient.ListSIPDispatchRule(
context.Background(), &livekit.ListSIPDispatchRuleRequest{})
if err != nil {
fmt.Println(err)
} else {
fmt.Println(dispatchRules)
}
}
```
---
**Kotlin**:
```kotlin
import livekit.LivekitSip
import io.livekit.server.SipServiceClient
val sipClient = SipServiceClient.createClient(
host = System.getenv("LIVEKIT_URL").replaceFirst(Regex("^ws"), "http"),
apiKey = System.getenv("LIVEKIT_API_KEY"),
secret = System.getenv("LIVEKIT_API_SECRET")
)
val response = sipClient.listSipDispatchRule().execute()
if (response.isSuccessful) {
val dispatchRules = response.body()
println("Number of dispatch rules: ${dispatchRules?.size}")
}
```
---
**LiveKit Cloud**:
1. Sign in to the **LiveKit Cloud** [dashboard](https://cloud.livekit.io/).
2. Select **Telephony** → [**Dispatch rules**](https://cloud.livekit.io/projects/p_/telephony/dispatch).
3. The **Dispatch rules** section lists all dispatch rules.
---
This document was rendered at 2026-02-03T03:25:12.579Z.
For the latest version of this document, see [https://docs.livekit.io/telephony/accepting-calls/dispatch-rule.md](https://docs.livekit.io/telephony/accepting-calls/dispatch-rule.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/self-hosting/distributed.md
LiveKit docs › Self-hosting › Distributed multi-region
---
# Distributed multi-region
> LiveKit is architected to be distributed, with homogeneous instances running across many servers. In distributed mode, Redis is required as shared data store and message bus.
## Multi-node routing
When Redis is configured, LiveKit automatically switches to a distributed setup by using Redis for room data as well as a message bus. In this mode, each node periodically reports their stats to Redis; this enables them to be aware of the entire cluster and make routing decisions based on availability and load. We recommend this setup for a redundant deployment.
When a new room is created, the node that received this request is able to choose an available node from the cluster to host the room.
When a client establishes a signal connection to LiveKit, it creates a persistent WebSocket connection with one of the instances. That instance will then acts as a signaling bridge, proxying messages between the node where the room is hosted and the client.
In a multi-node setup, LiveKit can support a large number of concurrent rooms. However, there are limits to the number of participants in a room since, for now, a room must fit on a single node.
## Downscaling and draining
It's simple to scale up instances, but what about scaling down? Terminating an instance while it's hosting active sessions would be extremely disruptive to the end user.
LiveKit solves this problem by providing connection draining natively. When it receives a request to terminate (via `SIGTERM`, `SIGINT`, or `SIGQUIT`) and there are participants currently connected, it will put itself into draining mode. While draining, the instance would:
- allow active rooms to run as usual
- accept traffic for new participants to active rooms
- reject participants trying to join new rooms
When all participants have disconnected, the server will complete draining and shut down.
## Multi-region support
It's possible to deploy LiveKit to multiple data centers, allowing users located in different regions to connect to a server that's closest to them.
LiveKit supports this via a [region-aware, load aware node selector](https://github.com/livekit/livekit/blob/master/pkg/routing/selector/regionaware.go). It's designed to be used in conjunction with region-aware load balancing of the signal connection.
Here's how it works:
1. Geo or latency aware DNS service (such as Route53 or Cloudflare) returns IP of load balancer closest to the user
2. User connects load balancer in that region
3. Then connects to an instance of LiveKit in that region
4. If the room doesn't already exist, LiveKit will use node selector to choose an available node
5. The selection criteria is- node must have lower utilization than `sysload_limit`
- nodes are in the region closest to the signaling instance
- a node satisfying the above is chosen at random
### Configuration
```yaml
node_selector:
kind: regionaware
sysload_limit: 0.5
# List of regions and their lat/lon coordinates
regions:
- name: us-west-2
lat: 37.64046607830567
lon: -120.88026233189062
- name: us-east
lat: 40.68914362140307
lon: -74.04445748616385
```
---
This document was rendered at 2026-02-03T03:25:20.903Z.
For the latest version of this document, see [https://docs.livekit.io/transport/self-hosting/distributed.md](https://docs.livekit.io/transport/self-hosting/distributed.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/telephony/features/dtmf.md
LiveKit docs › Features › DTMF
---
# Handling DTMF
> Sending and receiving DTMF tones.
## Overview
LiveKit's telephony stack fully supports Dual-tone Multi-Frequency (DTMF) tones, enabling integration with legacy Interactive Voice Response (IVR) systems. It also enables agents to receive DTMF tones from telephone users.
## Agents framework support
If you're building telephony apps with the LiveKit Agents framework, there are additional features that provide support for DTMF:
- The `ivr_detection` option for [`AgentSession`](https://docs.livekit.io/agents/logic/sessions.md#session-options). When set to `True`, this automatically makes use of built-in tools to detect IVR systems and relay DTMF tones from the user back to the telephony provider.
To enable IVR detection, set `ivr_detection=True` in the `AgentSession` constructor:
```python
session = AgentSession(
ivr_detection=True,
# ... stt, llm, vad, turn_detection, etc.
)
```
- A prebuilt task for collecting DTMF inputs. It can be used to collect any number of digits from a caller, including, for example, a phone number or credit card number. The task supports both DTMF tones and spoken digits. To learn more, see [GetDtmfTask](https://docs.livekit.io/agents/logic/tasks.md#getdtmftask).
## Sending DTMF using the API
To send DTMF tones, use the `publishDtmf` API on the `localParticipant`.
Any participant in the room can use the `publishDtmf` API to transmit DTMF tones to the room. SIP participants in the room receive the tones and relay them to the telephone user.
The `publishDtmf` API requires two parameters:
- `code`: DTMF code
- `digit`: DTMF digit
The following examples publishes the DTMF tones `1`, `2`, `3`, and `#` in sequence.
**Node.js**:
```typescript
// publishes 123# in DTMF
await localParticipant.publishDtmf(1, '1');
await localParticipant.publishDtmf(2, '2');
await localParticipant.publishDtmf(3, '3');
await localParticipant.publishDtmf(11, '#');
```
---
**Python**:
```python
# publishes 123# in DTMF
await local_participant.publish_dtmf(code=1, digit='1')
await local_participant.publish_dtmf(code=2, digit='2')
await local_participant.publish_dtmf(code=3, digit='3')
await local_participant.publish_dtmf(code=11, digit='#')
```
---
**Go**:
```go
import (
"github.com/livekit/protocol/livekit"
)
// publishes 123# in DTMF
localParticipant.PublishDataPacket(&livekit.SipDTMF{
Code: 1,
Digit: "1",
})
localParticipant.PublishDataPacket(&livekit.SipDTMF{
Code: 2,
Digit: "2",
})
localParticipant.PublishDataPacket(&livekit.SipDTMF{
Code: 3,
Digit: "3",
})
localParticipant.PublishDataPacket(&livekit.SipDTMF{
Code: 11,
Digit: "#",
})
```
> ℹ️ **Info**
>
> Sending DTMF tones requires both a numeric code and a string representation to ensure compatibility with various SIP implementations.
>
> Special characters like `*` and `#` are mapped to their respective numeric codes. See [RFC 4733](https://datatracker.ietf.org/doc/html/rfc4733#section-3.2) for details.
## Receiving DTMF by listening to events
When SIP receives DTMF tones, they are relayed to the room as events that participants can listen for.
**Node.js**:
```typescript
room.on(RoomEvent.DtmfReceived, (code, digit, participant) => {
console.log('DTMF received from participant', participant.identity, code, digit);
});
```
---
**Python**:
```python
@room.on("sip_dtmf_received")
def dtmf_received(dtmf: rtc.SipDTMF):
logging.info(f"DTMF received from {dtmf.participant.identity}: {dtmf.code} / {dtmf.digit}")
```
---
**Go**:
```go
import (
"fmt"
"github.com/livekit/protocol/livekit"
lksdk "github.com/livekit/server-sdk-go/v2"
)
func DTMFCallbackExample() {
// Create a new callback handler
cb := lksdk.NewRoomCallback()
// Handle data packets received from other participants
cb.OnDataPacket = func(data lksdk.DataPacket, params lksdk.DataReceiveParams) {
// handle DTMF
switch val := data.(type) {
case *livekit.SipDTMF:
fmt.Printf("Received DTMF from %s: %s (%d)\n", params.SenderIdentity, val.Digit, val.Code)
}
}
room := lksdk.NewRoom(cb)
...
}
```
---
This document was rendered at 2026-02-03T03:25:11.008Z.
For the latest version of this document, see [https://docs.livekit.io/telephony/features/dtmf.md](https://docs.livekit.io/telephony/features/dtmf.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/self-hosting/egress.md
# Source: https://docs.livekit.io/transport/media/ingress-egress/egress.md
LiveKit docs › Media › Stream export & import › Egress › Overview
---
# Egress overview
> Use LiveKit's Egress service to record or livestream a room.
## Overview
LiveKit Egress gives you a powerful and consistent set of APIs to export any room or individual tracks from a LiveKit session. It supports recording to an MP4 file or HLS segments, as well as exporting to live streaming services like YouTube Live, Twitch, and Facebook via RTMP.
For LiveKit Cloud customers, egress is available for your project without any additional configuration. If you're self-hosting LiveKit, egress must be [deployed](https://docs.livekit.io/transport/self-hosting/egress.md) separately.
## Egress types
The Egress service supports multiple types of exports for different use cases. The table below lists the different egress components and their descriptions.
| Egress type | Description | Use cases |
| **RoomComposite egress** | Export an entire room's video and/or audio using a web layout rendered by Chrome. Tied to a room's lifecycle and stops automatically when the room ends. Composition templates are customizable web pages that can be hosted anywhere. | Recording meetings for team members to watch later, capturing all participants and interactions in a room. |
| **Web egress** | Record and export any web page. Similar to room composite egress, but isn't tied to a LiveKit room and can record non-LiveKit content. | Restreaming content from a third-party source to YouTube and Twitch, recording external web applications. |
| **Participant egress** | Export a participant's video and audio together. A newer API designed to be easier to use than Track Composite Egress. | Recording individual participants in online classes, capturing a specific speaker's video and audio. |
| **TrackComposite egress** | Sync and export one audio and one video track together. Transcoding and multiplexing happen automatically. | Exporting audio and video from multiple cameras during production for post-production use, combining specific tracks. |
| **Track egress** | Export individual tracks directly without transcoding. Video tracks are exported as-is. | Streaming audio tracks to captioning services via WebSocket, exporting raw track data for processing. |
| **Auto egress** | Automatically start recording when a room is created. Configure the `egress` field in `CreateRoom` to record the room as a composite and each published track separately. | Recording all rooms automatically, capturing every track published to a room without manual intervention. |
## Service architecture
Depending on your request type, the Egress service either launches a web template in Chrome and connects to the room (for example, for room composite requests), or it uses the SDK directly (for track and track composite requests). It uses GStreamer to encode, and can output to a file or to one or more streams.

## Additional resources
The following topics provide more in-depth information about the various egress types.
- **[Room composite and web egress](https://docs.livekit.io/transport/media/ingress-egress/egress/composite-recording.md)**: Composite recording using a web-based recorder. Export an entire room or any web page.
- **[Participant and track composite egress](https://docs.livekit.io/transport/media/ingress-egress/egress/participant.md)**: Record a participant's audio and video tracks. Use TrackComposite egress for fine-grained control over tracks.
- **[Track egress](https://docs.livekit.io/transport/media/ingress-egress/egress/track.md)**: Export a single track without transcoding.
- **[Auto egress](https://docs.livekit.io/transport/media/ingress-egress/egress/autoegress.md)**: Automatically start recording when a room is created.
- **[Output and stream types](https://docs.livekit.io/transport/media/ingress-egress/egress/outputs.md)**: Sync and export one audio and one video track together. Transcoding and multiplexing happen automatically.
---
This document was rendered at 2026-02-03T03:25:17.019Z.
For the latest version of this document, see [https://docs.livekit.io/transport/media/ingress-egress/egress.md](https://docs.livekit.io/transport/media/ingress-egress/egress.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/tts/plugins/elevenlabs.md
# Source: https://docs.livekit.io/agents/models/tts/inference/elevenlabs.md
# Source: https://docs.livekit.io/agents/models/stt/plugins/elevenlabs.md
# Source: https://docs.livekit.io/agents/models/stt/inference/elevenlabs.md
LiveKit docs › Models › STT › Inference › ElevenLabs
---
# ElevenLabs STT
> Reference for ElevenLabs STT in LiveKit Inference.
## Overview
LiveKit Inference offers transcription powered by ElevenLabs. Pricing information is available on the [pricing page](https://livekit.io/pricing/inference#stt).
| Model name | Model ID | Languages |
| -------- | -------- | --------- |
| Scribe V2 Realtime | `elevenlabs/scribe_v2_realtime` | `en`, `en-US`, `en-GB`, `en-AU`, `en-CA`, `en-IN`, `en-NZ`, `es`, `es-ES`, `es-MX`, `es-AR`, `es-CO`, `es-CL`, `es-PE`, `es-VE`, `es-EC`, `es-GT`, `es-CU`, `es-BO`, `es-DO`, `es-HN`, `es-PY`, `es-SV`, `es-NI`, `es-CR`, `es-PA`, `es-UY`, `es-PR`, `fr`, `fr-FR`, `fr-CA`, `fr-BE`, `fr-CH`, `de`, `de-DE`, `de-AT`, `de-CH`, `it`, `it-IT`, `it-CH`, `pt`, `pt-BR`, `pt-PT`, `pl`, `pl-PL`, `ru`, `ru-RU`, `ja`, `ja-JP`, `zh`, `zh-CN`, `zh-TW`, `zh-HK`, `ko`, `ko-KR`, `ar`, `ar-SA`, `ar-EG`, `ar-AE`, `ar-IQ`, `ar-DZ`, `ar-MA`, `ar-KW`, `ar-JO`, `ar-LB`, `ar-OM`, `ar-QA`, `ar-BH`, `ar-TN`, `ar-YE`, `ar-SY`, `ar-SD`, `ar-LY`, `ar-MR`, `ar-SO`, `ar-DJ`, `ar-KM`, `ar-ER`, `ar-TD`, `hi`, `hi-IN`, `tr`, `tr-TR`, `nl`, `nl-NL`, `nl-BE`, `sv`, `sv-SE`, `id`, `id-ID`, `cs`, `cs-CZ`, `ro`, `ro-RO`, `hu`, `hu-HU`, `fi`, `fi-FI`, `da`, `da-DK`, `no`, `no-NO`, `th`, `th-TH`, `vi`, `vi-VN`, `uk`, `uk-UA`, `el`, `el-GR`, `he`, `he-IL`, `ms`, `ms-MY`, `sk`, `sk-SK`, `hr`, `hr-HR`, `bg`, `bg-BG`, `sr`, `sr-RS`, `sl`, `sl-SI`, `et`, `et-EE`, `lv`, `lv-LV`, `lt`, `lt-LT`, `is`, `is-IS`, `ga`, `ga-IE`, `mt`, `mt-MT`, `cy`, `cy-GB` |
## Usage
To use ElevenLabs, pass a descriptor with the model and language to the `stt` argument in your `AgentSession`:
**Python**:
```python
from livekit.agents import AgentSession
session = AgentSession(
stt="elevenlabs/scribe_v2_realtime:en",
# ... tts, stt, vad, turn_detection, etc.
)
```
---
**Node.js**:
```typescript
import { AgentSession } from '@livekit/agents';
session = new AgentSession({
stt: "elevenlabs/scribe_v2_realtime:en",
// ... tts, stt, vad, turn_detection, etc.
});
```
### Multilingual transcription
ElevenLabs Scribe 2 Realtime supports multilingual transcription for over 90 languages with automatic language detection.
### Parameters
To customize additional parameters, including the language to use, use the `STT` class from the `inference` module:
```python
from livekit.agents import AgentSession, inference
session = AgentSession(
stt=inference.STT(
model="elevenlabs/scribe_v2_realtime",
language="en"
),
# ... tts, stt, vad, turn_detection, etc.
)
```
- **`model`** _(string)_: The model to use for the STT.
- **`language`** _(string)_ (optional): Language code for the transcription.
- **`extra_kwargs`** _(dict)_ (optional): Additional parameters to pass to the ElevenLabs STT API. For available parameters, see [provider's documentation](https://elevenlabs.io/docs/api-reference/speech-to-text/v-1-speech-to-text-realtime).
## Additional resources
The following links provide more information about Deepgram in LiveKit Inference.
- **[ElevenLabs Plugin](https://docs.livekit.io/agents/models/stt/plugins/elevenlabs.md)**: Plugin to use your own ElevenLabs account instead of LiveKit Inference.
- **[ElevenLabs docs](https://elevenlabs.io/docs/capabilities/speech-to-text)**: ElevenLabs STT API documentation.
---
This document was rendered at 2026-02-03T03:25:02.781Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/stt/inference/elevenlabs.md](https://docs.livekit.io/agents/models/stt/inference/elevenlabs.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/media/ingress-egress/ingress/encoders.md
LiveKit docs › Media › Stream export & import › Ingress › Encoder configuration
---
# Encoder configuration
> How to configure streaming software to work with LiveKit Ingress.
The `IngressInfo` object returned by most Ingress APIs contains a full list of the ingress parameters. In particular, the `url` and `stream_key` fields provide the settings required to configure encoders to send media to the Ingress service. Refer to the documentation of any RTMP or WHIP-capable streaming software for more information about how to provide these parameters. Two common examples are OBS and FFmpeg:
## OBS
The [OBS Project](https://obsproject.com/) releases OBS Studio, a powerful cross platform broadcasting software that can be fully configured through a graphical user interface, and capable of sending complex video compositions to LiveKit WebRTC via Ingress. In order to configure OBS for LiveKit, in the main window, select the `Settings` option, and then the `Stream` tab. In the window, select the `Custom...` Service and enter the URL from the `StreamInfo` in the `Server` field, and the stream key in the `Stream Key` field.

## FFmpeg
[FFmpeg](https://ffmpeg.org/) is a powerful media processing command-line tool that can be used to stream media to LiveKit Ingress. The following command can be used for that purpose:
```shell
% ffmpeg -re -i -c:v libx254 -b:v 3M -preset veryfast -profile high -c:a libfdk_aac -b:a 128k -f flv "/"
```
For instance:
```shell
% ffmpeg -re -i my_file.mp4 -c:v libx264 -b:v 3M -preset veryfast -profile:v high -c:a libfdk_aac -b:a 128k -f flv rtmps://my-project.livekit.cloud/x/1234567890ab
```
Refer to the [FFmpeg documentation](https://ffmpeg.org/ffmpeg.html) for a list of the supported inputs, and how to use them.
## GStreamer
[GStreamer](https://gstreamer.freedesktop.org/) is multi platform multimedia framework that can be used either directly using command line tools provided as part of the distribution, or integrated in other applications using their API. GStreamer supports streaming media to LiveKit Ingress both over RTMP and WHIP.
For RTMP, the following sample command and pipeline definition can be used:
```shell
% gst-launch-1.0 flvmux name=mux ! rtmp2sink location="/" audiotestsrc wave=sine-table ! faac ! mux. videotestsrc is-live=true ! video/x-raw,width=1280,height=720 ! x264enc speed-preset=3 tune=zerolatency ! mux.
```
WHIP requires the following GStreamer plugins to be installed:
- nicesink
- webrtcbin
- whipsink
Some these plugins are distributed as part of [libnice](https://libnice.freedesktop.org) or the [Rust GStreamer plugins package](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs) and may not always be present. This can be verified using the `gst-inspect-1.0` command. LiveKit provides a Docker image based on Ubuntu that includes all the required GStreamer plugins at [livekit/gstreamer:1.22.8-prod-rs](https://hub.docker.com/layers/livekit/gstreamer/1.22.8-prod-rs/images/sha256-1a4d7ef428875550400430a57acf0759f1cb02771dbac2501b2d3fbe2f1ce74e?context=explore).
```shell
gst-launch-1.0 audiotestsrc wave=sine-table ! opusenc ! rtpopuspay ! 'application/x-rtp,media=audio,encoding-name=OPUS,payload=96,clock-rate=48000,encoding-params=(string)2' ! whip.sink_0 videotestsrc is-live=true ! video/x-raw,width=1280,height=720 ! x264enc speed-preset=3 tune=zerolatency ! rtph264pay ! 'application/x-rtp,media=video,encoding-name=H264,payload=97,clock-rate=90000' ! whip.sink_1 whipsink name=whip whip-endpoint="/"
```
These 2 sample command lines use the `audiotestsrc` and `videotestsrc` sources to generate test audio and video pattern. These can be replaced with other GStreamer sources to stream any media supported by GStreamer.
---
This document was rendered at 2026-02-03T03:25:18.278Z.
For the latest version of this document, see [https://docs.livekit.io/transport/media/ingress-egress/ingress/encoders.md](https://docs.livekit.io/transport/media/ingress-egress/ingress/encoders.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/encryption.md
LiveKit docs › Encryption › Overview
---
# Encryption overview
> Secure your realtime media and data with end-to-end encryption.
## Overview
LiveKit includes built-in support for end-to-end encryption (E2EE) for both realtime media tracks (audio and video) and data channels (text and byte streams). With E2EE enabled, content remains fully encrypted from sender to receiver, ensuring that no intermediaries (including LiveKit servers) can access or modify the content. This feature is:
- Available for both self-hosted and LiveKit Cloud customers at no additional cost.
- Ideal for regulated industries and security-critical applications.
- Designed to provide an additional layer of protection beyond standard transport encryption.
> ℹ️ **Security is our highest priority**
>
> Learn more about [our comprehensive approach to security](https://livekit.io/security).
## Encryption components
LiveKit provides end-to-end encryption for both media and data:
| Component | Description | Use cases |
| **Media encryption** | Encrypts all audio and video tracks from all participants in a room, ensuring no intermediaries can access the content. | Regulated industries, security-critical applications, and privacy-focused use cases. |
| **Data channel encryption** | Encrypts all text messages, byte streams, and data packets sent between participants. | Secure chat applications, private file sharing, and encrypted data exchange. |
## How E2EE works
E2EE is enabled at the room level and automatically applied to all media tracks and data channels from all participants in that room. You must enable it within the LiveKit SDK for each participant. In many cases you can use a built-in key provider with a single shared key for the whole room. If you require unique keys for each participant, or key rotation during the lifetime of a single room, you can implement your own key provider.
## Key distribution
It is your responsibility to securely generate, store, and distribute encryption keys to your application at runtime. LiveKit does not (and cannot) store or transport encryption keys for you.
If using a shared key, you would typically generate it on your server at the same time that you create a room and distribute it securely to participants alongside their access token for the room. When using unique keys per participant, you may need a more sophisticated method for distributing keys as new participants join the room. Remember that the key is needed for both encryption and decryption, so even when using per-participant keys, you must ensure that all participants have all keys.
## Media encryption
E2EE is enabled at the room level and automatically applied to all media tracks from all participants in that room. You must enable it within the LiveKit SDK for each participant.
## Data channel encryption
Realtime data and text are encrypted using the `encryption` field for `RoomOptions` when you create a room. When the `encryption` field is set, all outgoing data messages (including text and byte streams) are end-to-end encrypted.
End-to-end encryption for data channel messages is the default. However, for backwards compatibility, the `e2ee` field is still supported. If `encryption` is not set, data channel messages are _not_ encrypted.
> ℹ️ **e2ee field is deprecated**
>
> The `e2ee` field is deprecated and will be removed in the next major version of each client SDK. Use the `encryption` field instead.
> ❗ **Signaling messages and APIs**
>
> Signaling messages (control messages used to coordinate a WebRTC session) and API calls are _not_ end-to-end encrypted—they're encrypted in transit using TLS, but the LiveKit server can still read them.
## In this section
Learn how to implement end-to-end encryption in your applications.
- **[Get started](https://docs.livekit.io/transport/encryption/start.md)**: Learn how to implement E2EE with step-by-step guides and code examples for all platforms.
---
This document was rendered at 2026-02-03T03:25:19.828Z.
For the latest version of this document, see [https://docs.livekit.io/transport/encryption.md](https://docs.livekit.io/transport/encryption.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# 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,
#[serde(default)]
participant_name: Option,
#[serde(default)]
participant_identity: Option,
#[serde(default)]
participant_metadata: Option,
#[serde(default)]
participant_attributes: HashMap,
#[serde(default)]
room_config: Option,
}
#[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 {
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 (
)
}
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 (
)
})}
>
)
}
```
---
**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 (
{/* render the rest of your application here */}
)
}
```
---
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).
---
# Source: https://docs.livekit.io/transport/media/enhanced-noise-cancellation.md
LiveKit docs › Media › Enhanced noise cancellation
---
# Enhanced noise cancellation
> LiveKit Cloud offers AI-powered noise cancellation for realtime audio.
## Overview
LiveKit Cloud includes advanced models licensed from [Krisp](https://krisp.ai/) to remove background noise and ensure the best possible audio quality. The models run locally, with no audio data sent to Krisp servers as part of this process and negligible impact on audio latency or quality.
The feature includes a background voice cancellation (BVC) model, which removes extra background speakers in addition to background noise, providing the best possible experience for voice AI applications. You can also use the standard NC model if desired.
The following comparison shows the effect of the models on the audio as perceived by a user, and also as perceived by a voice AI agent running an STT model ([Deepgram Nova 3](https://docs.livekit.io/agents/models/stt/inference/deepgram.md) in these samples). The segments marked with a strikethrough indicate unwanted content that would confuse the agent. These samples illustrate that BVC is necessary to achieve clean STT in noisy multi-speaker environments.
Try the free [noise canceller tool](https://github.com/livekit-examples/noise-canceller) with your LiveKit Cloud account to test your own audio samples.
## Supported platforms
You can apply the filter in the frontend ("outbound") with plugins for JavaScript, Swift, and Android, or directly inside of your agent code ("inbound"). The BVC model is available only within your agent, using the Python or Node.js plugins. LiveKit also offers an NC model for SIP-based telephony, which can be enabled with a flag in the trunk configuration.
The following table shows the support for each platform.
| Platform | Outbound | Inbound | BVC | Package |
| Web | ✅ | ❌ | ❌ | [@livekit/krisp-noise-filter](https://www.npmjs.com/package/@livekit/krisp-noise-filter) |
| Swift | ✅ | ❌ | ❌ | [LiveKitKrispNoiseFilter](https://github.com/livekit/swift-krisp-noise-filter) |
| Android | ✅ | ❌ | ❌ | [io.livekit:krisp-noise-filter](https://central.sonatype.com/artifact/io.livekit/krisp-noise-filter) |
| Flutter | ✅ | ❌ | ❌ | [livekit_noise_filter](https://pub.dev/packages/livekit_noise_filter) |
| React Native | ✅ | ❌ | ❌ | [@livekit/react-native-krisp-noise-filter](https://www.npmjs.com/package/@livekit/react-native-krisp-noise-filter) |
| Unity | ❌ | ❌ | ❌ | N/A |
| Python | ❌ | ✅ | ✅ | [livekit-plugins-noise-cancellation](https://pypi.org/project/livekit-plugins-noise-cancellation/) |
| Node.js | ❌ | ✅ | ✅ | [@livekit/noise-cancellation-node](https://www.npmjs.com/package/@livekit/noise-cancellation-node) |
| Telephony | ✅ | ✅ | ❌ | [LiveKit telephony documentation](https://docs.livekit.io/transport/media/enhanced-noise-cancellation.md#telephony) |
## Usage instructions
Use the following instructions to integrate the filter into your app, either inside of your agent code or in the frontend.
> 💡 **Tip**
>
> Leaving default settings on is strongly recommended. Learn more about these defaults in the [Noise & echo cancellation](https://docs.livekit.io/transport/media/noise-cancellation.md) docs.
### LiveKit Agents
The following examples show how to set up noise cancellation inside your agent code. This applies noise cancellation to inbound audio and is the recommended approach for most voice AI use cases.
> 💡 **Tip**
>
> When using noise or background voice cancellation in the agent code, do not enable Krisp noise cancellation in the frontend. Noise cancellation models are trained on raw audio and might produce unexpected results if the input has already been processed by Krisp in the frontend.
>
> Standard noise cancellation and the separate echo cancellation feature can be left enabled.
#### Installation
Install the noise cancellation plugin:
**Python**:
```shell
uv add "livekit-plugins-noise-cancellation~=0.2"
```
---
**Node.js**:
```shell
pnpm add @livekit/noise-cancellation-node
```
#### Usage
Include the filter in the room input options when starting your agent session:
**Python**:
```python
from livekit.plugins import noise_cancellation
from livekit.agents import room_io
# ...
await session.start(
# ...,
room_options=room_io.RoomOptions(
audio_input=room_io.AudioInputOptions(
noise_cancellation=noise_cancellation.BVC(),
),
),
)
# ...
```
---
**Node.js**:
```typescript
import { BackgroundVoiceCancellation } from '@livekit/noise-cancellation-node';
// ...
await session.start({
// ...,
inputOptions: {
noiseCancellation: BackgroundVoiceCancellation(),
},
});
// ...
```
#### Usage with AudioStream
Apply the filter to any individual inbound AudioStream:
**Python**:
```python
from livekit.rtc import AudioStream
from livekit.plugins import noise_cancellation
stream = AudioStream.from_track(
track=track,
noise_cancellation=noise_cancellation.BVC(),
)
```
---
**Node.js**:
```typescript
import { BackgroundVoiceCancellation } from '@livekit/noise-cancellation-node';
import { AudioStream } from '@livekit/rtc-node';
const stream = new AudioStream(track, {
noiseCancellation: BackgroundVoiceCancellation(),
});
```
#### Available models
There are three noise cancellation models available:
**Python**:
```python
# Standard enhanced noise cancellation
noise_cancellation.NC()
# Background voice cancellation (NC + removes non-primary voices
# that would confuse transcription or turn detection)
noise_cancellation.BVC()
# Background voice cancellation optimized for telephony applications
noise_cancellation.BVCTelephony()
```
---
**Node.js**:
```typescript
import {
// Standard enhanced noise cancellation
NoiseCancellation,
// Background voice cancellation (NC + removes non-primary voices
// that would confuse transcription or turn detection)
BackgroundVoiceCancellation,
// Background voice cancellation optimized for telephony applications
TelephonyBackgroundVoiceCancellation,
} from '@livekit/noise-cancellation-node';
```
### Telephony
Noise cancellation can be applied directly at your SIP trunk for inbound or outbound calls. This uses the standard noise cancellation (NC) model. Other models are not available for SIP.
#### Inbound
Include `krisp_enabled: true` in the inbound trunk configuration.
```json
{
"trunk": {
"name": "My trunk",
"numbers": ["+15105550100"],
"krisp_enabled": true
}
}
```
See the full [inbound trunk docs](https://docs.livekit.io/telephony/accepting-calls/inbound-trunk.md) for more information.
#### Outbound
Include `krisp_enabled: true` in the [`CreateSipParticipant`](https://docs.livekit.io/reference/telephony/sip-api.md#createsipparticipant) request.
```python
request = CreateSIPParticipantRequest(
sip_trunk_id = "",
sip_call_to = "",
room_name = "my-sip-room",
participant_identity = "sip-test",
participant_name = "Test Caller",
krisp_enabled = True,
wait_until_answered = True
)
```
See the full [outbound call docs](https://docs.livekit.io/telephony/making-calls.md) for more information.
### Frontend
The following examples show how to set up noise cancellation in the frontend. This applies noise cancellation to outbound audio.
**JavaScript**:
> 💡 **Tip**
>
> When using noise or background voice cancellation in the frontend, do not enable Krisp noise cancellation in the agent code.
>
> Standard noise cancellation and the separate echo cancellation feature can be left enabled.
#### Installation
```shell
npm install @livekit/krisp-noise-filter
```
This package includes the Krisp SDK but not the models, which downloads at runtime to minimize the impact on your application's bundle size.
#### React components usage
LiveKit Components includes a convenient [`useKrispNoiseFilter`](https://docs.livekit.io/reference/components/react/hook/usekrispnoisefilter.md) hook to easily integrate Krisp into your React app:
```tsx
import { useKrispNoiseFilter } from '@livekit/components-react/krisp';
function MyKrispSetting() {
const krisp = useKrispNoiseFilter();
return (
krisp.setNoiseFilterEnabled(ev.target.checked)}
checked={krisp.isNoiseFilterEnabled}
disabled={krisp.isNoiseFilterPending}
/>
);
}
```
#### Base JS SDK usage
For other frameworks or advanced use cases, use the `KrispNoiseFilter` class directly:
```ts
import { type LocalAudioTrack, Room, RoomEvent, Track } from 'livekit-client';
const room = new Room();
// We recommend a dynamic import to only load the required resources when you enable the plugin
const { KrispNoiseFilter } = await import('@livekit/krisp-noise-filter');
room.on(RoomEvent.LocalTrackPublished, async (trackPublication) => {
if (
trackPublication.source === Track.Source.Microphone &&
trackPublication.track instanceof LocalAudioTrack
) {
if (!isKrispNoiseFilterSupported()) {
console.warn('Krisp noise filter is currently not supported on this browser');
return;
}
// Once instantiated, the filter will begin initializing and will download additional resources
const krispProcessor = KrispNoiseFilter();
console.log('Enabling LiveKit Krisp noise filter');
await trackPublication.track.setProcessor(krispProcessor);
// To enable/disable the noise filter, use setEnabled()
await krispProcessor.setEnabled(true);
// To check the current status use:
// krispProcessor.isEnabled()
// To stop and dispose of the Krisp processor, simply call:
// await trackPublication.track.stopProcessor()
}
});
```
#### Available models
The JavaScript noise filter supports only the standard noise cancellation (NC) model.
#### Compatibility
Not all browsers support the underlying Krisp SDK (including Safari <17.4). Use `isKrispNoiseFilterSupported()` to check if the current browser is supported.
---
**Android**:
> 💡 **Tip**
>
> When using noise or background voice cancellation in the frontend, do not enable Krisp noise cancellation in the agent code.
>
> Standard noise cancellation and the separate echo cancellation feature can be left enabled.
#### Installation
Add the package to your `build.gradle` file:
```groovy
dependencies {
implementation "io.livekit:krisp-noise-filter:0.0.10"
}
```
Get the latest SDK version number from [Maven Central](https://central.sonatype.com/artifact/io.livekit/krisp-noise-filter).
#### Usage
```kotlin
val krisp = KrispAudioProcessor.getInstance(getApplication())
coroutineScope.launch(Dispatchers.IO) {
// Only needs to be done once.
// This should be executed on the background thread to avoid UI freezes.
krisp.init()
}
// Pass the KrispAudioProcessor into the Room creation
room = LiveKit.create(
getApplication(),
overrides = LiveKitOverrides(
audioOptions = AudioOptions(
audioProcessorOptions = AudioProcessorOptions(
capturePostProcessor = krisp,
)
),
),
)
// Or to set after Room creation
room.audioProcessingController.setCapturePostProcessing(krisp)
```
#### Available models
The Android noise filter supports only the standard noise cancellation (NC) model.
---
**Swift**:
> 💡 **Tip**
>
> When using noise or background voice cancellation in the frontend, do not enable Krisp noise cancellation in the agent code.
>
> Standard noise cancellation and the separate echo cancellation feature can be left enabled.
#### Installation
Add a new [package dependency](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) to your app by URL:
```
https://github.com/livekit/swift-krisp-noise-filter
```
Or in your `Package.swift` file:
```swift
.package(url: "https://github.com/livekit/swift-krisp-noise-filter.git", from: "0.0.7"),
```
#### Usage
Here is a simple example of a SwiftUI app that uses Krisp in its root view:
```swift
import LiveKit
import SwiftUI
import LiveKitKrispNoiseFilter
// Keep this as a global variable or somewhere that won't be deallocated
let krispProcessor = LiveKitKrispNoiseFilter()
struct ContentView: View {
@StateObject private var room = Room()
var body: some View {
MyOtherView()
.environmentObject(room)
.onAppear {
// Attach the processor
AudioManager.shared.capturePostProcessingDelegate = krispProcessor
// This must be done before calling `room.connect()`
room.add(delegate: krispProcessor)
// You are now ready to connect to the room from this view or any child view
}
}
}
```
For a complete example, view the [Krisp sample project](https://github.com/livekit-examples/swift-example-collection/tree/main/krisp-minimal).
#### Available models
The Swift noise filter supports only the standard noise cancellation (NC) model.
#### Compatibility
- The Krisp SDK requires iOS 13+ or macOS 10.15+.
- If your app also targets visionOS or tvOS, you'll need to wrap your Krisp code in `#if os(iOS) || os(macOS)` and [add a filter to the library linking step in Xcode](https://developer.apple.com/documentation/xcode/customizing-the-build-phases-of-a-target#Link-against-additional-frameworks-and-libraries).
---
**React Native**:
> 💡 **Tip**
>
> When using noise or background voice cancellation in the frontend, do not enable Krisp noise cancellation in the agent code.
>
> Standard noise cancellation and the separate echo cancellation feature can be left enabled.
#### Installation
```shell
npm install @livekit/react-native-krisp-noise-filter
```
This package includes both the Krisp SDK and the required models.
#### Usage
```tsx
import { KrispNoiseFilter } from '@livekit/react-native-krisp-noise-filter';
import { useLocalParticipant } from '@livekit/components-react';
import { useMemo, useEffect } from 'react';
function MyComponent() {
let { microphoneTrack } = useLocalParticipant();
const krisp = useMemo(() => KrispNoiseFilter(), []);
useEffect(() => {
const localAudioTrack = microphoneTrack?.audioTrack;
if (!localAudioTrack) {
return;
}
localAudioTrack?.setProcessor(krisp);
}, [microphoneTrack, krisp]);
}
```
#### Available models
The React Native noise filter supports only the standard noise cancellation (NC) model.
---
**Flutter**:
> 💡 **Tip**
>
> When using noise or background voice cancellation in the frontend, do not enable Krisp noise cancellation in the agent code.
>
> Standard noise cancellation and the separate echo cancellation feature can be left enabled.
#### Installation
Add the package to your `pubspec.yaml` file:
```yaml
dependencies:
livekit_noise_filter: ^0.1.0
```
#### Usage
```dart
import 'package:livekit_client/livekit_client.dart';
import 'package:livekit_noise_filter/livekit_noise_filter.dart';
// Create the noise filter instance
final liveKitNoiseFilter = LiveKitNoiseFilter();
// Configure room with the noise filter
final room = Room(
roomOptions: RoomOptions(
defaultAudioCaptureOptions: AudioCaptureOptions(
processor: liveKitNoiseFilter,
),
),
);
// Connect to room and enable microphone
await room.connect(url, token);
await room.localParticipant?.setMicrophoneEnabled(true);
// You can also enable/disable the filter at runtime
// liveKitNoiseFilter.setBypass(true); // Disables noise cancellation
// liveKitNoiseFilter.setBypass(false); // Enables noise cancellation
```
#### Available models
The Flutter noise filter supports only the standard noise cancellation (NC) model.
#### Compatibility
The Flutter noise filter is currently supported only on iOS, macOS, and Android platforms.
---
This document was rendered at 2026-02-03T03:25:16.525Z.
For the latest version of this document, see [https://docs.livekit.io/transport/media/enhanced-noise-cancellation.md](https://docs.livekit.io/transport/media/enhanced-noise-cancellation.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/reference/other/events.md
LiveKit docs › Other › Events and error handling
---
# Events and error handling
> Guides and reference for events and error handling in LiveKit Agents.
## Events
`AgentSession` emits events to notify you of state changes. Each event is emitted with an event object as its sole argument.
### user_input_transcribed
A `UserInputTranscribedEvent` is emitted when user transcription is available.
#### Properties
- `language`: str
- `transcript`: str
- `is_final`: bool
- `speaker_id`: str | None - Only available if speaker diarization is supported in your STT plugin.
#### Example
**Python**:
```python
from livekit.agents import UserInputTranscribedEvent
@session.on("user_input_transcribed")
def on_user_input_transcribed(event: UserInputTranscribedEvent):
print(f"User input transcribed: {event.transcript}, "
f"language: {event.language}, "
f"final: {event.is_final}, "
f"speaker id: {event.speaker_id}")
```
---
**Node.js**:
```ts
import { voice } from '@livekit/agents';
session.on(voice.AgentSessionEventTypes.UserInputTranscribed, (event) => {
console.log(`User input transcribed: ${event.transcript}, language: ${event.language}, final: ${event.isFinal}, speaker id: ${event.speakerId}`);
});
```
### conversation_item_added
A `ConversationItemAddedEvent` is emitted when a item is committed to the chat history. This event is emitted for both user and agent items.
#### Properties
- `item`: [ChatMessage](https://github.com/livekit/agents/blob/3ee369e7783a2588cffecc0725e582cac10efa39/livekit-agents/livekit/agents/llm/chat_context.py#L105)
#### Example
**Python**:
```python
from livekit.agents import ConversationItemAddedEvent
from livekit.agents.llm import ImageContent, AudioContent
...
@session.on("conversation_item_added")
def on_conversation_item_added(event: ConversationItemAddedEvent):
print(f"Conversation item added from {event.item.role}: {event.item.text_content}. interrupted: {event.item.interrupted}")
# to iterate over all types of content:
for content in event.item.content:
if isinstance(content, str):
print(f" - text: {content}")
elif isinstance(content, ImageContent):
# image is either a rtc.VideoFrame or URL to the image
print(f" - image: {content.image}")
elif isinstance(content, AudioContent):
# frame is a list[rtc.AudioFrame]
print(f" - audio: {content.frame}, transcript: {content.transcript}")
```
---
**Node.js**:
```ts
import { voice } from '@livekit/agents';
// ...
session.on(voice.AgentSessionEventTypes.ConversationItemAdded, (event) => {
console.log(`Conversation item added from ${event.item.role}: ${event.item.textContent}. interrupted: ${event.item.interrupted}`);
// to iterate over all types of content:
for (const content of event.item.content) {
switch (typeof content === 'string' ? 'string' : content.type) {
case 'string':
console.log(` - text: ${content}`);
break;
case 'image_content':
// image is either a VideoFrame or URL to the image
console.log(` - image: ${content.image}`);
break;
case 'audio_content':
// frame is an array of AudioFrame
console.log(` - audio: ${content.frame}, transcript: ${content.transcript}`);
break;
}
}
});
```
### function_tools_executed
`FunctionToolsExecutedEvent` is emitted after all function tools have been executed for a given user input.
#### Methods
- `zipped()` returns a list of tuples of function calls and their outputs.
#### Properties
- `function_calls`: list[[FunctionCall](https://github.com/livekit/agents/blob/3ee369e7783a2588cffecc0725e582cac10efa39/livekit-agents/livekit/agents/llm/chat_context.py#L129)]
- `function_call_outputs`: list[[FunctionCallOutput](https://github.com/livekit/agents/blob/3ee369e7783a2588cffecc0725e582cac10efa39/livekit-agents/livekit/agents/llm/chat_context.py#L137)]
### metrics_collected
`MetricsCollectedEvent` is emitted when new metrics are available to be reported. For more information on metrics, see [Metrics and usage data](https://docs.livekit.io/deploy/observability/data.md#metrics).
#### Properties
- `metrics`: Union[STTMetrics, LLMMetrics, TTSMetrics, VADMetrics, EOUMetrics]
### speech_created
`SpeechCreatedEvent` is emitted when new agent speech is created. Speech could be created for any of the following reasons:
- the user has provided input
- `session.say` is used to create agent speech
- `session.generate_reply` is called to create a reply
#### Properties
- `user_initiated`: str - True if speech was created using public methods like `say` or `generate_reply`
- `source`: str - "say", "generate_reply", or "tool_response"
- `speech_handle`: [SpeechHandle](https://docs.livekit.io/agents/build/audio.md#speechhandle) - handle to track speech playout.
### agent_state_changed
`AgentStateChangedEvent` is emitted when the agent's state changes. The `lk.agent.state` attribute on the agent participant is updated to reflect the new state, allowing frontend code to easily respond to changes.
#### Properties
- `old_state`: AgentState
- `new_state`: AgentState
#### AgentState
The agent could be in one of the following states:
- `initializing` - agent is starting up. this should be brief.
- `listening` - agent is waiting for user input
- `thinking` - agent is processing user input
- `speaking` - agent is speaking
### user_state_changed
`UserStateChangedEvent` is emitted when the user's state changes. This change is driven by the VAD module running on the user's audio input.
#### Properties
- `old_state`: UserState
- `new_state`: UserState
#### UserState
The user's state can be one of the following:
- `speaking` - VAD detected user has started speaking
- `listening` - VAD detected the user has stopped speaking
- `away` - The user hasn't responded for a while (default: 15s). Specify a custom timeout with `AgentSession(user_away_timeout=...)`.
#### Example
- **[Handling idle user](https://github.com/livekit/agents/blob/main/examples/voice_agents/inactive_user.py)**: Check in with the user after they go idle.
### close
The `CloseEvent` is emitted when the AgentSession has closed and the agent is no longer running. This can occur for several reasons:
- The user ended the conversation
- `session.aclose()` was called
- The room was deleted, disconnecting the agent
- An unrecoverable error occurred during the session
#### Properties
- `error`: LLMError | STTError | TTSError | RealtimeModelError | None - The error that caused the session to close, if applicable
## Handling errors
In addition to state changes, it's important to handle errors that may occur during a session. In real-time conversations, inference API failures can disrupt the flow, potentially leaving the agent unable to continue.
### FallbackAdapter
For STT, LLM, and TTS, the Agents framework includes a `FallbackAdapter` that can fall back to secondary providers if the primary one fails.
> ℹ️ **FallbackAdapter support for Node.js**
>
> In Node.js, the `FallbackAdapter` is only available for LLM.
When in use, `FallbackAdapter` handles the following:
- Automatically resubmits the failed request to backup providers when the primary provider fails.
- Marks the failed provider as unhealthy and stops sending requests to it.
- Continues to use the backup providers until the primary provider recovers.
- Periodically checks the primary provider's status in the background.
**Python**:
```python
from livekit.agents import llm, stt, tts
from livekit.plugins import assemblyai, deepgram, elevenlabs, openai, groq
session = AgentSession(
stt=stt.FallbackAdapter(
[
assemblyai.STT(),
deepgram.STT(),
]
),
llm=llm.FallbackAdapter(
[
openai.responses.LLM(model="gpt-4o"),
openai.LLM.with_azure(model="gpt-4o", ...),
]
),
tts=tts.FallbackAdapter(
[
elevenlabs.TTS(...),
groq.TTS(...),
]
),
)
```
---
**Node.js**:
```typescript
import { llm, voice } from '@livekit/agents';
import * as openai from '@livekit/agents-plugin-openai';
const session = new voice.AgentSession({
llm: new llm.FallbackAdapter({
llms: [
new openai.LLM({ model: 'openai/gpt-4o' }),
new openai.LLM.withAzure({ model: 'openai/gpt-4o' }),
],
}),
// ... stt, tts, etc.
});
```
For a complete example, see the [Node.js example in GitHub](https://github.com/livekit/agents-js/blob/main/examples/src/llm_fallback_adapter.ts).
### Error event
`AgentSession` emits `ErrorEvent` when errors occur during the session. It includes an `error` object with a `recoverable` field indicating whether the session will retry the failed operation.
- If `recoverable` is `True`, the event is informational, and the session will continue as expected.
- If `recoverable` is `False` (e.g., after exhausting retries), the session requires intervention. You can handle the error—for instance, by using `.say()` to inform the user of an issue.
#### Properties
- `model_config`: dict - a dictionary representing the current model's configuration
- `error`: [LLMError | STTError | TTSError | RealtimeModelError](https://github.com/livekit/agents/blob/db551d2/livekit-agents/livekit/agents/voice/events.py#L138) - the error that occurred. `recoverable` is a field within `error`.
- `source`: LLM | STT | TTS | RealtimeModel - the source object responsible for the error
### Example
- **[Error handling](https://github.com/livekit/agents/blob/main/examples/voice_agents/error_callback.py)**: Handling unrecoverable errors with a presynthesized message.
---
This document was rendered at 2026-02-03T03:25:08.125Z.
For the latest version of this document, see [https://docs.livekit.io/reference/other/events.md](https://docs.livekit.io/reference/other/events.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/reference/other/egress/examples.md
LiveKit docs › Other › Egress › Egress examples
---
# Egress examples
> Usage examples for Egress APIs to record or livestream a room or individual tracks.
## Recording room composite as HLS
This example records a [room composite](https://docs.livekit.io/transport/media/ingress-egress/egress/composite-recording.md#roomcomposite-egress) layout as HLS segments to an S3-compatible bucket.
**LiveKit CLI**:
> ℹ️ **Note**
>
> When `live_playlist_name` is provided, a playlist is generated containing only the last few segments. This can be useful to livestream the recording via HLS.
```json
{
"room_name": "my-room",
"layout": "grid",
"preset": "H264_720P_30",
"custom_base_url": "https://my-custom-template.com",
"audio_only": false,
"segment_outputs": [
{
"filename_prefix": "path/to/my-output",
"playlist_name": "my-output.m3u8",
"live_playlist_name": "my-output-live.m3u8",
"segment_duration": 2,
"s3": {
"access_key": "",
"secret": "",
"region": "",
"bucket": "my-bucket",
"force_path_style": true
}
}
]
}
```
```shell
lk egress start --type room-composite egress.json
```
---
**JavaScript**:
```typescript
const outputs = {
segments: new SegmentedFileOutput({
filenamePrefix: 'my-output',
playlistName: 'my-output.m3u8',
livePlaylistName: 'my-output-live.m3u8',
segmentDuration: 2,
output: {
case: 's3',
value: {
accessKey: '',
secret: '',
bucket: '',
region: '',
forcePathStyle: true,
},
},
}),
};
const egressClient = new EgressClient('https://myproject.livekit.cloud');
await egressClient.startRoomCompositeEgress('my-room', outputs, {
layout: 'grid',
customBaseUrl: 'https://my-custom-template.com',
encodingOptions: EncodingOptionsPreset.H264_1080P_30,
audioOnly: false,
});
```
---
**Go**:
```go
req := &livekit.RoomCompositeEgressRequest{
RoomName: "my-room-to-record",
Layout: "speaker",
AudioOnly: false,
CustomBaseUrl: "https://my-custom-template.com",
Options: &livekit.RoomCompositeEgressRequest_Preset{
Preset: livekit.EncodingOptionsPreset_PORTRAIT_H264_1080P_30,
},
}
req.SegmentOutputs = []*livekit.SegmentedFileOutput{
{
FilenamePrefix: "my-output",
PlaylistName: "my-output.m3u8",
LivePlaylistName: "my-output-live.m3u8",
SegmentDuration: 2,
Output: &livekit.SegmentedFileOutput_S3{
S3: &livekit.S3Upload{
AccessKey: "",
Secret: "",
Endpoint: "",
Bucket: "",
ForcePathStyle: true,
},
},
},
}
egressClient := lksdk.NewEgressClient(
"https://project.livekit.cloud",
os.Getenv("LIVEKIT_API_KEY"),
os.Getenv("LIVEKIT_API_SECRET"),
)
res, err := egressClient.StartRoomCompositeEgress(context.Background(), req)
```
---
**Ruby**:
```ruby
outputs = [
LiveKit::Proto::SegmentedFileOutput.new(
filename_prefix: "my-output",
playlist_name: "my-output.m3u8",
live_playlist_name: "my-output-live.m3u8",
segment_duration: 2,
s3: LiveKit::Proto::S3Upload.new(
access_key: "",
secret: "",
endpoint: "",
region: "",
bucket: "my-bucket",
force_path_style: true,
)
)
]
egress_client = LiveKit::EgressClient.new("https://myproject.livekit.cloud")
egress_client.start_room_composite_egress(
'my-room',
outputs,
layout: 'speaker',
custom_base_url: 'https://my-custom-template.com',
encoding_options: LiveKit::Proto::EncodingOptionsPreset::H264_1080P_30,
audio_only: false
)
```
---
**Python**:
```python
from livekit import api
req = api.RoomCompositeEgressRequest(
room_name="my-room",
layout="speaker",
custom_base_url="http://my-custom-template.com",
preset=api.EncodingOptionsPreset.H264_720P_30,
audio_only=False,
segment_outputs=[api.SegmentedFileOutput(
filename_prefix="my-output",
playlist_name="my-playlist.m3u8",
live_playlist_name="my-live-playlist.m3u8",
segment_duration=2,
s3=api.S3Upload(
bucket="my-bucket",
region="",
access_key="",
secret="",
force_path_style=True,
),
)],
)
lkapi = api.LiveKitAPI("http://localhost:7880")
res = await lkapi.egress.start_room_composite_egress(req)
```
---
**Java**:
```java
import io.livekit.server.EgressServiceClient;
import io.livekit.server.EncodedOutputs;
import retrofit2.Call;
import retrofit2.Response;
import livekit.LivekitEgress;
import java.io.IOException;
public class Main {
public void startEgress() throws IOException {
EgressServiceClient ec = EgressServiceClient.createClient(
"https://myproject.livekit.cloud", "apiKey", "secret");
LivekitEgress.SegmentedFileOutput segmentOutput = LivekitEgress.SegmentedFileOutput.newBuilder().
setFilenamePrefix("my-segmented-file").
setPlaylistName("my-playlist.m3u8").
setLivePlaylistName("my-live-playlist.m3u8").
setSegmentDuration(2).
setS3(LivekitEgress.S3Upload.newBuilder()
.setBucket("")
.setAccessKey("")
.setSecret("")
.setForcePathStyle(true)).
build();
Call call = ec.startRoomCompositeEgress(
"my-room",
segmentOutput,
// layout
"speaker",
LivekitEgress.EncodingOptionsPreset.H264_720P_30,
// not using advanced encoding options, since preset is specified
null,
// not audio-only
false,
// not video-only
false,
// using custom template, leave empty to use defaults
"https://my-templates.com");
Response response = call.execute();
LivekitEgress.EgressInfo egressInfo = response.body();
}
}
```
## Recording web in portrait
This example records a [web page](https://docs.livekit.io/transport/media/ingress-egress/egress/composite-recording.md#web-egress) in portrait mode to Google Cloud Storage, streaming to RTMP.
Portrait orientation can be specified by either using a `preset` option or setting `advanced` options. Egress automatically resizes the Chrome compositor to your specified resolution. However, keep in mind the following requirements:
- Chrome has a minimum browser width of 500px.
- Your application must maintain a portrait layout, even when the browser reports a width larger than typical mobile phones. (for example, 720px width or larger).
**LiveKit CLI**:
```json
{
"url": "https://my-page.com",
"preset": "PORTRAIT_H264_720P_30",
"audio_only": false,
"file_outputs": [
{
"filepath": "my-test-file.mp4",
"gcp": {
"credentials": "{\"type\": \"service_account\", ...}",
"bucket": "my-bucket"
}
}
],
"stream_outputs": [
{
"protocol": "RTMP",
"urls": ["rtmps://my-rtmp-server.com/live/stream-key"]
}
]
}
```
```shell
lk egress start --type web egress.json
```
---
**JavaScript**:
```typescript
import * as fs from 'fs';
const content = fs.readFileSync('/path/to/credentials.json');
const outputs = {
file: new EncodedFileOutput({
filepath: 'my-recording.mp4',
output: {
case: 'gcp',
value: new GCPUpload({
// credentials need to be a JSON encoded string containing credentials
credentials: content.toString(),
bucket: 'my-bucket',
}),
},
}),
stream: new StreamOutput({
protocol: StreamProtocol.RTMP,
urls: ['rtmp://example.com/live/stream-key'],
}),
};
await egressClient.startWebEgress('https://my-site.com', outputs, {
encodingOptions: EncodingOptionsPreset.PORTRAIT_H264_1080P_30,
audioOnly: false,
});
```
---
**Go**:
```go
credentialsJson, err := os.ReadFile("/path/to/credentials.json")
if err != nil {
panic(err.Error())
}
req := &livekit.WebEgressRequest{
Url: "https://my-website.com",
AudioOnly: false,
Options: &livekit.WebEgressRequest_Preset{
Preset: livekit.EncodingOptionsPreset_PORTRAIT_H264_1080P_30,
},
}
req.FileOutputs = []*livekit.EncodedFileOutput{
{
Filepath: "myfile.mp4",
Output: &livekit.EncodedFileOutput_Gcp{
Gcp: &livekit.GCPUpload{
Credentials: string(credentialsJson),
Bucket: "my-bucket",
},
},
},
}
req.StreamOutputs = []*livekit.StreamOutput{
{
Protocol: livekit.StreamProtocol_RTMP,
Urls: []string{"rtmp://myserver.com/live/stream-key"},
},
}
res, err := egressClient.StartWebEgress(context.Background(), req)
```
---
**Ruby**:
```ruby
content = File.read("/path/to/credentials.json")
outputs = [
LiveKit::Proto::EncodedFileOutput.new(
filepath: "myfile.mp4",
s3: LiveKit::Proto::S3Upload.new(
credentials: content,
bucket: "my-bucket"
)
),
LiveKit::Proto::StreamOutput.new(
protocol: LiveKit::Proto::StreamProtocol::RTMP,
urls: ["rtmp://myserver.com/live/stream-key"]
)
]
egress_client.start_web_egress(
'https://my-website.com',
outputs,
encoding_options: LiveKit::Proto::EncodingOptionsPreset::PORTRAIT_H264_1080P_30,
audio_only: false
)
```
---
**Python**:
```python
content = ""
with open("/path/to/credentials.json", "r") as f:
content = f.read()
file_output = api.EncodedFileOutput(
filepath="myfile.mp4",
gcp=api.GCPUpload(
credentials=content,
bucket="my-bucket",
),
)
req = api.WebEgressRequest(
url="https://my-site.com",
preset=EncodingOptionsPreset.PORTRAIT_H264_1080P_30,
audio_only=False,
file_outputs=[file_output],
stream_outputs=[api.StreamOutput(
protocol=api.StreamProtocol.RTMP,
urls=["rtmp://myserver.com/live/stream-key"],
)],
)
res = await lkapi.egress.start_web_egress(req)
```
---
**Java**:
```java
public void startEgress() throws IOException {
EgressServiceClient ec = EgressServiceClient.createClient(
"https://myproject.livekit.cloud", "apiKey", "secret");
// We recommend using Google's auth library (google-auth-library-oauth2-http) to load their credentials file.
GoogleCredentials credentials = GoogleCredentials.fromStream(new FileInputStream("path/to/credentials.json"));
LivekitEgress.SegmentedFileOutput segmentOutput = LivekitEgress.SegmentedFileOutput.newBuilder().
setFilenamePrefix("my-segmented-file").
setPlaylistName("my-playlist.m3u8").
setLivePlaylistName("my-live-playlist.m3u8").
setSegmentDuration(2).
setGcp(LivekitEgress.GCPUpload.newBuilder()
.setBucket("")
.setCredentials(credentials.toString())
).
build();
LivekitEgress.StreamOutput streamOutput = LivekitEgress.StreamOutput.newBuilder().
setProtocol(LivekitEgress.StreamProtocol.RTMP).
addUrls("rtmps://myserver.com/live/stream-key").
build();
EncodedOutputs outputs = new EncodedOutputs(
// no file output
null,
streamOutput,
segmentOutput,
// no image output
null
);
Call call = ec.startWebEgress(
"https://my-site.com",
outputs,
LivekitEgress.EncodingOptionsPreset.PORTRAIT_H264_720P_30,
// not using advanced encoding options, since preset is specified
null,
// not audio-only
false,
// not video-only
false,
// wait for console.log("START_RECORDING") before recording
true);
Response response = call.execute();
LivekitEgress.EgressInfo egressInfo = response.body();
}
```
## SRT streaming with thumbnails
This example streams an [individual participant](https://docs.livekit.io/transport/media/ingress-egress/egress/participant.md) to an SRT server, generating thumbnails every 5 seconds. Thumbnails are stored in Azure Blob Storage.
**LiveKit CLI**:
```json
{
"room_name": "my-room",
"identity": "participant-to-record",
"screen_share": false,
"advanced": {
"width": 1280,
"height": 720,
"framerate": 30,
"audioCodec": "AAC",
"audioBitrate": 128,
"videoCodec": "H264_HIGH",
"videoBitrate": 5000,
"keyFrameInterval": 2
},
"stream_outputs": [
{
"protocol": "SRT",
"urls": ["srt://my-srt-server.com:9999"]
}
],
"image_outputs": [
{
"capture_interval": 5,
"width": 1280,
"height": 720,
"filename_prefix": "{room_name}/{publisher_identity}",
"filename_suffix": "IMAGE_SUFFIX_TIMESTAMP",
"disable_manifest": true,
"azure": {
"account_name": "my-account",
"account_key": "my-key",
"container_name": "my-container"
}
}
]
}
```
```shell
lk egress start --type participant egress.json
```
---
**JavaScript**:
```typescript
const outputs: EncodedOutputs = {
stream: new StreamOutput({
protocol: StreamProtocol.SRT,
url: 'srt://my-srt-server.com:9999',
}),
images: new ImageOutput({
captureInterval: 5,
width: 1280,
height: 720,
filenamePrefix: '{room_name}/{publisher_identity}',
filenameSuffix: ImageFileSuffix.IMAGE_SUFFIX_TIMESTAMP,
output: {
case: 'azure',
value: {
accountName: 'azure-account-name',
accountKey: 'azure-account-key',
container_name: 'azure-container',
},
},
}),
};
const info = await ec.startParticipantEgress('my-room', 'participant-to-record', outputs, {
screenShare: false,
encodingOptions: {
width: 1280,
height: 720,
framerate: 30,
audioCodec: AudioCodec.AAC,
audioBitrate: 128,
videoCodec: VideoCodec.H264_HIGH,
videoBitrate: 5000,
keyFrameInterval: 2,
},
});
```
---
**Go**:
```go
req := &livekit.ParticipantEgressRequest{
RoomName: "my-room",
Identity: "participant-to-record",
ScreenShare: false,
Options: &livekit.ParticipantEgressRequest_Advanced{
Advanced: &livekit.EncodingOptions{
Width: 1280,
Height: 720,
Framerate: 30,
AudioCodec: livekit.AudioCodec_AAC,
AudioBitrate: 128,
VideoCodec: livekit.VideoCodec_H264_HIGH,
VideoBitrate: 5000,
KeyFrameInterval: 2,
},
},
StreamOutputs: []*livekit.StreamOutput{{
Protocol: livekit.StreamProtocol_SRT,
Urls: []string{"srt://my-srt-host:9999"},
}},
ImageOutputs: []*livekit.ImageOutput{{
CaptureInterval: 5,
Width: 1280,
Height: 720,
FilenamePrefix: "{room_name}/{publisher_identity}",
FilenameSuffix: livekit.ImageFileSuffix_IMAGE_SUFFIX_TIMESTAMP,
DisableManifest: true,
Output: &livekit.ImageOutput_Azure{
Azure: &livekit.AzureBlobUpload{
AccountName: "my-account-name",
AccountKey: "my-account-key",
ContainerName: "my-container",
},
},
}},
}
info, err := client.StartParticipantEgress(context.Background(), req)
```
---
**Ruby**:
```ruby
outputs = [
LiveKit::Proto::StreamOutput.new(
protocol: LiveKit::Proto::StreamProtocol::SRT,
urls: ["srt://my-srt-server:9999"],
),
LiveKit::Proto::ImageOutput.new(
capture_interval: 5,
width: 1280,
height: 720,
filename_prefix: "{room_name}/{publisher_identity}",
filename_suffix: LiveKit::Proto::ImageFileSuffix::IMAGE_SUFFIX_TIMESTAMP,
azure: LiveKit::Proto::AzureBlobUpload.new(
account_name: "account-name",
account_key: "account-key",
container_name: "container-name",
)
)
]
info = egressClient.start_participant_egress(
'room-name',
'publisher-identity',
outputs,
screen_share: false,
advanced: LiveKit::Proto::EncodingOptions.new(
width: 1280,
height: 720,
framerate: 30,
audio_codec: LiveKit::Proto::AudioCodec::AAC,
audio_bitrate: 128,
video_codec: LiveKit::Proto::VideoCodec::H264_HIGH,
video_bitrate: 5000,
key_frame_interval: 2,
)
)
```
---
**Python**:
```python
request = api.ParticipantEgressRequest(
room_name="my-room",
identity="publisher-to-record",
screen_share=False,
advanced=api.EncodingOptions(
width=1280,
height=720,
framerate=30,
audio_codec=api.AudioCodec.AAC,
audio_bitrate=128,
video_codec=api.VideoCodec.H264_HIGH,
video_bitrate=5000,
keyframe_interval=2,
),
stream_outputs=[api.StreamOutput(
protocol=api.StreamProtocol.SRT,
urls=["srt://my-srt-server:9999"],
)],
image_outputs=[api.ImageOutput(
capture_interval=5,
width=1280,
height=720,
filename_prefix="{room_name}/{publisher_identity}",
filename_suffix=api.IMAGE_SUFFIX_TIMESTAMP,
azure=api.AzureBlobUpload(
account_name="my-azure-account",
account_key="my-azure-key",
container_name="my-azure-container",
),
)],
)
info = await lkapi.egress.start_participant_egress(request)
```
---
**Java**:
```java
public void startEgress() throws IOException {
EgressServiceClient ec = EgressServiceClient.createClient(
"https://myproject.livekit.cloud", "apiKey", "secret");
LivekitEgress.StreamOutput streamOutput = LivekitEgress.StreamOutput.newBuilder().
setProtocol(LivekitEgress.StreamProtocol.SRT).
addUrls("srt://my-srt-server:9999").
build();
LivekitEgress.ImageOutput imageOutput = LivekitEgress.ImageOutput.newBuilder().
setCaptureInterval(5).
setWidth(1280).
setHeight(720).
setFilenamePrefix("{room_name}/{publisher_identity}").
setFilenameSuffix(LivekitEgress.ImageFileSuffix.IMAGE_SUFFIX_TIMESTAMP).
setAzure(LivekitEgress.AzureBlobUpload.newBuilder()
.setAccountName("")
.setAccountKey("")
.setContainerName("")).
build();
EncodedOutputs outputs = new EncodedOutputs(
// no file output
null,
streamOutput,
null,
imageOutput
);
LivekitEgress.EncodingOptions encodingOptions = LivekitEgress.EncodingOptions.newBuilder()
.setWidth(1280)
.setHeight(720)
.setFramerate(30)
.setAudioCodec(LivekitModels.AudioCodec.AAC)
.setAudioBitrate(128)
.setVideoCodec(LivekitModels.VideoCodec.H264_HIGH)
.setVideoBitrate(5000)
.setKeyFrameInterval(2)
.build();
Call call = ec.startParticipantEgress(
"my-room",
"publisher-to-record",
outputs,
// capture camera/microphone, not screenshare
false,
// not using preset, using custom encoding options
null,
encodingOptions);
Response response = call.execute();
LivekitEgress.EgressInfo egressInfo = response.body();
}
```
## Adding RTMP to track composite egress
Create a TrackComposite Egress recorded as HLS segments, with RTMP output added later.
**LiveKit CLI**:
```json
{
"room_name": "my-room",
"audio_track_id": "TR_AUDIO_ID",
"video_track_id": "TR_VIDEO_ID",
"stream_outputs": [
{
"protocol": "RTMP",
"urls": []
}
],
"segment_outputs": [
{
"filename_prefix": "path/to/my-output",
"playlist_name": "my-output.m3u8",
"segment_duration": 2,
"s3": {
"access_key": "",
"secret": "",
"region": "",
"bucket": "my-bucket"
}
}
]
}
```
```shell
lk egress start --type track-composite egress.json
# later, to add a RTMP output
lk egress update-stream --id --add-urls rtmp://new-server.com/live/stream-key
# to remove RTMP output
lk egress update-stream --id --remove-urls rtmp://new-server.com/live/stream-key
```
---
**JavaScript**:
```typescript
const outputs: EncodedOutputs = {
// a placeholder RTMP output is needed to ensure stream urls can be added to it later
stream: new StreamOutput({
protocol: StreamProtocol.RTMP,
urls: [],
}),
segments: new SegmentedFileOutput({
filenamePrefix: 'my-output',
playlistName: 'my-output.m3u8',
segmentDuration: 2,
output: {
case: 's3',
value: {
accessKey: '',
secret: '',
bucket: '',
region: '',
forcePathStyle: true,
},
},
}),
};
const info = await ec.startTrackCompositeEgress('my-room', outputs, {
videoTrackId: 'TR_VIDEO_TRACK_ID',
audioTrackId: 'TR_AUDIO_TRACK_ID',
encodingOptions: EncodingOptionsPreset.H264_720P_30,
});
// later, to add RTMP output
await ec.updateStream(info.egressId, ['rtmp://new-server.com/live/stream-key']);
// to remove RTMP output
await ec.updateStream(info.egressId, [], ['rtmp://new-server.com/live/stream-key']);
```
---
**Go**:
```go
req := &livekit.TrackCompositeEgressRequest{
RoomName: "my-room",
VideoTrackId: "TR_VIDEO_TRACK_ID",
AudioTrackId: "TR_AUDIO_TRACK_ID",
Options: &livekit.TrackCompositeEgressRequest_Preset{
Preset: livekit.EncodingOptionsPreset_H264_720P_30,
},
SegmentOutputs: []*livekit.SegmentedFileOutput{{
FilenamePrefix: "my-output",
PlaylistName: "my-output.m3u8",
SegmentDuration: 2,
Output: &livekit.SegmentedFileOutput_S3{
S3: &livekit.S3Upload{
AccessKey: "",
Secret: "",
Endpoint: "",
Bucket: "",
ForcePathStyle: true,
},
},
}},
// a placeholder RTMP output is needed to ensure stream urls can be added to it later
StreamOutputs: []*livekit.StreamOutput{{
Protocol: livekit.StreamProtocol_RTMP,
Urls: []string{},
}},
}
info, err := client.StartTrackCompositeEgress(context.Background(), req)
// add new output URL to the stream
client.UpdateStream(context.Background(), &livekit.UpdateStreamRequest{
EgressId: info.EgressId,
AddOutputUrls: []string{"rtmp://new-server.com/live/stream-key"},
})
// remove an output URL from the stream
client.UpdateStream(context.Background(), &livekit.UpdateStreamRequest{
EgressId: info.EgressId,
RemoveOutputUrls: []string{"rtmp://new-server.com/live/stream-key"},
})
```
---
**Ruby**:
```ruby
outputs = [
# a placeholder RTMP output is needed to ensure stream urls can be added to it later
LiveKit::Proto::StreamOutput.new(
protocol: LiveKit::Proto::StreamProtocol::RTMP,
urls: [],
),
LiveKit::Proto::SegmentedFileOutput.new(
filename_prefix: "my-output",
playlist_name: "my-output.m3u8",
segment_duration: 2,
s3: LiveKit::Proto::S3Upload.new(
access_key: "",
secret: "",
endpoint: "",
region: "",
bucket: "my-bucket",
force_path_style: true,
)
)
]
info = egressClient.start_track_composite_egress(
'room-name',
outputs,
audio_track_id: 'TR_AUDIO_TRACK_ID',
video_track_id: 'TR_VIDEO_TRACK_ID',
preset: LiveKit::Proto::EncodingOptionsPreset::H264_1080P_30,
)
# add new output URL to the stream
egressClient.update_stream(info.egress_id, ["rtmp://new-server.com/live/stream-key"])
# remove an output URL from the stream
egressClient.remove_stream(info.egress_id, [], ["rtmp://new-server.com/live/stream-key"])
```
---
**Python**:
```python
request = api.TrackCompositeEgressRequest(
room_name="my-room",
audio_track_id="TR_AUDIO_TRACK_ID",
video_track_id="TR_VIDEO_TRACK_ID",
preset=api.EncodingOptionsPreset.H264_720P_30,
# a placeholder RTMP output is needed to ensure stream urls can be added to it later
stream_outputs=[api.StreamOutput(
protocol=api.StreamProtocol.RTMP,
urls=[],
)],
segment_outputs=[api.SegmentedFileOutput(
filename_prefix= "my-output",
playlist_name= "my-playlist.m3u8",
live_playlist_name= "my-live-playlist.m3u8",
segment_duration= 2,
s3 = api.S3Upload(
bucket="my-bucket",
region="",
access_key="",
secret="",
force_path_style=True,
),
)],
)
info = await lkapi.egress.start_track_composite_egress(request)
# add new output URL to the stream
lkapi.egress.update_stream(api.UpdateStreamRequest(
egress_id=info.egress_id,
add_output_urls=["rtmp://new-server.com/live/stream-key"],
))
# remove an output URL from the stream
lkapi.egress.update_stream(api.UpdateStreamRequest(
egress_id=info.egress_id,
remove_output_urls=["rtmp://new-server.com/live/stream-key"],
))
```
---
**Java**:
```java
public void startEgress() throws IOException {
EgressServiceClient ec = EgressServiceClient.createClient(
"https://myproject.livekit.cloud", "apiKey", "secret");
// a placeholder RTMP output is needed to ensure stream urls can be added to it later
LivekitEgress.StreamOutput streamOutput = LivekitEgress.StreamOutput.newBuilder().
setProtocol(LivekitEgress.StreamProtocol.RTMP).
build();
LivekitEgress.SegmentedFileOutput segmentOutput = LivekitEgress.SegmentedFileOutput.newBuilder().
setFilenamePrefix("my-hls-file").
setPlaylistName("my-playlist.m3u8").
setLivePlaylistName("my-live-playlist.m3u8").
setSegmentDuration(2).
setS3(LivekitEgress.S3Upload.newBuilder()
.setBucket("")
.setAccessKey("")
.setSecret("")
.setForcePathStyle(true)).
build();
EncodedOutputs outputs = new EncodedOutputs(
// no file output
null,
streamOutput,
segmentOutput,
null
);
Call call = ec.startTrackCompositeEgress(
"my-room",
outputs,
"TR_AUDIO_TRACK_ID",
"TR_VIDEO_TRACK_ID",
LivekitEgress.EncodingOptionsPreset.H264_1080P_30);
Response response = call.execute();
LivekitEgress.EgressInfo egressInfo = response.body();
// add new output URL to the stream
call = ec.updateStream(egressInfo.getEgressId(), List.of("rtmp://new-server.com/live/stream-key"), List.of());
response = call.execute();
egressInfo = response.body();
// remove an output URL from the stream
call = ec.updateStream(egressInfo.getEgressId(), List.of(), List.of("rtmp://new-server.com/live/stream-key"));
response = call.execute();
egressInfo = response.body();
}
```
## Exporting individual tracks without transcoding
Export video tracks to Azure Blob Storage without transcoding.
> ℹ️ **Separate video and audio tracks**
>
> Video and audio tracks must be exported separately using Track Egress.
**LiveKit CLI**:
```json
{
"room_name": "my-room",
"track_id": "TR_TRACK_ID",
"filepath": "{room_name}/{track_id}",
"azure": {
"account_name": "my-account",
"account_key": "my-key",
"container_name": "my-container"
}
}
```
```shell
lk egress start --type track egress.json
```
---
**JavaScript**:
```typescript
const output = new DirectFileOutput({
filepath: '{room_name}/{track_id}',
output: {
case: 'azure',
value: {
accountName: 'account-name',
accountKey: 'account-key',
containerName: 'container-name',
},
},
});
const info = await ec.startTrackEgress('my-room', output, 'TR_TRACK_ID');
```
---
**Go**:
```go
req := &livekit.TrackEgressRequest{
RoomName: "my-room",
TrackId: "TR_TRACK_ID",
Output: &livekit.TrackEgressRequest_File{
File: &livekit.DirectFileOutput{
Filepath: "{room_name}/{track_id}",
Output: &livekit.DirectFileOutput_Azure{
Azure: &livekit.AzureBlobUpload{
AccountName: "",
AccountKey: "",
ContainerName: "",
},
},
},
},
}
info, err := client.StartTrackEgress(context.Background(), req)
```
---
**Ruby**:
```ruby
output = LiveKit::Proto::DirectFileOutput.new(
filepath: "{room_name}/{track_id}",
azure: LiveKit::Proto::AzureBlobUpload.new(
account_name: "account",
account_key: "account-key",
container_name: "container"
)
)
egressClient.start_track_egress("my-room", output, "TR_TRACK_ID")
```
---
**Python**:
```python
request = api.TrackEgressRequest(
room_name="my-room",
track_id="TR_TRACK_ID",
file=api.DirectFileOutput(
filepath="{room_name}/{track_id}",
azure=api.AzureBlobUpload(
account_name="ACCOUNT_NAME",
account_key="ACCOUNT_KEY",
container_name="CONTAINER_NAME",
),
),
)
egress_info = await lkapi.egress.start_track_egress(request)
```
---
**Java**:
```java
public void startEgress() throws IOException {
EgressServiceClient ec = EgressServiceClient.createClient(
"https://myproject.livekit.cloud", "apiKey", "secret");
LivekitEgress.DirectFileOutput fileOutput = LivekitEgress.DirectFileOutput.newBuilder().
setFilepath("{room_name}/{track_id}").
setAzure(LivekitEgress.AzureBlobUpload.newBuilder()
.setAccountName("")
.setAccountKey("")
.setContainerName("")).
build();
Call call = ec.startTrackEgress(
"my-room",
fileOutput,
"TR_TRACK_ID");
Response response = call.execute();
LivekitEgress.EgressInfo egressInfo = response.body();
}
```
## Stop an active egress
To stop an active egress, see the API reference for [StopEgress](https://docs.livekit.io/reference/other/egress/api.md#stopegress) for examples.
---
This document was rendered at 2026-02-03T03:25:27.281Z.
For the latest version of this document, see [https://docs.livekit.io/reference/other/egress/examples.md](https://docs.livekit.io/reference/other/egress/examples.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/transport/sdk-platforms/expo.md
LiveKit docs › Get Started › SDK platform quickstarts › Expo
---
# Expo quickstart
> Get started with LiveKit and Expo on React Native
## Voice AI quickstart
To build your first voice AI app for Expo, use the following quickstart and the starter app. Otherwise follow the getting started guide below.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Create a voice AI agent in less than 10 minutes.
- **[React Native Voice Agent](https://github.com/livekit-examples/agent-starter-react-native)**: A native voice AI assistant app built with React Native and Expo.
## Getting started guide
The following guide walks you through the steps to build a video-conferencing application using Expo. It uses the [LiveKit React Native SDK](https://github.com/livekit/client-sdk-react-native) to render the UI and communicate with LiveKit servers via WebRTC. By the end, you will have a basic video-conferencing application you can run with multiple participants.
### Install LiveKit SDK
LiveKit provides a [React Native SDK](https://github.com/livekit/client-sdk-react-native) and corresponding Expo config plugin. Install the packages and dependencies with:
```shell
npm install @livekit/react-native @livekit/react-native-expo-plugin @livekit/react-native-webrtc @config-plugins/react-native-webrtc livekit-client
```
> ℹ️ **Note**
>
> The LiveKit SDK is not compatible with the Expo Go app due to the native code required. Using `expo-dev-client` and [building locally](https://docs.expo.dev/guides/local-app-development/) will allow you to create development builds compatible with LiveKit.
### Configure Expo
In your root folder, add the Expo plugins to the `app.json` file:
```json
{
"expo": {
"plugins": ["@livekit/react-native-expo-plugin", "@config-plugins/react-native-webrtc"]
}
}
```
Finally, in your App.js file, setup the LiveKit SDK by calling `registerGlobals()`. This sets up the required WebRTC libraries for use in Javascript, and is needed for LiveKit to work.
```jsx
import { registerGlobals } from '@livekit/react-native';
registerGlobals();
```
### Connect to a room, publish video & audio
```jsx
import * as React from 'react';
import {
StyleSheet,
View,
FlatList,
ListRenderItem,
} from 'react-native';
import { useEffect } from 'react';
import {
AudioSession,
LiveKitRoom,
useTracks,
TrackReferenceOrPlaceholder,
VideoTrack,
isTrackReference,
registerGlobals,
} from '@livekit/react-native';
import { Track } from 'livekit-client';
registerGlobals();
// !! Note !!
// This sample hardcodes a token which expires in 2 hours.
const wsURL = "%{wsURL}%"
const token = "%{token}%"
export default function App() {
// Start the audio session first.
useEffect(() => {
let start = async () => {
await AudioSession.startAudioSession();
};
start();
return () => {
AudioSession.stopAudioSession();
};
}, []);
return (
);
};
const RoomView = () => {
// Get all camera tracks.
const tracks = useTracks([Track.Source.Camera]);
const renderTrack: ListRenderItem = ({item}) => {
// Render using the VideoTrack component.
if(isTrackReference(item)) {
return ()
} else {
return ()
}
};
return (
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'stretch',
justifyContent: 'center',
},
participantView: {
height: 300,
},
});
```
See the [quickstart example repo](https://github.com/livekit-examples/react-native-expo-quickstart) for a fully configured app using Expo.
### Create a backend server to generate tokens
Set up a server to generate tokens for your app at runtime by following this guide: [Generating Tokens](https://docs.livekit.io/frontends/authentication/tokens/generate.md).
## Next steps
The following resources are useful for getting started with LiveKit on React Native and Expo.
- **[Generating tokens](https://docs.livekit.io/frontends/authentication/tokens/generate.md)**: Guide to generating authentication tokens for your users.
- **[Realtime media](https://docs.livekit.io/transport/media.md)**: Complete documentation for live video and audio tracks.
- **[Realtime data](https://docs.livekit.io/transport/data.md)**: Send and receive realtime data between clients.
- **[React Native SDK](https://github.com/livekit/client-sdk-react-native)**: LiveKit React Native SDK on GitHub.
- **[React Native SDK reference](https://htmlpreview.github.io/?https://raw.githubusercontent.com/livekit/client-sdk-react-native/main/docs/modules.html)**: LiveKit React Native SDK reference docs.
---
This document was rendered at 2026-02-03T03:25:15.452Z.
For the latest version of this document, see [https://docs.livekit.io/transport/sdk-platforms/expo.md](https://docs.livekit.io/transport/sdk-platforms/expo.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/logic/external-data.md
LiveKit docs › Logic & Structure › External data & RAG
---
# External data and RAG
> Best practices for adding context and taking external actions.
## Overview
Your agent can connect to external data sources to retrieve information, store data, or take other actions. In general, you can install any Python package or add custom code to the agent to use any database or API that you need.
For instance, your agent might need to:
- Load a user's profile information from a database before starting a conversation.
- Search a private knowledge base for information to accurately answer user queries.
- Perform read/write/update operations on a database or service such as a calendar.
- Store conversation history or other data to a remote server.
This guide covers best practices and techniques for job initialization, retrieval-augmented generation (RAG), tool calls, and other techniques to connect your agent to external data sources and other systems.
## Initial context
By default, each `AgentSession` begins with an empty chat context. You can load user or task-specific data into the agent's context before connecting to the room and starting the session. For instance, this agent greets the user by name based on the [job metadata](https://docs.livekit.io/agents/server/job.md#metadata).
**Python**:
```python
from livekit import agents
from livekit.agents import AgentServer, Agent, ChatContext, AgentSession
class Assistant(Agent):
def __init__(self, chat_ctx: ChatContext) -> None:
super().__init__(chat_ctx=chat_ctx, instructions="You are a helpful voice AI assistant.")
server = AgentServer()
@server.rtc_session()
async def my_agent(ctx: agents.JobContext):
# Simple lookup, but you could use a database or API here if needed
metadata = json.loads(ctx.job.metadata)
user_name = metadata["user_name"]
session = AgentSession(
# ... stt, llm, tts, vad, turn_detection, etc.
)
initial_ctx = ChatContext()
initial_ctx.add_message(role="assistant", content=f"The user's name is {user_name}.")
await session.start(
room=ctx.room,
agent=Assistant(chat_ctx=initial_ctx),
# ... room_options, etc.
)
await session.generate_reply(
instructions="Greet the user by name and offer your assistance."
)
```
---
**Node.js**:
```typescript
import { voice, llm, defineAgent, type JobContext } from '@livekit/agents';
class Assistant extends voice.Agent {
constructor(chatCtx: llm.ChatContext) {
super({
chatCtx,
instructions: 'You are a helpful voice AI assistant.',
});
}
}
export default defineAgent({
entry: async (ctx: JobContext) => {
// Simple lookup, but you could use a database or API here if needed
const metadata = JSON.parse(ctx.job.metadata);
const userName = metadata.user_name;
const session = new voice.AgentSession({
// ... stt, llm, tts, vad, turnDetection, etc.
});
const initialCtx = llm.ChatContext.empty();
initialCtx.addMessage({
role: 'assistant',
content: `The user's name is ${userName}.`,
});
await session.start({
room: ctx.room,
agent: new Assistant(initialCtx),
// ... inputOptions, outputOptions, etc.
});
const handle = session.generateReply({
instructions: 'Greet the user by name and offer your assistance.',
});
await handle.waitForPlayout();
},
});
```
> 💡 **Load time optimizations**
>
> If your agent requires external data in order to start, the following tips can help minimize the impact to the user experience:
>
> 1. For static data (not user-specific) load it in the [prewarm function](https://docs.livekit.io/agents/server/options.md#prewarm)
> 2. Send user specific data in the [job metadata](https://docs.livekit.io/agents/server/job.md#metadata), [room metadata](https://docs.livekit.io/transport/data/state/room-metadata.md), or [participant attributes](https://docs.livekit.io/transport/data/state/participant-attributes.md) rather than loading it in the entrypoint.
> 3. If you must make a network call in the entrypoint, do so before `ctx.connect()`. This ensures your frontend doesn't show the agent participant before it is listening to incoming audio.
## Tool calls
To achieve the highest degree of precision or take external actions, you can offer the LLM a choice of [tools](https://docs.livekit.io/agents/build/tools.md) to use in its response. These tools can be as generic or as specific as needed for your use case.
For instance, define tools for `search_calendar`, `create_event`, `update_event`, and `delete_event` to give the LLM complete access to the user's calendar. Use [participant attributes](https://docs.livekit.io/transport/data/state/participant-attributes.md) or [job metadata](https://docs.livekit.io/agents/server/job.md#metadata) to pass the user's calendar ID and access tokens to the agent.
- **[Tool definition and use](https://docs.livekit.io/agents/build/tools.md)**: Guide to defining and using custom tools in LiveKit Agents.
## Add context during conversation
You can use the [on_user_turn_completed node](https://docs.livekit.io/agents/build/nodes.md#on_user_turn_completed) to perform a RAG lookup based on the user's most recent turn, prior to the LLM generating a response. This method can be highly performant as it avoids the extra round-trips involved in tool calls, but it's only available for STT-LLM-TTS pipelines that have access to the user's turn in text form. Additionally, the results are only as good as the accuracy of the search function you implement.
For instance, you can use vector search to retrieve additional context relevant to the user's query and inject it into the chat context for the next LLM generation. Here is a simple example:
**Python**:
```python
from livekit.agents import ChatContext, ChatMessage
async def on_user_turn_completed(
self, turn_ctx: ChatContext, new_message: ChatMessage,
) -> None:
# RAG function definition omitted for brevity
rag_content = await my_rag_lookup(new_message.text_content())
turn_ctx.add_message(
role="assistant",
content=f"Additional information relevant to the user's next message: {rag_content}"
)
```
---
**Node.js**:
```typescript
import { voice, llm } from '@livekit/agents';
class RagAgent extends voice.Agent {
async onUserTurnCompleted(
turnCtx: llm.ChatContext,
newMessage: llm.ChatMessage,
): Promise {
// RAG function definition omitted for brevity
const ragContent = await myRagLookup(newMessage.textContent);
turnCtx.addMessage({
role: 'assistant',
content: `Additional information relevant to the user's next message: ${ragContent}`,
});
}
}
```
## User feedback
It’s important to provide users with direct feedback about status updates—for example, to explain a delay or failure. Here are a few example use cases:
- When an operation takes more than a few hundred milliseconds.
- When performing write operations such as sending an email or scheduling a meeting.
- When the agent is unable to perform an operation.
The following section describes various techniques to provide this feedback to the user.
### Verbal status updates
Use [Agent speech](https://docs.livekit.io/agents/build/speech.md) to provide verbal feedback to the user during a long-running tool call or other operation.
In the following example, the agent speaks a status update only if the call takes longer than a specified timeout. The update is dynamically generated based on the query, and could be extended to include an estimate of the remaining time or other information.
**Python**:
```python
import asyncio
from livekit.agents import function_tool, RunContext
@function_tool()
async def search_knowledge_base(
self,
context: RunContext,
query: str,
) -> str:
# Send a verbal status update to the user after a short delay
async def _speak_status_update(delay: float = 0.5):
await asyncio.sleep(delay)
await context.session.generate_reply(instructions=f"""
You are searching the knowledge base for \"{query}\" but it is taking a little while.
Update the user on your progress, but be very brief.
""")
status_update_task = asyncio.create_task(_speak_status_update(0.5))
# Perform search (function definition omitted for brevity)
result = await _perform_search(query)
# Cancel status update if search completed before timeout
status_update_task.cancel()
return result
```
---
**Node.js**:
```typescript
import { llm, Task } from '@livekit/agents';
import { z } from 'zod';
const searchKnowledgeBase = llm.tool({
description: 'Search the knowledge base for information',
parameters: z.object({
query: z.string(),
}),
execute: async ({ query }, { ctx, abortSignal }) => {
// Send a verbal status update to the user after a short delay
const speakStatusUpdate = async (controller: AbortController) => {
await new Promise(resolve => setTimeout(resolve, 500));
if (!controller.signal.aborted) {
ctx.session.generateReply({
instructions: `You are searching the knowledge base for "${query}" but it is taking a little while. Update the user on your progress, but be very brief.`,
});
}
};
const statusUpdateTask = Task.from(speakStatusUpdate);
// Perform search (function definition omitted for brevity)
const result = await performSearch(query);
// Cancel status update if search completed before timeout
statusUpdateTask.cancel()
return result;
},
});
```
For more information, see the following article:
- **[Agent speech](https://docs.livekit.io/agents/build/speech.md)**: Explore the speech capabilities and features of LiveKit Agents.
### "Thinking" sounds
Add [background audio](https://docs.livekit.io/agents/build/audio.md#background-audio) to play a "thinking" sound automatically when tool calls are ongoing. This can be useful to provide a more natural feel to the agent's responses.
**Python**:
```python
from livekit.agents import AgentServer, BackgroundAudioPlayer, AudioConfig, BuiltinAudioClip
server = AgentServer()
@server.rtc_session()
async def my_agent(ctx: agents.JobContext):
session = AgentSession(
# ... stt, llm, tts, vad, turn_detection, etc.
)
await session.start(
room=ctx.room,
# ... agent, etc.
)
background_audio = BackgroundAudioPlayer(
thinking_sound=[
AudioConfig(BuiltinAudioClip.KEYBOARD_TYPING, volume=0.8),
AudioConfig(BuiltinAudioClip.KEYBOARD_TYPING2, volume=0.7),
],
)
await background_audio.start(room=ctx.room, agent_session=session)
```
---
**Node.js**:
```typescript
import { type JobContext, defineAgent, log, voice } from '@livekit/agents';
export default defineAgent({
entry: async (ctx: JobContext) => {
const logger = log();
await ctx.connect();
logger.info('Connected to room');
const agent = new voice.Agent({
instructions: 'You are a helpful assistant',
// ... tools, etc.
});
const session = new voice.AgentSession({
// ... stt, llm, tts, vad, turn_detection, etc.
});
await session.start({ agent, room: ctx.room });
const backgroundAudio = new voice.BackgroundAudioPlayer({
thinkingSound: [
{ source: voice.BuiltinAudioClip.KEYBOARD_TYPING, volume: 0.8, probability: 0.6 },
{ source: voice.BuiltinAudioClip.KEYBOARD_TYPING2, volume: 0.7, probability: 0.4 },
],
});
await backgroundAudio.start({ room: ctx.room, agentSession: session });
// Play another audio file at any time using the play method:
// backgroundAudio.play('filepath.ogg');
},
});
```
For a complete example, see the following:
- **[Background audio](https://github.com/livekit/agents/blob/main/examples/voice_agents/background_audio.py)**: Guide to using background audio in your agent in Python.
- **[Background audio](https://github.com/livekit/agents-js/blob/main/examples/src/background_audio.ts)**: Guide to using background audio in your agent in Node.js.
### Frontend UI
If your app includes a frontend, you can add custom UI to represent the status of the agent's operations. For instance, present a popup for a long-running operation that the user can optionally cancel:
**Python**:
```python
from livekit.agents import get_job_context
import json
import asyncio
@function_tool()
async def perform_deep_search(
self,
context: RunContext,
summary: str,
query: str,
) -> str:
"""
Initiate a deep internet search that will reference many external sources to answer the given query. This may take 1-5 minutes to complete.
Summary: A user-friendly summary of the query
Query: the full query to be answered
"""
async def _notify_frontend(query: str):
room = get_job_context().room
response = await room.local_participant.perform_rpc(
destination_identity=next(iter(room.remote_participants)),
# frontend method that shows a cancellable popup
# (method definition omitted for brevity, see RPC docs)
method='start_deep_search',
payload=json.dumps({
"summary": summary,
"estimated_completion_time": 300,
}),
# Allow the frontend a long time to return a response
response_timeout=500,
)
# In this example the frontend has a Cancel button that returns "cancelled"
# to stop the task
if response == "cancelled":
deep_search_task.cancel()
notify_frontend_task = asyncio.create_task(_notify_frontend(query))
# Perform deep search (function definition omitted for brevity)
deep_search_task = asyncio.create_task(_perform_deep_search(query))
try:
result = await deep_search_task
except asyncio.CancelledError:
result = "Search cancelled by user"
finally:
notify_frontend_task.cancel()
return result
```
---
**Node.js**:
```typescript
import { llm, Task, getJobContext } from '@livekit/agents';
import { z } from 'zod';
const performDeepSearch = llm.tool({
description: 'Initiate a deep internet search that will reference many external sources to answer the given query. This may take 1-5 minutes to complete.',
parameters: z.object({
summary: z.string(),
query: z.string(),
}),
execute: async ({ summary, query }, { ctx }) => {
// Notify frontend with cancellable popup
const notifyFrontend = async (controller: AbortController) => {
const room = getJobContext().room;
const participant = Array.from(room.remoteParticipants.values())[0]!;
const response = await room.localParticipant!.performRpc({
destinationIdentity: participant.identity,
// frontend method that shows a cancellable popup
// (method definition omitted for brevity, see RPC docs)
method: 'start_deep_search',
payload: JSON.stringify({
summary,
estimated_completion_time: 300,
}),
// Allow the frontend a long time to return a response
responseTimeout: 500000,
});
// In this example the frontend has a Cancel button that returns "cancelled"
// to stop the task
if (response === "cancelled") {
deepResearchTask.cancel();
}
};
const notifyTask = Task.from(notifyFrontend);
// Perform deep search (function definition omitted for brevity)
const deepResearchTask = Task.from((controller) => performDeepSearch(query, controller));
let result = "";
try {
result = await deepResearchTask.result;
} catch (error) {
result = "Search cancelled by user";
} finally {
notifyTask.cancel();
return result;
}
},
});
```
For more information and examples, see the following articles:
- **[Web and mobile frontends](https://docs.livekit.io/agents/start/frontend.md)**: Guide to building a custom web or mobile frontend for your agent.
- **[RPC](https://docs.livekit.io/transport/data/rpc.md)**: Learn how to use RPC to communicate with your agent from the frontend.
## Fine-tuned models
Sometimes the best way to get the most relevant results is to fine-tune a model for your specific use case. You can explore the available [LLM plugins](https://docs.livekit.io/agents/models/llm.md#plugins) to find a provider that supports fine-tuning, or use [Ollama](https://docs.livekit.io/agents/models/llm/plugins/ollama.md) to integrate a custom model.
## External services
Many providers offer services to provide memory or other capabilities to your agents. Some suggested services that work well with LiveKit Agents include:
- **[Letta plugin](https://docs.livekit.io/agents/models/llm/plugins/letta.md)**: Build and deploy stateful AI agents that maintain memory and context across long-running conversations.
- **[AgentMail](https://docs.agentmail.to/integrate-livekit-agents)**: Give your agents their own email inboxes.
- **[LlamaIndex](https://www.llamaindex.ai/)**: Framework for connecting custom data to LLMs.
- **[Mem0](https://mem0.ai)**: Self-improving memory layer for AI agents.
## Additional examples
The following examples show how to implement RAG and other techniques:
- **[LlamaIndex RAG](https://github.com/livekit/agents/tree/main/examples/voice_agents/llamaindex-rag)**: A voice AI agent that uses LlamaIndex for RAG to answer questions from a knowledge base.
---
This document was rendered at 2026-02-03T03:24:57.270Z.
For the latest version of this document, see [https://docs.livekit.io/agents/logic/external-data.md](https://docs.livekit.io/agents/logic/external-data.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/stt/plugins/fal.md
LiveKit docs › Models › STT › Plugins › FAL
---
# fal STT plugin guide
> How to use the fal STT plugin for LiveKit Agents.
Available in:
- [ ] Node.js
- [x] Python
## Overview
This plugin allows you to use [fal](https://fal.ai/) as an STT provider for your voice agents.
## Quick reference
This section includes a basic usage example and some reference material. For links to more detailed documentation, see [Additional resources](#additional-resources).
### Installation
Install the plugin from PyPI:
```shell
uv add "livekit-agents[fal]~=1.3"
```
### Authentication
The fal plugin requires a [fal API key](https://fal.ai/dashboard/keys).
Set `FAL_KEY` in your `.env` file.
### Usage
Use fal STT in an `AgentSession` or as a standalone transcription service. For example, you can use this STT in the [Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md).
```python
from livekit.plugins import fal
session = AgentSession(
stt = fal.STT(
language="de",
),
# ... llm, tts, etc.
)
```
### Parameters
This section describes some of the available parameters. See the [plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/fal/index.html.md#livekit.plugins.fal.STT) for a complete list of all available parameters.
- **`language`** _(str)_ (optional) - Default: `en`: Speech recognition language.
## Additional resources
The following resources provide more information about using fal with LiveKit Agents.
- **[Python package](https://pypi.org/project/livekit-plugins-fal/)**: The `livekit-plugins-fal` package on PyPI.
- **[Plugin reference](https://docs.livekit.io/reference/python/v1/livekit/plugins/fal/index.html.md#livekit.plugins.fal.STT)**: Reference for the fal STT plugin.
- **[GitHub repo](https://github.com/livekit/agents/tree/main/livekit-plugins/livekit-plugins-fal)**: View the source or contribute to the LiveKit fal STT plugin.
- **[fal docs](https://fal.ai/docs)**: fal's full docs site.
- **[Voice AI quickstart](https://docs.livekit.io/agents/start/voice-ai.md)**: Get started with LiveKit Agents and fal.
---
This document was rendered at 2026-02-03T03:25:03.279Z.
For the latest version of this document, see [https://docs.livekit.io/agents/models/stt/plugins/fal.md](https://docs.livekit.io/agents/models/stt/plugins/fal.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/telephony/features.md
LiveKit docs › Features › Overview
---
# Telephony features overview
> An overview of telephony features for LiveKit.
## Overview
LiveKit telephony includes advanced features for call handling, audio quality, security, and compliance. Use these features to build production-ready telephony applications with enhanced call quality, secure communications, and regulatory compliance.
## Telephony features
Enhance your telephony applications with advanced call handling, audio quality, security, and compliance features.
| Feature | Description | Use cases |
| **DTMF** | Support for Dual-tone Multi-Frequency (DTMF) tones, enabling integration with legacy IVR systems and allowing agents to receive DTMF input from callers. | IVR system integration, menu navigation, and collecting numeric input from callers. |
| **Region pinning** | Restrict network traffic to specific geographical regions to comply with local telephony regulations or data residency requirements. | Regulatory compliance, data residency requirements, and regional data isolation. |
| **Transfers** | Transfer calls between participants, including call forwarding and agent-assisted transfers for seamless call routing. | Call center workflows, call escalation, and transferring calls between agents or departments. |
| **HD voice** | Support for high-fidelity audio using wideband codecs for superior call quality compared to traditional PSTN calls. | High-quality voice applications, professional call centers, and applications requiring clear audio. |
| **Secure trunking** | Encrypt signaling and media traffic using TLS and SRTP to protect calls from eavesdropping and man-in-the-middle attacks. | Secure communications, compliance requirements, and protecting sensitive call data. |
## In this section
Read more about each telephony feature.
- **[DTMF](https://docs.livekit.io/telephony/features/dtmf.md)**: Send and receive DTMF tones for integration with IVR systems.
- **[Region pinning](https://docs.livekit.io/telephony/features/region-pinning.md)**: Isolate LiveKit traffic to specific regions for compliance.
- **[Transfers](https://docs.livekit.io/telephony/features/transfers.md)**: Transfer calls between participants and agents.
- **[HD voice](https://docs.livekit.io/telephony/features/hd-voice.md)**: Enable high-fidelity audio for superior call quality.
- **[Secure trunking](https://docs.livekit.io/telephony/features/secure-trunking.md)**: Encrypt signaling and media traffic for secure calls.
---
This document was rendered at 2026-02-03T03:25:10.840Z.
For the latest version of this document, see [https://docs.livekit.io/telephony/features.md](https://docs.livekit.io/telephony/features.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/deploy/admin/firewall.md
LiveKit docs › Administration › Configuring firewalls
---
# Configuring firewalls
> Learn how to configure firewalls for LiveKit Cloud.
## Corporate firewalls
LiveKit uses WebSocket and WebRTC to transmit data and media. All transmissions are encrypted with [TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security) and [DTLS](https://en.wikipedia.org/wiki/Datagram_Transport_Layer_Security).
LiveKit Cloud requires access to a few domains in order to establish a connection. If you are behind a corporate firewall, please ensure outbound traffic is allowed to the following addresses and ports:
| Host | Port | Purpose |
| *.livekit.cloud | TCP: 443 | Signal connection over secure WebSocket |
| *.turn.livekit.cloud | TCP: 443 | [TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT)/TLS. Used when UDP connection isn't viable |
| *.host.livekit.cloud | UDP: 3478 | TURN/UDP servers that assist in establishing connectivity |
| all hosts (optional) | UDP: 50000-60000 | UDP connection for WebRTC |
| all hosts (optional) | TCP: 7881 | TCP connection for WebRTC |
In order to obtain the best audio and video quality, we recommend allowing access to the UDP ports listed above. Additionally, please ensure UDP hole-punching is enabled (or disable symmetric NAT). This helps machines behind the firewall to establish a direct connection to a LiveKit Cloud media server.
## Minimum requirements
If wildcard hostnames are not allowed by your firewall or security policy, the following are the mimimum set of hostnames required to connect to LiveKit Cloud:
| Host | Port |
| `.livekit.cloud` | TCP 443 |
| `.sfo3.production.livekit.cloud` | TCP 443 |
| `.dsfo3a.production.livekit.cloud` | TCP 443 |
| `.dsfo3b.production.livekit.cloud` | TCP 443 |
| `.dfra1a.production.livekit.cloud` | TCP 443 |
| `.dfra1b.production.livekit.cloud` | TCP 443 |
| `.dblr1a.production.livekit.cloud` | TCP 443 |
| `.dblr1b.production.livekit.cloud` | TCP 443 |
| `.dsgp1a.production.livekit.cloud` | TCP 443 |
| `.dsgp1b.production.livekit.cloud` | TCP 443 |
| `.dsyd1a.production.livekit.cloud` | TCP 443 |
| `.dsyd1b.production.livekit.cloud` | TCP 443 |
| `.osaopaulo1a.production.livekit.cloud` | TCP 443 |
| `.osaopaulo1b.production.livekit.cloud` | TCP 443 |
| `.oashburn1a.production.livekit.cloud` | TCP 443 |
| `.oashburn1b.production.livekit.cloud` | TCP 443 |
| `.omarseille1a.production.livekit.cloud` | TCP 443 |
| `.omarseille1b.production.livekit.cloud` | TCP 443 |
| `.otokyo1a.production.livekit.cloud` | TCP 443 |
| `.otokyo1b.production.livekit.cloud` | TCP 443 |
| `.ophoenix1a.production.livekit.cloud` | TCP 443 |
| `.ophoenix1b.production.livekit.cloud` | TCP 443 |
| `.olondon1a.production.livekit.cloud` | TCP 443 |
| `.olondon1b.production.livekit.cloud` | TCP 443 |
| `.ochicago1a.production.livekit.cloud` | TCP 443 |
| `.ochicago1b.production.livekit.cloud` | TCP 443 |
| `.osingapore1a.production.livekit.cloud` | TCP 443 |
| `.osingapore1b.production.livekit.cloud` | TCP 443 |
| `.odubai1a.production.livekit.cloud` | TCP 443 |
| `.odubai1b.production.livekit.cloud` | TCP 443 |
| `.ohyderabad1a.production.livekit.cloud` | TCP 443 |
| `.ohyderabad1b.production.livekit.cloud` | TCP 443 |
| `.ojohannesburg1a.production.livekit.cloud` | TCP 443 |
| `.ojohannesburg1b.production.livekit.cloud` | TCP 443 |
| `.omumbai1a.production.livekit.cloud` | TCP 443 |
| `.omumbai1b.production.livekit.cloud` | TCP 443 |
| `.ofrankfurt1a.production.livekit.cloud` | TCP 443 |
| `.ofrankfurt1b.production.livekit.cloud` | TCP 443 |
| `.ojerusalem1a.production.livekit.cloud` | TCP 443 |
| `.ojerusalem1b.production.livekit.cloud` | TCP 443 |
| `.osydney1a.production.livekit.cloud` | TCP 443 |
| `.osydney1b.production.livekit.cloud` | TCP 443 |
| `.ozurich1a.production.livekit.cloud` | TCP 443 |
| `.ozurich1b.production.livekit.cloud` | TCP 443 |
| `.turn.livekit.cloud` | TCP 443 |
| `sfo3.turn.livekit.cloud` | TCP 443 |
| `dsfo3a.turn.livekit.cloud` | TCP 443 |
| `dsfo3b.turn.livekit.cloud` | TCP 443 |
| `dfra1a.turn.livekit.cloud` | TCP 443 |
| `dfra1b.turn.livekit.cloud` | TCP 443 |
| `dblr1a.turn.livekit.cloud` | TCP 443 |
| `dblr1b.turn.livekit.cloud` | TCP 443 |
| `dsgp1a.turn.livekit.cloud` | TCP 443 |
| `dsgp1b.turn.livekit.cloud` | TCP 443 |
| `dsyd1a.turn.livekit.cloud` | TCP 443 |
| `dsyd1b.turn.livekit.cloud` | TCP 443 |
| `osaopaulo1a.turn.livekit.cloud` | TCP 443 |
| `osaopaulo1b.turn.livekit.cloud` | TCP 443 |
| `oashburn1a.turn.livekit.cloud` | TCP 443 |
| `oashburn1b.turn.livekit.cloud` | TCP 443 |
| `omarseille1a.turn.livekit.cloud` | TCP 443 |
| `omarseille1b.turn.livekit.cloud` | TCP 443 |
| `otokyo1a.turn.livekit.cloud` | TCP 443 |
| `otokyo1b.turn.livekit.cloud` | TCP 443 |
| `ophoenix1a.turn.livekit.cloud` | TCP 443 |
| `ophoenix1b.turn.livekit.cloud` | TCP 443 |
| `olondon1a.turn.livekit.cloud` | TCP 443 |
| `olondon1b.turn.livekit.cloud` | TCP 443 |
| `ochicago1a.turn.livekit.cloud` | TCP 443 |
| `ochicago1b.turn.livekit.cloud` | TCP 443 |
| `osingapore1a.turn.livekit.cloud` | TCP 443 |
| `osingapore1b.turn.livekit.cloud` | TCP 443 |
| `odubai1a.turn.livekit.cloud` | TCP 443 |
| `odubai1b.turn.livekit.cloud` | TCP 443 |
| `ohyderabad1a.turn.livekit.cloud` | TCP 443 |
| `ohyderabad1b.turn.livekit.cloud` | TCP 443 |
| `ojohannesburg1a.turn.livekit.cloud` | TCP 443 |
| `ojohannesburg1b.turn.livekit.cloud` | TCP 443 |
| `omumbai1a.turn.livekit.cloud` | TCP 443 |
| `omumbai1b.turn.livekit.cloud` | TCP 443 |
| `ofrankfurt1a.turn.livekit.cloud` | TCP 443 |
| `ofrankfurt1b.turn.livekit.cloud` | TCP 443 |
| `ojerusalem1a.turn.livekit.cloud` | TCP 443 |
| `ojerusalem1b.turn.livekit.cloud` | TCP 443 |
| `osydney1a.turn.livekit.cloud` | TCP 443 |
| `osydney1b.turn.livekit.cloud` | TCP 443 |
| `ozurich1a.turn.livekit.cloud` | TCP 443 |
| `ozurich1b.turn.livekit.cloud` | TCP 443 |
> ℹ️ **Note**
>
> This list of domains is subject to change. Last updated 2025-06-27.
## Static IPs
Static IPs are currently available for the following regions:
| Region | IP blocks |
| EU | `143.223.88.0/21` `161.115.160.0/19` |
| India | `143.223.88.0/21` `161.115.160.0/19` |
| US | `143.223.88.0/21` `161.115.160.0/19` |
> ℹ️ **Note**
>
> All other regions must use wildcard domains.
Static IPs apply to the following services:
- Realtime
- SIP signalling and media
- Webhooks
---
This document was rendered at 2026-02-03T03:25:23.739Z.
For the latest version of this document, see [https://docs.livekit.io/deploy/admin/firewall.md](https://docs.livekit.io/deploy/admin/firewall.md).
To explore all LiveKit documentation, see [llms.txt](https://docs.livekit.io/llms.txt).
---
# Source: https://docs.livekit.io/agents/models/llm/plugins/fireworks.md
LiveKit docs › Models › LLM › Plugins › Fireworks
---
# Fireworks AI LLM plugin guide
> How to use Fireworks AI with LiveKit Agents.
Available in:
- [x] Node.js
- [x] Python
## Overview
This plugin allows you to use [Fireworks AI](https://fireworks.ai/) as an LLM provider for your voice agents. Fireworks AI compatibility is provided by the OpenAI plugin using the Fireworks AI Chat Completions API.
## Usage
Install the OpenAI plugin to add Fireworks AI support:
**Python**:
```shell
uv add "livekit-agents[openai]~=1.3"
```
---
**Node.js**:
```shell
pnpm add @livekit/agents-plugin-openai@1.x
```
Set the following environment variable in your `.env` file:
```shell
FIREWORKS_API_KEY=