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.
import { Voice } from "@telequick/sdk/voice";
// browser softphone helpers live on the moqt subpath:
import { captureMicrophone, OpusPlayer } from "@telequick/sdk/moqt";
The surface splits cleanly 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 events do appear (the human-agent control channel).

Construct the client

The top-level Voice client hands out three scoped helpers — calls, audioBridge, and agents — that each carry your org id and the transport.
const v = new Voice({
  baseUrl: "https://engine.telequick.dev",   // control-plane origin
  apiKey:  process.env.TELEQUICK_CREDENTIALS!,
  orgId:   "org_abc",
});

v.calls;        // control plane  (place / fetch / transfer / hangup)
v.audioBridge;  // data plane     (uplink / downlink audio frames)
v.agents;       // AI-agent attach
baseUrl
string
required
Control-plane origin (no trailing slash). Requests go over HTTPS with Authorization: Bearer <apiKey>.
apiKey
string
required
API key with voice:* scopes.
orgId
string
required
Default org / tenant id applied to every call.
relayHost
string
Relay hostname for the audio bridge. Defaults to relay.telequick.dev.
fetch
(input, init) => Promise<Response>
Override the fetch implementation (e.g. in Node before global fetch).
webTransport
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

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.
const call = await v.calls.originate({
  to:      "+15551234567",
  from:    "+15558675309",
  trunkId: "trunk_main",
  ringTimeoutSec: 30,        // optional, server-clamped 5..120
});
console.log(call.sid, call.status);
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.
const bridge = await v.audioBridge.attach(call.sid, {
  codec: "opus",                       // default
  onUplink: (frame, tsUs) => myAsr.feed(frame),
});
3

Push audio back to the caller

Send each encoded frame from your TTS onto the downlink track.
myTts.onChunk((opus) => bridge.publishDownlink(opus));
4

Close when the call ends

Tear down both tracks and the underlying session.
await call.hangup();
await bridge.close();
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.
const call = await v.calls.originate({
  to:      "+15551234567",
  from:    "+15558675309",
  trunkId: "trunk_main",
  agent:   "healthcare-assistant",  // optional: engine attaches the bridge on answer
  ringTimeoutSec: 30,               // optional, server-clamped 5..120
});
to
string
required
E.164 destination.
from
string
required
Caller-id, E.164.
trunkId
string
required
SIP trunk to route over.
agent
string
AI agent id to attach automatically on answer.
ringTimeoutSec
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 moving through its lifecycle.
const call = await v.calls.get({ sid });

Call — one call handle

Returned by originate() and get(). Read-only fields plus two mutating methods.
FieldTypeNotes
sidstringThe call_sid — the only id you need to address audio.
statusCallStatusdialingcompleted (see Types).
tostringE.164 destination.
fromstringCaller-id.
startedAtstringISO-8601 start time.
trunkIdstring?SIP trunk.
agentstring?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).
await call.transfer({ to: "+15557654321" });   // forward to PSTN
await call.transfer("+15557654321");           // shorthand
await call.transfer({ agent: "billing-bot" }); // re-attach an agent
Throws a VoiceError if neither to nor agent is supplied.

call.hangup() → Promise<void>

End the call and drop both audio tracks.
await call.hangup();

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.
const bridge = await v.audioBridge.attach(call.sid, {
  codec: "opus",          // default
  sampleRate: 48000,      // default
  channels: 1,            // default
  frameMs: 20,            // default
  onUplink: (frame, tsUs) => myAsr.feed(frame),
});

// push synthesized audio back to the caller:
myTts.onChunk((opus) => bridge.publishDownlink(opus));
Callback for inbound caller audio, fired once per encoded frame.
codec
AudioCodec
Default opus. See Types.
sampleRate
number
Default 48000.
channels
number
Default 1.
frameMs
number
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.
const bridge = await v.audioBridge.attachCaller(call.sid, {
  codec: "opus",
  onDownlink: (frame, tsUs) => player.push(tsUs, frame),
});

AudioBridge methods

MethodDirectionNotes
publishDownlink(frame, timestampUs?)server → callerPush one encoded frame to the caller (e.g. 20 ms Opus).
publishUplink(frame, timestampUs?)caller → cloudBrowser-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.
callSidfieldThe 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>

await v.agents.attach(call.sid, "healthcare-assistant");
You can attach an agent two ways: pass agent to originate() (attached on answer) or call agents.attach() against a live sid — e.g. to hand a call from a human to a bot, or swap one bot for another. See the agent runtime overview for how the attached agent is configured.

Browser softphone helpers

These ship on the moqt 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:
const bridge = await v.audioBridge.attachCaller(call.sid, {
  codec: "opus",
  onDownlink: (frame, tsUs) => player.push(tsUs, frame),
});

// forward each captured mic frame onto the uplink track
const mic = await captureMicrophone(
  { write: (tsUs, frame) => bridge.publishUplink(frame, tsUs) } as any,
  { audioConstraints: { echoCancellation: true, autoGainControl: true, noiseSuppression: true } },
);
// later:
mic.stop();
captureMicrophone hard-gates on the browser’s encoded-transform support and throws if it is unavailable — it never silently falls back to a plain WebRTC media transport. Check Browser compatibility before shipping.

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.
import { OpusPlayer } from "@telequick/sdk/moqt";

const ctx = new AudioContext();
const player = new OpusPlayer(ctx, { sampleRate: 48000, channels: 1 });
await player.start();

// feed it the frames from the downlink subscription:
// onDownlink: (frame, tsUs) => player.push(tsUs, frame)

player.close();

Types

TypeValues
CallStatusdialing | ringing | in_progress | completed | failed | no_answer
AudioCodecopus | pcm16 | g711_ulaw | g711_alaw
Voice is audio-only — there are no video codecs on this surface. Phone legs ride G.711 on the wire and are transcoded 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 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.

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.