Multi-Participant Calls
Rymi uses a room-centric architecture where every call maps to a single LiveKit room. A call can include multiple WebRTC (browser) and PSTN (phone) participants in the same session, all sharing one AI agent.
How It Works
┌──────────────┐
│ Call Room │ ← One LiveKit room per call
├──────────────┤
│ AI Agent │ ← Always present (one per call in v1)
│ Browser #1 │ ← WebRTC participant
│ Browser #2 │ ← WebRTC participant (added mid-call)
│ Phone +1555 │ ← PSTN participant via telephony leg
└──────────────┘Key rules:
- One call = one room with a deterministic
room_nameofcall_<call_id> - One AI agent per call in v1
- Mixed transport — WebRTC and PSTN participants coexist
- Unified transcript — every transcript entry is attributed to the specific participant who spoke
Creating a Multi-Participant Call
Pass multiple entries in the participants[] array:
curl -X POST https://api.rymi.live/v1/calls \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "550e8400-...",
"participants": [
{ "transport": "webrtc", "identity": "moderator" },
{ "transport": "pstn", "identity": "+15551234567", "from_number": "+15559876543" }
]
}'The response contains per-participant access info:
{
"id": "call_abc123",
"room_name": "call_call_abc123",
"status": "connecting",
"participants": [
{
"id": "p_1",
"transport": "webrtc",
"identity": "moderator",
"status": "joining",
"access": {
"url": "wss://livekit.rymi.live",
"token": "eyJ..."
}
},
{
"id": "p_2",
"transport": "pstn",
"identity": "+15551234567",
"status": "queued",
"telephony_leg_id": "leg_456",
"job_id": "job_789"
}
]
}- WebRTC participants receive a LiveKit
tokenandurlto connect from the browser - PSTN participants are queued to the CallProcessor and dialed asynchronously
Adding Participants to Active Calls
You can add new participants to a call that's already in progress:
curl -X POST https://api.rymi.live/v1/calls/call_abc123/participants \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"participants": [
{ "transport": "webrtc", "identity": "observer" }
]
}'This is useful for:
- Conference-style calls — bringing in a supervisor or observer
- Warm transfers — connecting a browser user to an existing phone call
- Escalation flows — adding a human agent to a call that started with the AI
Participant Tracking
Every participant has a lifecycle tracked in the call_participants table:
| Field | Description |
|---|---|
id | Unique participant ID |
call_id | Parent call |
role | agent, customer, or observer |
transport | webrtc, pstn, sip, or internal |
identity | Browser identity string or E.164 phone number |
status | joining, active, left, failed |
joined_at | When the participant connected |
left_at | When the participant disconnected |
Participant-Attributed Transcripts
All transcript entries include who said what:
curl https://api.rymi.live/v1/calls/call_abc123/transcript \
-H "Authorization: Bearer YOUR_API_KEY"{
"transcript": [
{
"id": "t_1",
"speaker": "user",
"text": "Hello, I'm calling about my order.",
"sequence": 0,
"started_at_ms": 1200,
"ended_at_ms": 3400,
"is_final": true,
"source": "runtime"
},
{
"id": "t_2",
"speaker": "agent",
"text": "I'd be happy to help you with your order. Could you share your order number?",
"sequence": 1,
"source": "runtime"
}
],
"text": "User: Hello, I'm calling about my order.\nAgent: I'd be happy to help you with your order. Could you share your order number?"
}Telephony Legs
PSTN participants have their own telephony_legs record that tracks provider-specific data:
| Field | Description |
|---|---|
provider | plivo, twilio, or vonage |
provider_call_id | The external provider's call identifier |
direction | inbound or outbound |
status | queued, ringing, in_progress, completed, failed |
from_number | Caller ID |
to_number | Destination |
bill_duration | Billable seconds from the provider |
This isolation means telephony billing and lifecycle are tracked independently from the call-level state.
Call Termination
A call reaches terminal state when all non-agent participants have left or failed. The AI agent is always the last to leave. When the call terminates:
- The LiveKit room closes
- Post-call intelligence is triggered (if transcription was enabled)
- Recordings are finalized (if recording was enabled)
SDK Usage
Browser SDK
import { Rymi } from '@rymi/web';
// Your backend creates the call and returns access credentials
const { url, token } = await fetchFromYourBackend();
const call = Rymi.createCall({ url, token });
call.on('transcript', ({ role, text }) => {
// role indicates which participant spoke
console.log(`${role}: ${text}`);
});
await call.connect();Node.js SDK
import { Rymi } from '@rymi/node';
const rymi = new Rymi();
// Create a call with mixed participants
const call = await rymi.calls.create({
agent_id: 'your-agent-id',
participants: [
{ transport: 'webrtc', identity: 'dashboard-user' },
{ transport: 'pstn', identity: '+15551234567', from_number: '+15559876543' }
]
});
// WebRTC participant gets LiveKit access
const webrtcParticipant = call.participants.find(p => p.transport === 'webrtc');
console.log(webrtcParticipant?.access?.token);
