Voice client for the
control and data planes, and the optional browser helpers when you are building
a softphone in the browser.
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 events do appear
(the human-agent control channel).Construct the client
The top-levelVoice client hands out three scoped helpers — calls,
audioBridge, and agents — that each carry your org id and the transport.
Control-plane origin (no trailing slash). Requests go over HTTPS with
Authorization: Bearer <apiKey>.API key with
voice:* scopes.Default org / tenant id applied to every call.
Relay hostname for the audio bridge. Defaults to
relay.telequick.dev.Override the
fetch implementation (e.g. in Node before global fetch).Inject a custom WebTransport factory for the audio bridge (mainly for Node
tests).
VoiceError if baseUrl, apiKey, or orgId is
missing.
Place a call and move audio
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.Originate the call
calls.originate() places an outbound call over a SIP trunk and resolves to
a Call handle once the call is dialing.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.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.
E.164 destination.
Caller-id, E.164.
SIP trunk to route over.
AI agent id to attach automatically on answer.
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 moving through its lifecycle.
Call — one call handle
Returned by originate() and get(). Read-only fields plus two mutating
methods.
| Field | Type | Notes |
|---|---|---|
sid | string | The call_sid — the only id you need to address audio. |
status | CallStatus | dialing … completed (see Types). |
to | string | E.164 destination. |
from | string | Caller-id. |
startedAt | string | ISO-8601 start time. |
trunkId | string? | SIP trunk. |
agent | string? | Attached AI agent id, if any. |
call.transfer(args | string) → Promise<void>
Transfer to a PSTN number or re-attach a different agent. Provide exactly
one of to / agent; passing a bare string is shorthand for { to }. A PSTN
transfer is executed as a REFER on the SIP leg
(Telephony API).
VoiceError if neither to nor agent is supplied.
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 and
hands back a handle you hold for the call’s lifetime. Under the hood 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 the caller’s audio (uplink),
publish audio back (downlink). onUplink is required.
Callback for inbound caller audio, fired once per encoded frame.
Default
48000.Default
1.Frame duration in ms. Default
20.audioBridge.attachCaller(callSid, opts) → Promise<AudioBridge>
The browser-caller mirror of attach(): subscribe 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
| Method | Direction | Notes |
|---|---|---|
publishDownlink(frame, timestampUs?) | server → caller | Push one encoded frame to the caller (e.g. 20 ms Opus). |
publishUplink(frame, timestampUs?) | caller → cloud | Browser-caller side; push one encoded mic frame. |
onUplink(cb) | — | Setter for the inbound-caller consumer (server side). |
onDownlink(cb) | — | Setter for the inbound-cloud consumer (browser side). |
close() | — | Tear down both tracks and the underlying session. |
callSid | field | The sid this bridge belongs to. |
timestampUs is an optional bigint; if omitted the SDK stamps a monotonic
microsecond clock. The onUplink / onDownlink setters exist to keep the
public surface stable — the consumer callback you pass to attach() /
attachCaller() is the one that actually 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>
Browser softphone helpers
These ship on themoqt subpath and 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 ships the payloads over the media-over-QUIC transport.
The mechanics are covered in
Browser Audio Capture
and WebRTC diversion.
captureMicrophone(publication, opts?) → Promise<MicCapture>
Capture the mic, run echo-cancellation / gain-control / noise-suppression,
Opus-encode, and write 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:
OpusPlayer
Decode received Opus with WebCodecs and render through an audio ring buffer
(silence-padded on underrun). Construct with an AudioContext, start() once,
then push() each frame you receive.
Types
| Type | Values |
|---|---|
CallStatus | dialing | ringing | in_progress | completed | failed | no_answer |
AudioCodec | opus | pcm16 | g711_ulaw | g711_alaw |
VoiceError.
The control-plane surface is request/response — there is no socket of call
events, so track a call’s lifecycle by re-fetching with calls.get({ sid }).
The data plane is event-driven: the onUplink / onDownlink callbacks fire
per audio frame, and the underlying session auto-reconnects and replays the
publish/subscribe on link loss.
Related
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.