> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.rubrichq.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.rubrichq.io/_mcp/server.

# LiveKit

Connect your own voice bot — built with [Pipecat](https://docs.pipecat.ai), the [LiveKit Agents](https://docs.livekit.io/agents) framework, or anything that can join a LiveKit room — and let RubricHQ's simulated caller talk to it over real audio.

Both parties join **one LiveKit room**:

* **`rubric-test-caller`** — RubricHQ's AI caller, running your scenario.
* **your agent-under-test** — the bot you're testing.

They publish and subscribe to each other's audio; RubricHQ records the call and produces transcript + audio metrics.

> **Tip**
>
> Prefer Daily as your transport? The model is identical — see [Daily](/integrations/daily).

---

## The model: rooms, tokens, and who creates them

A few LiveKit facts that decide how the integration is wired:

* A **room** is one call/session. A **token** is a JWT that authorizes one identity to join one room.
* **Minting a token requires the LiveKit project's API key + secret** (the token is signed with the secret; its `iss` is the API key). **Joining a room requires only the room URL + a token** — never the API key.
* So the only real question is **who creates the room and mints the tokens.** That gives two modes:

| Mode                 | Room management | Who creates the room + mints tokens | Whose LiveKit creds       | What you implement                                                                       |
| -------------------- | --------------- | ----------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------- |
| **You create rooms** | `client`        | **You**                             | Yours (stay on your side) | An endpoint that creates a room and returns a join token for our caller                  |
| **We create rooms**  | `rubric`        | **RubricHQ**                        | Yours, stored in RubricHQ | An endpoint that **joins** a room we created (or a registered LiveKit agent we dispatch) |

Configure this under **Agent → Channels → Web**, provider **LiveKit**.

> **Note**
>
> Credentials are **only needed for "We create rooms"** mode — because that's where *we* mint the tokens. In "You create rooms" mode we never see your API key; you hand us a scoped token.

---

## Credentials (for "We create rooms")

Provide your LiveKit project credentials in either place — RubricHQ resolves **per-agent override → workspace default → error**:

1. **Workspace default** — **Settings → Integrations → Transport Providers → LiveKit**: `URL`, `API Key`, `API Secret`. Used by every agent that doesn't override.
2. **Per-agent override** — **Agent → Channels → Web → "We create rooms" → LiveKit credentials**. Overrides the workspace default for that one agent.

> **Tip**
>
> The API **Secret** is write-only: once saved it shows **"Configuration saved"** and is never sent back to the browser. Leave it untouched to keep it; click **Edit** to replace it.

---

## Mode 1 — You create rooms (`client`)

Recommended for most custom/Pipecat bots. You own the LiveKit project; RubricHQ never needs your keys.

**Config**

| Field           | Value                                                                  |
| --------------- | ---------------------------------------------------------------------- |
| Room management | `You create rooms` (`client`)                                          |
| Bot Runner URL  | Your endpoint, e.g. `https://your-bot.example.com/api/create-and-join` |

**Sequence**

1. RubricHQ POSTs to your **Bot Runner URL** with the run context.
2. Your service **creates a LiveKit room**, starts your bot in it, and **returns a join token** for our caller.
3. RubricHQ's caller joins that room with the token you returned, and the conversation runs.

**What we send → your Bot Runner URL**

```json
POST {bot_runner_url}
Content-Type: application/json

{
  "rubric_run_id": "1234",
  "metadata": {
    "scenario_id": 8,
    "scenario_name": "Cooperative Upbeat Payment Flow",
    "agent_id": 1
  }
}
```

**What you return (immediately, in the HTTP response)**

```json
{
  "room_name": "client-33791bbd",
  "room_url":  "wss://your-project.livekit.cloud",
  "token":     "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```

The `token` is a join token **you mint** for RubricHQ's caller. Decoded, it looks like:

```json
{
  "name": "rubric-test-caller",
  "video": { "roomJoin": true, "room": "client-33791bbd", "canPublish": true, "canSubscribe": true },
  "iss": "<your-livekit-api-key>"
}
```

> **Note**
>
> The room + token come back **in the HTTP response** — not via a webhook. By the time you respond `200`, the room must exist and the token must be valid, because RubricHQ's caller joins immediately.

**Reference bot runner (`client` mode)**

```python
# POST /api/create-and-join
import secrets
from livekit.api import LiveKitAPI, CreateRoomRequest, AccessToken, VideoGrants

async def create_and_join(req):
    room = f"client-{secrets.token_hex(4)}"
    async with LiveKitAPI(url=LK_URL, api_key=LK_KEY, api_secret=LK_SECRET) as lk:
        await lk.room.create_room(CreateRoomRequest(name=room, empty_timeout=300))

    # mint a token for OUR caller (you sign it with YOUR key/secret)
    caller_token = (AccessToken(LK_KEY, LK_SECRET)
        .with_identity("rubric-test-caller")
        .with_grants(VideoGrants(room_join=True, room=room, can_publish=True, can_subscribe=True))
        .to_jwt())

    start_your_bot_in(room)  # join the room with your own bot token
    return {"room_name": room, "room_url": LK_URL, "token": caller_token}
```

---

## Mode 2 — We create rooms (`rubric`)

RubricHQ creates the room on your LiveKit project (using the credentials above), mints both tokens, then gets your bot into that room. How we reach your bot depends on the **agent type**:

| Agent type                             | How your bot joins                                         | You implement                                 |
| -------------------------------------- | ---------------------------------------------------------- | --------------------------------------------- |
| **Pipecat**                            | We POST your bot's **join endpoint** with room credentials | `POST /api/session` that joins the given room |
| **Custom**                             | We POST your **Bot Runner URL** with room credentials      | a join endpoint (same shape)                  |
| **LiveKit** (registered Agents worker) | We issue an **explicit dispatch** by `agent_name`          | nothing — your worker auto-receives the job   |

**Config**

| Field                         | Value                                                                             |
| ----------------------------- | --------------------------------------------------------------------------------- |
| Room management               | `We create rooms` (`rubric`)                                                      |
| Web Call URL / Bot Runner URL | Your join endpoint, e.g. `https://your-bot.example.com` (we call `…/api/session`) |
| LiveKit credentials           | URL / API Key / API Secret (per-agent or workspace)                               |
| Agent Name                    | *(LiveKit worker only)* the name to dispatch; leave blank for automatic dispatch  |

**Sequence (Pipecat / Custom)**

1. RubricHQ creates a room on your project and mints a `bot_token` (for your bot) and a `test_caller_token` (for our caller).
2. RubricHQ POSTs your join endpoint with the room name, the `bot_token`, and the room URL.
3. Your bot joins that room with the token we minted; our caller joins too; the conversation runs.

**What we send → your join endpoint**

```json
POST {web_call_url}/api/session
Content-Type: application/json

{
  "room_name":   "rubric-179-ab12cd34",
  "bot_token":   "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "livekit_url": "wss://your-project.livekit.cloud"
}
```

**What you return**

```json
{ "status": "started", "room_name": "rubric-179-ab12cd34" }
```

The `bot_token` we minted for your bot, decoded:

```json
{
  "name": "pipecat-bot",
  "video": { "roomJoin": true, "room": "rubric-179-ab12cd34", "canPublish": true, "canSubscribe": true },
  "iss": "<your-livekit-api-key>"
}
```

> **Note**
>
> Your bot doesn't need its own credentials here — it joins **our** room with the **`bot_token` we minted** (signed by your project's key, so it's valid on your project). It only needs the `room_name`, `bot_token`, and `livekit_url` from the request.

**Reference bot runner (`rubric` mode — Pipecat)**

```python
# POST /api/session  — join a room RubricHQ already created
async def create_session(req):
    start_your_bot(
        room_name=req["room_name"],
        token=req["bot_token"],     # minted by RubricHQ with YOUR project's key
        url=req["livekit_url"],
    )
    return {"status": "started", "room_name": req["room_name"]}
```

### LiveKit explicit dispatch (registered Agents workers)

If your bot is a **registered LiveKit Agents worker**, you don't need a join endpoint. Set **Agent Name** to your worker's name and RubricHQ issues a dispatch into the room it created:

```python
await lk.agent_dispatch.create_dispatch(
    CreateAgentDispatchRequest(agent_name="my-voice-agent", room="rubric-179-ab12cd34",
                               metadata='{"rubric_run_id": 179}')
)
```

Leave **Agent Name** blank to rely on your worker's **automatic dispatch** (it joins every new room on the project).

> **Note**
>
> The dispatch only pulls in an agent if a **worker registered under that `agent_name` is online** and connected to the project. RubricHQ always issues the dispatch correctly (you can confirm it via `lk.agent_dispatch.list_dispatch(room_name=…)`), but if no matching worker is running, only our caller is in the room and the run will have no agent to talk to.

---

## Optional room config — "LiveKit Config (JSON)"

When **RubricHQ creates the room** ("We create rooms"), you can pass extra room-creation options as JSON under **Agent → Channels → Web → LiveKit Config (JSON)**. They're **optional** and forwarded to LiveKit's [`CreateRoom`](https://docs.livekit.io/home/server/managing-rooms/) call; RubricHQ's required keys (the room name) always take precedence.

```json
{
  "empty_timeout": 600,
  "departure_timeout": 120,
  "max_participants": 5,
  "metadata": { "team": "qa", "suite": "billing" },
  "min_playout_delay": 0,
  "max_playout_delay": 0,
  "sync_streams": false,
  "room_preset": "my-preset"
}
```

| Key                 | Type             | Default                 | Description                                                                                                                           |
| ------------------- | ---------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `empty_timeout`     | int (sec)        | `300`                   | How long the room stays open with **no** participants (before the first joins / after a brief empty period) before LiveKit closes it. |
| `departure_timeout` | int (sec)        | LiveKit default (\~20s) | How long the room stays open **after the last** participant leaves.                                                                   |
| `max_participants`  | int              | `10`                    | Maximum simultaneous participants.                                                                                                    |
| `metadata`          | string \| object | —                       | Arbitrary room metadata. Objects are JSON-encoded to a string (LiveKit stores metadata as a string).                                  |
| `min_playout_delay` | int (ms)         | —                       | Minimum jitter-buffer playout delay. Raise to smooth choppy audio; keep low for latency.                                              |
| `max_playout_delay` | int (ms)         | —                       | Maximum jitter-buffer playout delay.                                                                                                  |
| `sync_streams`      | bool             | —                       | Enable A/V stream synchronization on playback.                                                                                        |
| `room_preset`       | string           | —                       | Name of a room preset configured on your LiveKit project; its settings are applied as the base.                                       |

Only the keys above are honored — any other keys are ignored. For the authoritative meaning of each, see the [LiveKit CreateRoom reference](https://docs.livekit.io/reference/server/server-apis/#createroom).

> **Note**
>
> This JSON only applies in **"We create rooms"** mode. In **"You create rooms"** mode *you* create the room, so you set these options yourself in your own `CreateRoom` call.

---

## Choosing a mode

### Do you want RubricHQ to hold your LiveKit credentials?

* **No** → use **You create rooms** (`client`). Implement `create-and-join`. We never see your keys.
* **Yes** → use **We create rooms** (`rubric`).

### In "We create rooms", how is your bot reached?

* **Pipecat / custom bot with an HTTP server** → implement `POST /api/session` to join the given room.
* **Registered LiveKit Agents worker** → set **Agent Name** (or leave blank for auto-dispatch); implement nothing.

---

## Results

When the call ends, the **transcript**, **audio recording** (speaker-separated when available), and **metrics** appear automatically on the run in RubricHQ — your bot runner doesn't need to report anything back. If a call can't proceed (missing/incorrect credentials, room creation failed, or the bot never joins) the run is marked **failed** with the reason rather than left hanging.

> **Tip**
>
> A reference Pipecat bot runner that supports **both** modes only needs two endpoints: `POST /api/create-and-join` (client mode) and `POST /api/session` (rubric mode). Everything else — recording, transcription, metrics — is handled by RubricHQ.