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

# Daily

Connect your own voice bot — built with [Pipecat](https://docs.pipecat.ai) or anything that can join a [Daily](https://docs.daily.co) room — and let RubricHQ's simulated caller talk to it over real audio.

Both parties join **one Daily 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 LiveKit as your transport? The model is identical — see [LiveKit](/integrations/livekit).

---

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

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

* A **room** (a `https://<domain>.daily.co/<name>` URL) is one call/session. A **meeting token** authorizes one identity to join one room.
* **Creating rooms and minting meeting tokens requires your Daily API key.** **Joining a room requires only the room URL + a meeting 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 Daily key            | What you implement                                                      |
| -------------------- | --------------- | ----------------------------------- | -------------------------- | ----------------------------------------------------------------------- |
| **You create rooms** | `client`        | **You**                             | Yours (stays 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                            |

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

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

---

## Credentials (for "We create rooms")

Provide your Daily **API key** in either place — RubricHQ resolves **per-agent override → workspace default → error**:

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

> **Tip**
>
> The API key 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`)

You own the Daily account; RubricHQ never needs your key.

**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-daily` |

**Sequence**

1. RubricHQ POSTs to your **Bot Runner URL** with the run context.
2. Your service **creates a Daily room**, starts your bot in it, and **returns a meeting token** for our caller.
3. RubricHQ's caller joins that room with the `room_url` + `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_url": "https://your-domain.daily.co/client-459ff0f5",
  "token":    "<a Daily meeting token for rubric-test-caller>"
}
```

> **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-daily
import secrets, time, httpx

DAILY = "https://api.daily.co/v1"
H = {"Authorization": f"Bearer {DAILY_API_KEY}", "Content-Type": "application/json"}

async def create_and_join_daily(req):
    room_name = f"client-{secrets.token_hex(4)}"
    exp = int(time.time()) + 3600
    async with httpx.AsyncClient(timeout=15) as c:
        r = await c.post(f"{DAILY}/rooms", headers=H, json={
            "name": room_name, "privacy": "private",
            "properties": {"exp": exp, "max_participants": 10},
        })
        room_url = r.json()["url"]

        async def mint(user):
            t = await c.post(f"{DAILY}/meeting-tokens", headers=H, json={
                "properties": {"room_name": room_name, "user_name": user, "exp": exp}})
            return t.json()["token"]

        bot_token, caller_token = await mint("pipecat-bot"), await mint("rubric-test-caller")

    start_your_bot_in(room_url, bot_token)        # your agent joins
    return {"room_url": room_url, "token": caller_token}
```

---

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

RubricHQ creates the Daily room with your API key, mints a token for your bot and one for our caller, then POSTs your join endpoint so your bot enters the room.

**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`) |
| Daily API key                 | per-agent or workspace                                                            |

**Sequence**

1. RubricHQ creates a Daily room and mints a `bot_token` (for your bot) and a caller token (for our caller).
2. RubricHQ POSTs your join endpoint with the `room_url` and the `bot_token`.
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_url": "https://your-domain.daily.co/rubric-188-aaa02027",
  "token":    "<a Daily meeting token for pipecat-bot>"
}
```

**What you return**

```json
{ "status": "started", "room_name": "https://your-domain.daily.co/rubric-188-aaa02027" }
```

> **Note**
>
> Your bot doesn't need its own Daily key here — it joins **our** room with the **token we minted**. It only needs the `room_url` and `token` from the request.

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

```python
# POST /api/session  — join a Daily room RubricHQ already created
async def create_session(req):
    start_your_bot(room_url=req["room_url"], token=req["token"])
    return {"status": "started", "room_name": req["room_url"]}
```

---

## Optional room config — "Room Properties (JSON)"

When **RubricHQ creates the room** ("We create rooms"), you can pass any Daily room [`properties`](https://docs.daily.co/reference/rest-api/rooms/config) as JSON under **Agent → Channels → Web → Room Properties (JSON)**. They're merged into the room's `properties`; RubricHQ's required keys (`exp`, `max_participants`) take precedence.

```json
{
  "enable_recording": "cloud",
  "enable_dialout": false,
  "lang": "en",
  "start_audio_off": false,
  "eject_at_room_exp": true
}
```

### Recording

`enable_recording` controls Daily-side recording. Allowed values:

| Value                | Result                                                                                       |
| -------------------- | -------------------------------------------------------------------------------------------- |
| `"cloud"`            | Daily records to its cloud (composite). Requires a Daily plan that includes cloud recording. |
| `"cloud-audio-only"` | Audio-only cloud recording.                                                                  |
| `"raw-tracks"`       | Per-participant raw track recordings.                                                        |
| `"local"`            | Client-side local recording.                                                                 |
| *(omit)*             | No Daily-side recording.                                                                     |

When Daily-side cloud recording is enabled, RubricHQ also fetches the **vendor's own recording link** after the call and surfaces it on the run, alongside our capture.

> **Note**
>
> Whatever you set here, RubricHQ always captures its **own speaker-separated recording** of the call for analysis, so transcript and audio metrics work even if you leave Daily-side recording off. This JSON is for *your* room preferences (keeping a vendor-side copy, language, timeouts, etc.).

### Commonly useful properties

| Key                                | Type          | Description                                                  |
| ---------------------------------- | ------------- | ------------------------------------------------------------ |
| `enable_recording`                 | string        | Daily-side recording mode (see above).                       |
| `max_participants`                 | int           | Max simultaneous participants (RubricHQ sets a default).     |
| `exp` / `nbf`                      | int (unix ts) | Room expiry / not-before (RubricHQ sets `exp`).              |
| `eject_at_room_exp`                | bool          | Eject everyone when the room expires.                        |
| `eject_after_elapsed`              | int (sec)     | Eject a participant N seconds after they join.               |
| `lang`                             | string        | Prebuilt UI language (`en`, `es`, `fr`, `de`, …, or `user`). |
| `geo`                              | string        | Preferred server region for signaling/media.                 |
| `start_audio_off`                  | bool          | Join with mic muted.                                         |
| `start_video_off`                  | bool          | Join with camera off.                                        |
| `enable_chat`                      | bool          | Enable in-call chat (default `false`).                       |
| `enable_screenshare`               | bool          | Allow screen sharing (default `true`).                       |
| `enable_knocking`                  | bool          | Lobby with knock/admit for private rooms.                    |
| `enable_prejoin_ui`                | bool          | Waiting room with device checks.                             |
| `enable_dialin` / `enable_dialout` | bool          | Allow PSTN dial-in / dial-out.                               |
| `enable_transcription_storage`     | bool          | Save Daily transcriptions as WebVTT.                         |
| `permissions`                      | object        | Default permissions for non-owner participants.              |
| `recordings_bucket`                | object        | Custom S3 bucket for Daily cloud recordings.                 |

This is the testing-relevant subset; any key from the [Daily room config reference](https://docs.daily.co/reference/rest-api/rooms/config) is accepted and passed through.

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

---

## Choosing a mode

### Do you want RubricHQ to hold your Daily API key?

* **No** → use **You create rooms** (`client`). Implement `create-and-join-daily`. We never see your key.
* **Yes** → use **We create rooms** (`rubric`). Implement `POST /api/session` to join the given room.

---

## 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-daily` (client mode) and `POST /api/session` (rubric mode). Everything else — recording, transcription, metrics — is handled by RubricHQ.