Skip to content

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

text
┌──────────────┐
│   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_name of call_<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:

bash
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:

json
{
  "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 token and url to 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:

bash
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:

FieldDescription
idUnique participant ID
call_idParent call
roleagent, customer, or observer
transportwebrtc, pstn, sip, or internal
identityBrowser identity string or E.164 phone number
statusjoining, active, left, failed
joined_atWhen the participant connected
left_atWhen the participant disconnected

Participant-Attributed Transcripts

All transcript entries include who said what:

bash
curl https://api.rymi.live/v1/calls/call_abc123/transcript \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "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:

FieldDescription
providerplivo, twilio, or vonage
provider_call_idThe external provider's call identifier
directioninbound or outbound
statusqueued, ringing, in_progress, completed, failed
from_numberCaller ID
to_numberDestination
bill_durationBillable 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:

  1. The LiveKit room closes
  2. Post-call intelligence is triggered (if transcription was enabled)
  3. Recordings are finalized (if recording was enabled)

SDK Usage

Browser SDK

typescript
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

typescript
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);