Daily

Connect your own voice bot — built with Pipecat or anything that can join a Daily 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.

Prefer LiveKit as your transport? The model is identical — see 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:
ModeRoom managementWho creates the room + mints tokensWhose Daily keyWhat you implement
You create roomsclientYouYours (stays on your side)An endpoint that creates a room and returns a join token for our caller
We create roomsrubricRubricHQYours, stored in RubricHQAn endpoint that joins a room we created

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

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 defaultSettings → Integrations → Transport Providers → Daily: API Key. Used by every agent that doesn’t override.
  2. Per-agent overrideAgent → Channels → Web → “We create rooms” → Daily API key. Overrides the workspace default for that one agent.

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

FieldValue
Room managementYou create rooms (client)
Bot Runner URLYour 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

1POST {bot_runner_url}
2Content-Type: application/json
3
4{
5 "rubric_run_id": "1234",
6 "metadata": {
7 "scenario_id": 8,
8 "scenario_name": "Cooperative Upbeat Payment Flow",
9 "agent_id": 1
10 }
11}

What you return (immediately, in the HTTP response)

1{
2 "room_url": "https://your-domain.daily.co/client-459ff0f5",
3 "token": "<a Daily meeting token for rubric-test-caller>"
4}

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)

1# POST /api/create-and-join-daily
2import secrets, time, httpx
3
4DAILY = "https://api.daily.co/v1"
5H = {"Authorization": f"Bearer {DAILY_API_KEY}", "Content-Type": "application/json"}
6
7async def create_and_join_daily(req):
8 room_name = f"client-{secrets.token_hex(4)}"
9 exp = int(time.time()) + 3600
10 async with httpx.AsyncClient(timeout=15) as c:
11 r = await c.post(f"{DAILY}/rooms", headers=H, json={
12 "name": room_name, "privacy": "private",
13 "properties": {"exp": exp, "max_participants": 10},
14 })
15 room_url = r.json()["url"]
16
17 async def mint(user):
18 t = await c.post(f"{DAILY}/meeting-tokens", headers=H, json={
19 "properties": {"room_name": room_name, "user_name": user, "exp": exp}})
20 return t.json()["token"]
21
22 bot_token, caller_token = await mint("pipecat-bot"), await mint("rubric-test-caller")
23
24 start_your_bot_in(room_url, bot_token) # your agent joins
25 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

FieldValue
Room managementWe create rooms (rubric)
Web Call URL / Bot Runner URLYour join endpoint, e.g. https://your-bot.example.com (we call …/api/session)
Daily API keyper-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

1POST {web_call_url}/api/session
2Content-Type: application/json
3
4{
5 "room_url": "https://your-domain.daily.co/rubric-188-aaa02027",
6 "token": "<a Daily meeting token for pipecat-bot>"
7}

What you return

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

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)

1# POST /api/session — join a Daily room RubricHQ already created
2async def create_session(req):
3 start_your_bot(room_url=req["room_url"], token=req["token"])
4 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 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.

1{
2 "enable_recording": "cloud",
3 "enable_dialout": false,
4 "lang": "en",
5 "start_audio_off": false,
6 "eject_at_room_exp": true
7}

Recording

enable_recording controls Daily-side recording. Allowed values:

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

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

KeyTypeDescription
enable_recordingstringDaily-side recording mode (see above).
max_participantsintMax simultaneous participants (RubricHQ sets a default).
exp / nbfint (unix ts)Room expiry / not-before (RubricHQ sets exp).
eject_at_room_expboolEject everyone when the room expires.
eject_after_elapsedint (sec)Eject a participant N seconds after they join.
langstringPrebuilt UI language (en, es, fr, de, …, or user).
geostringPreferred server region for signaling/media.
start_audio_offboolJoin with mic muted.
start_video_offboolJoin with camera off.
enable_chatboolEnable in-call chat (default false).
enable_screenshareboolAllow screen sharing (default true).
enable_knockingboolLobby with knock/admit for private rooms.
enable_prejoin_uiboolWaiting room with device checks.
enable_dialin / enable_dialoutboolAllow PSTN dial-in / dial-out.
enable_transcription_storageboolSave Daily transcriptions as WebVTT.
permissionsobjectDefault permissions for non-owner participants.
recordings_bucketobjectCustom S3 bucket for Daily cloud recordings.

This is the testing-relevant subset; any key from the Daily room config reference is accepted and passed through.

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

1

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.

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.