Skip to main content

Voice Client Libraries

Which packages you actually install to build a voice client, and how to wire them to the Voice Session API.

What you install

There is no CallMissed-branded voice package on PyPI or npm. Voice is two plain pieces: a JSON REST call to create the session, and a standard WebRTC client in the browser to carry the audio.

LayerWhat to useWhy
Create a session (server side)Any HTTP client: httpx, requests, fetch, curlPOST /v1/voice/sessions is a plain JSON endpoint
Browser audiolivekit-client (npm)The create response hands you a WebRTC URL and token that this package consumes
Transcripts and session recordsAny HTTP clientPlain JSON GET endpoints

For the text LLM, speech-to-text and text-to-speech APIs there is likewise no bespoke package: those surfaces are OpenAI and Anthropic compatible, so you use the official openai or anthropic SDK with our base URL. See Libraries & SDKs.

Authentication: every REST call on this page takes Authorization: Bearer cm_your_api_key. To create a voice session the key needs the stt, tts and llm permissions; a key missing any of them gets 403.

Step 1: create the session

Mint the session on your server, never in the browser, so your API key is never shipped to a client.

curl -X POST https://api.callmissed.com/v1/voice/sessions \
  -H "Authorization: Bearer cm_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "system_prompt": "You are a helpful assistant. Keep replies to one or two sentences.",
    "greeting": "Hi, how can I help?",
    "voice": "shubh",
    "language": "en-IN",
    "max_duration_seconds": 1800
  }'
import httpx

async with httpx.AsyncClient() as client:
    r = await client.post(
        "https://api.callmissed.com/v1/voice/sessions",
        headers={"Authorization": "Bearer cm_your_api_key"},
        json={
            "system_prompt": "You are a helpful assistant.",
            "greeting": "Hi, how can I help?",
            "voice": "shubh",
            "language": "en-IN",
        },
    )
    r.raise_for_status()
    session = r.json()

# Hand session["ws_url"] and session["token"] to your browser client.
print(session["id"], session["ws_url"])
// Server-side route handler. Returns only ws_url + token to the browser.
const res = await fetch("https://api.callmissed.com/v1/voice/sessions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CALLMISSED_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    system_prompt: "You are a helpful assistant.",
    greeting: "Hi, how can I help?",
    voice: "shubh",
    language: "en-IN",
  }),
});

const session = await res.json();
return Response.json({ wsUrl: session.ws_url, token: session.token });

Fields used above

FieldTypeDefaultNotes
system_promptstringa concise built-in assistant promptMax 4096 characters
greetingstringagent decides its own openerMax 500 characters. The exact first line the agent speaks
voicestringshubhMax 50 characters
languagestringen-INMax 10 characters, BCP-47
llm_modelstringgemma-4-31bOmit it to take the platform default voice stack
max_duration_secondsint1800Between 30 and 3600. Hard ceiling for one active call
variablesobjectnoneValues for {{token}} placeholders in the greeting and prompt
metadataobjectnoneArbitrary JSON stored with the session

bot_id, webhook_url, tts_provider, tts_model, stt_model and tts_engine are also accepted. A model id the voice agent cannot serve is rejected at create time with 422. The Voice Session API page is the full reference for the request body, the other endpoints and the webhook events.

Step 2: what the response gives you

{
  "id": "7c2b9e30-1d8a-4c5f-9b3d-2f4a6e8b1c2d",
  "tenant_id": "0a1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
  "bot_id": null,
  "status": "created",
  "config": { "system_prompt": "You are a helpful assistant.", "voice": "shubh", "language": "en-IN" },
  "ws_url": "wss://…",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "started_at": null,
  "ended_at": null,
  "duration_seconds": null,
  "turn_count": 0,
  "total_audio_seconds": 0,
  "end_reason": null,
  "metadata": null,
  "created_at": "2026-04-19T12:00:00Z",
  "analysis": null
}

The three fields your client needs:

FieldUse
ws_urlThe media server URL, issued per session. Read it from the response and pass it through; do not hardcode it
tokenThe connection credential. Returned once at creation and never fetchable again. It expires one hour after issue
idThe session id, for fetching the transcript afterwards

analysis is null at creation and is populated later, once post-call analysis has run.

Step 3: connect the browser

The transport package is the third-party livekit-client:

npm install livekit-client
import { Room, RoomEvent, Track } from "livekit-client";

// wsUrl + token come from your own server route (step 1).
const { wsUrl, token } = await fetch("/api/voice/session").then((r) => r.json());

const room = new Room();

room.on(RoomEvent.TrackSubscribed, (track) => {
  if (track.kind === Track.Kind.Audio) {
    document.body.appendChild(track.attach());
  }
});

room.on(RoomEvent.TranscriptionReceived, (segments, participant) => {
  for (const seg of segments) {
    if (!seg.final) continue;
    const who = participant?.isLocal ? "You" : "Agent";
    console.log(`${who}: ${seg.text}`);
  }
});

await room.connect(wsUrl, token);
await room.localParticipant.setMicrophoneEnabled(true);

The agent joins the room on its own and runs the speech pipeline. Speech boundaries are detected server side, and interruptions are handled for you: talk while the agent is speaking and it stops and listens.

Call room.disconnect() to end the call from the client.

Streaming audio from a server, not a browser

livekit-client is a browser package. If the thing holding the microphone is a Python, Go or Node process rather than a browser tab, do not reach for a WebRTC client at all: use the Managed Voice Agent instead. It takes raw audio over a single plain WebSocket with no client SDK on your side.

Step 4: read the transcript

curl "https://api.callmissed.com/v1/voice/sessions/{id}/transcript?format=json" \
  -H "Authorization: Bearer cm_your_api_key"

format accepts json (the default, a structured turn list), txt (alternating plain text) or srt (subtitles).