The Rust Voice SDK gives you the same two planes as every other TeleQuick SDK, in idiomatic Rust:
  • a control planeCalls, Agents — a small blocking client over the control-plane API (originate, transfer, hangup, bind an agent), and
  • a data planeAudioBridge — bidirectional call audio (Opus / PCM16 / G.711) over QUIC (MoQT), with the voice/<sid>/{uplink,downlink} track convention enforced for you.
It mirrors the TypeScript, Python, and Go Voice modules — same route names, same track layout — so a backend written against any of them ports over method-for-method.
The control-plane client (Calls/Agents) is synchronous and blocking (it speaks the control-plane tRPC surface over HTTPS). The AudioBridge data plane binds a bundled native transport library — the shared QUIC/MoQT engine core used by every TeleQuick native SDK — via callbacks. There is no async/tokio Voice surface today; run the control-plane calls from a blocking context (or tokio::task::spawn_blocking), and treat the audio callbacks as running on the transport thread. See Native transport library.

Install

The Voice module ships in the TeleQuick Rust SDK crate. Add it to your Cargo.toml:
[dependencies]
telequick = "1"
The realtime tracks bind a bundled native transport library, so build and run with it on your linker and library search path:
RUSTFLAGS="-L /path/to/lib" LD_LIBRARY_PATH=/path/to/lib cargo run

Connect

Construct a Voice client with your control-plane API base URL, an API key, and your workspace (org) id. The key and org scope every control-plane request; the client also carries the relay_host your audio bridges dial.
use telequick::voice::Voice;

let voice = Voice::new(
    "https://engine.telequick.dev",           // control-plane API base URL
    &std::env::var("TELEQUICK_API_KEY").unwrap(),
    "org_abc123",                        // your workspace id
)?;

// Audio bridges dial your workspace relay by default; override if needed.
let mut voice = voice;
voice.relay_host = "relay.telequick.dev".into();
Voice hands out the three sub-clients:
MethodReturnsPlane
voice.calls()CallsControl — originate / fetch / transfer / hangup
voice.agents()AgentsControl — bind a server-side agent to a call
voice.audio_bridge()AudioBridgeFactoryData — attach to a live call’s audio
Voice::new returns Err if the base URL, API key, or org id is empty. Every control-plane method returns Result<_, VoiceError>; a non-2xx or tRPC error response surfaces as VoiceError(message). Match on it rather than unwrapping in production.

Place and control a call

1

Originate

Calls::originate takes an OriginateArgs and returns a Call whose data field carries the assigned sid and initial status.
use telequick::voice::OriginateArgs;

let call = voice.calls().originate(OriginateArgs {
    to:       "+14155550101",
    from:     "+14155550100",
    trunk_id: "trunk_main",
    agent:    Some("agent_support"), // bind a server-side agent, or None
    ring_timeout_sec: 30,
})?;

println!("call {} is {}", call.data.sid, call.data.status);
2

Fetch an existing call

Look a call up later by its sid:
let call = voice.calls().get("call_sid_xyz")?;
3

Transfer or hang up

A Call exposes the control operations. Transfers map onto the same control-plane route (voice.calls.transfer); pass a PSTN number or an agent.
call.transfer_to("+14155550199")?;   // blind transfer to a PSTN number (REFER)
call.transfer_agent("agent_billing")?; // re-attach to a different agent
call.hangup()?;

OriginateArgs

to
&str
required
Destination in E.164 (+14155550101).
from
&str
required
Caller-ID / originating number in E.164.
trunk_id
&str
required
The trunk to route the outbound leg over. Configure trunks in the console; see SIP trunking.
agent
Option<&str>
default:"None"
Bind a server-side agent to answer the call. None originates a bare call you drive yourself (e.g. via AudioBridge).
ring_timeout_sec
u32
default:"30"
Seconds to ring before giving up. OriginateArgs::default() sets 30.
Call.data
CallData
The returned Call carries a CallData with sid, status, to, from, started_at, trunk_id: Option<String>, and agent: Option<String>.
transfer_to performs a SIP REFER-based transfer. Operator-initiated transfers are shipped; executing the outbound leg from a peer-initiated REFER is still partial in the engine — see AI ⇄ human handoff for what’s wired end-to-end.

Stream call audio (AudioBridge)

When you want to send and receive raw media yourself — feed uplink to your own ASR, play your own TTS back — attach an AudioBridge to a live call_sid. The factory subscribes to the caller’s audio (voice/<sid>/uplink) and opens a publication for audio back to the caller (voice/<sid>/downlink) in one call.
use telequick::voice::{AudioBridgeOpts, Codec};

let bridge = voice.audio_bridge().attach(
    &call.data.sid,
    AudioBridgeOpts { codec: Codec::Pcm16, ..Default::default() },
    // uplink callback: (frame_bytes, timestamp_us) — caller -> you
    |frame: &[u8], ts_us: u64| {
        // hand `frame` to your ASR / recorder / mixer
    },
)?;

// downlink: your audio -> caller. Timestamps are stamped for you.
bridge.publish_downlink(&pcm16_frame);
codec
Codec
default:"Codec::Opus"
Codec::Opus | Pcm16 | G711ULaw | G711ALaw. This is the on-track capability; audio is audio-only (no video codecs in Voice). The runtime bus is 8 kHz PCM16 end-to-end — see Codecs.
sample_rate
u32
default:"48000"
Wire sample rate. AudioBridgeOpts::default() is Opus / 48000 / 1 channel / 20 ms.
channels
u8
default:"1"
Channel count (voice is mono).
frame_ms
u16
default:"20"
Frame duration in milliseconds (20 ms = one media tick).
AudioBridge closes on drop (RAII) — dropping it tears down both the subscription and the publication. There is no explicit close(); keep the bridge alive for as long as you want audio to flow (bind it to a let, not let _). The uplink callback signature is FnMut(&[u8], u64) + Send + 'static(frame, timestamp_us).

Bind a server-side agent

If you’d rather let a configured agent (ASR/LLM/TTS or a speech-to-speech provider) run the conversation, attach one to a live call instead of bridging audio yourself:
voice.agents().attach(&call.data.sid, "agent_support")?;
The agent is defined in the console; the runtime picks up its pipeline config per call. See the Agent Runtime overview.

End-to-end example

use telequick::voice::{Voice, OriginateArgs, AudioBridgeOpts, Codec};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut voice = Voice::new(
        "https://engine.telequick.dev",
        &std::env::var("TELEQUICK_API_KEY")?,
        "org_abc123",
    )?;
    voice.relay_host = "relay.telequick.dev".into();

    // 1. Place an outbound call (no server agent — we'll drive audio).
    let call = voice.calls().originate(OriginateArgs {
        to:   "+14155550101",
        from: "+14155550100",
        trunk_id: "trunk_main",
        agent: None,
        ring_timeout_sec: 30,
    })?;
    println!("dialing {} -> {}", call.data.from, call.data.to);

    // 2. Attach an audio bridge; forward uplink to your own pipeline.
    let bridge = voice.audio_bridge().attach(
        &call.data.sid,
        AudioBridgeOpts { codec: Codec::Pcm16, ..Default::default() },
        |frame, _ts_us| { /* feed your ASR here */ let _ = frame; },
    )?;

    // 3. ... run your loop, call bridge.publish_downlink(&frame) to talk back ...
    let _ = &bridge;

    // 4. Hang up (dropping `bridge` closes the media legs).
    call.hangup()?;
    Ok(())
}

Native transport library

The control plane (Calls, Agents) is pure Rust over HTTPS and needs no native dependency. The data plane (AudioBridge, and the underlying MoqtClient in the crate’s moqt module) binds the bundled native QUIC/MoQT transport core — the same engine core shared across the native SDKs — so:
  • Build and run with the native library on your linker/library path (RUSTFLAGS="-L …", LD_LIBRARY_PATH=…).
  • The transport auto-reconnects with backoff and re-announces / re-subscribes every track on reconnect — no app code. You can publish/subscribe before the session is up; calls are queued and replayed on connect.
  • Native SDKs get the QUIC → WebSocket fallback ladder transparently for UDP-blocked networks. (The browser/TS SDK is WebTransport-only today; this fallback is a native-core benefit.)
The lower-level track API is available directly as telequick::moqt (MoqtClient::connect, publish_audio, subscribe_audio) if you need capabilities beyond the Voice convention — for example subscribing with an explicit filter or window. AudioBridge is the ergonomic wrapper that applies the voice/<sid>/{uplink,downlink} naming for you.

What’s not in the Rust surface

Grounding you before you reach for something that isn’t here:
  • No async Voice API. Control-plane calls block; there is no tokio-native Voice client. Wrap them in spawn_blocking if you’re inside a runtime.
  • No human-agent control channel. The event-driven softphone/agent control channel (call-offered / accept / reject) currently ships only in the TypeScript SDK. In Rust, drive calls via Calls/Agents and bridge audio via AudioBridge.
  • No browser capture. WebRTC-diversion mic capture is a browser-only concern (Browser audio capture). The Rust SDK is a server-side / backend client.

Next steps

Calls API

The control-plane routes behind Calls — originate, transfer, hangup.

Transport & tracks

The MoQT track model AudioBridge builds on.

Sessions, calls & tracks

How call_sid and the voice/<sid>/{uplink,downlink} layout fit together.

Outbound call cookbook

A full originate → stream → hang up walkthrough.