The Voice SDK lives on its own subpath. Import the Voice client for the control and data planes, and the optional browser helpers when you are building a softphone in the browser.
The surface splits into three jobs: place and control calls (Voice.calls), move audio frames (Voice.audioBridge), and hand a call to a server-side AI agent (Voice.agents). This page is the TypeScript reference. The same shape ships in Python, Go, and Rust.
This is a callback surface, not an EventEmitter. There is no join() or say(). You originate a call, then feed and drain audio through publishDownlink / onUplink (or hand the whole call to an agent). See SDK Events for the one place where events appear (the human-agent control channel).

Construct the client

The top-level Voice client gives you three scoped helpers: calls, audioBridge, and agents. Each helper carries your org id and the transport.
string
required
Control-plane origin (no trailing slash). Requests go over HTTPS with Authorization: Bearer <apiKey>.
string
required
API key with voice:* scopes.
string
required
Default org / tenant id applied to every call.
string
Relay hostname for the audio bridge. Defaults to relay.clutchcall.dev.
(input, init) => Promise<Response>
Override the fetch implementation (for example, in Node before global fetch).
WebTransportFactory
Inject a custom WebTransport factory for the audio bridge (mainly for Node tests).
The constructor throws a VoiceError if baseUrl, apiKey, or orgId is missing.

Place a call and move audio

This is the end-to-end shape of a server-side voice integration: originate, attach the bridge, drain caller audio into your ASR, and push synthesized audio back.
1

Originate the call

calls.originate() places an outbound call over a SIP trunk and resolves to a Call handle once the call is dialing.
2

Attach the audio bridge

audioBridge.attach() opens the bidirectional bridge as the server: it subscribes the caller’s audio (uplink) and publishes audio back (downlink). Pass onUplink to receive caller frames.
3

Push audio back to the caller

Send each encoded frame from your TTS onto the downlink track.
4

Close when the call ends

Tear down both tracks and the underlying session.
Hold the AudioBridge handle for the whole call. If it is garbage-collected, the tracks go silent. Call close() explicitly when the call ends.

Calls — control plane

Reachable as v.calls. Originates calls and fetches their current state. Every method resolves against the control-plane API and returns a Call.

calls.originate(args) → Promise<Call>

Place an outbound call over a SIP trunk. Resolves once the call is dialing.
string
required
E.164 destination.
string
required
Caller-id, E.164.
string
required
SIP trunk to route over.
string
AI agent id to attach automatically on answer.
number
Seconds to ring before giving up. Server-clamped 5..120, default 30.

calls.get({ sid }) → Promise<Call>

Fetch the current state of a call by its sid. Use it to poll status after originate(). The control plane is request/response, so re-fetch to observe a call as it moves through its lifecycle.

Call — one call handle

originate() and get() return a Call. It has read-only fields plus two mutating methods.

call.transfer(args | string) → Promise<void>

Transfer the call to a PSTN number or re-attach a different agent. Provide exactly one of to / agent. A bare string is shorthand for { to }. The engine executes a PSTN transfer as a REFER on the SIP leg (Telephony API).
The method throws a VoiceError if you supply neither to nor agent.

call.hangup() → Promise<void>

End the call and drop both audio tracks.

AudioBridge — data plane

Reachable as v.audioBridge (an AudioBridgeFactory). It opens the bidirectional audio bridge for one call over our media-over-QUIC transport. It returns a handle that you hold for the call’s lifetime. The bridge maps to the voice/<sid>/{uplink,downlink} track convention described in Realtime Tracks.

audioBridge.attach(callSid, opts) → Promise<AudioBridge>

Open the bridge as the server: subscribe to the caller’s audio (uplink) and publish audio back (downlink). onUplink is required.
Callback for inbound caller audio, fired once per encoded frame.
AudioCodec
Default opus. See Types.
number
Default 48000.
number
Default 1.
number
Frame duration in ms. Default 20.

audioBridge.attachCaller(callSid, opts) → Promise<AudioBridge>

The browser-caller mirror of attach(): subscribe to the downlink (cloud → caller) and publish the uplink (mic → cloud). Pass onDownlink to receive audio for playback. This is the softphone path. Pair it with captureMicrophone and OpusPlayer.

AudioBridge methods

timestampUs is an optional bigint. If you omit it, the SDK stamps a monotonic microsecond clock. The onUplink / onDownlink setters keep the public surface stable. The consumer callback that you pass to attach() / attachCaller() is the one that fires per frame.
The browser/TS SDK moves audio over WebTransport only. The QUIC→WebSocket fallback ladder currently ships in the native SDK cores (Python, Go, Rust, and the other wrappers), not in the browser build. Plan for WebTransport-capable clients when you build in the browser.

Agents — AI-agent attach

Reachable as v.agents. Bind a server-side speech-to-speech agent to a live call. The engine wires the audio bridge end-to-end, so you do not open an AudioBridge yourself.

agents.attach(callSid, agent) → Promise<void>

You can attach an agent in two ways. Pass agent to originate(), and the engine attaches it on answer. Or call agents.attach() against a live sid — for example, to hand a call from a human to a bot, or to swap one bot for another. See the agent runtime overview for how to configure the attached agent.

Browser softphone helpers

These helpers ship on the moqt subpath. They turn a microphone into encoded Opus and play encoded Opus back, so the softphone never touches a WebRTC transport for media. The browser’s own audio pipeline (echo cancellation, gain control, noise suppression, Opus encode) runs behind a loopback capture graph. The SDK taps the encoded frames and sends the payloads over the media-over-QUIC transport. Browser Audio Capture and WebRTC diversion cover the mechanics.

captureMicrophone(publication, opts?) → Promise<MicCapture>

This helper captures the mic and runs echo cancellation, gain control, and noise suppression. It encodes the audio to Opus and writes each encoded frame to a MoQT audio publication. The returned stop() tears down the capture graph (it does not close the publication). The simplest softphone loop forwards captured frames into the bridge with publishUplink:
captureMicrophone hard-gates on the browser’s encoded-transform support and throws if that support is unavailable. It never silently falls back to a plain WebRTC media transport. Check Browser compatibility before you ship.

OpusPlayer

OpusPlayer decodes received Opus with WebCodecs and renders it through an audio ring buffer (padded with silence on underrun). Construct it with an AudioContext. Call start() once, then push() each frame you receive.

Types

Voice is audio-only. There are no video codecs on this surface. Phone legs use G.711 on the wire; the engine transcodes them to the codec you request. Browser legs are Opus. Errors thrown by the SDK are instances of VoiceError. The control-plane surface is request/response. There is no socket of call events, so track a call’s lifecycle with repeated calls.get({ sid }) calls. The data plane is event-driven. The onUplink / onDownlink callbacks fire per audio frame. The underlying session auto-reconnects and replays the publish/subscribe on link loss.

SDK Events

The human-agent control channel — the one EventEmitter surface.

Calls API

The control-plane routes behind calls and Call.

Browser audio capture

How the softphone taps encoded Opus onto QUIC.

Python / Go / Rust

The same Voice client in the other languages.