The AudioBridge helper in the SDK is a thin wrapper — under it, every call’s audio is just two live MoQT tracks fanned out by the relay. This page is the drop-below-the-SDK surface: the raw publish_audio / subscribe_audio primitive, the exact namespace layout for a call, the capability tags that route a track, and how the relay authorizes you to touch a namespace. Reach for it when you’re bridging your own runtime, taping a call into a recorder, or wiring a human agent to a live caller. Read Realtime Tracks (MoQT) first for the general model (namespace + name + capability, connect/backoff/reconnect, track kinds). Everything here is that model, pinned to voice.

The two tracks of a call

Every call is keyed by its call_sid (see Sessions, calls, tracks & streams). Its audio lives under a per-call namespace, split by direction, with the track name audio:
NamespaceDirectionCarries
voice/<sid>/uplinkcaller → cloudwhat the caller is saying
voice/<sid>/downlinkcloud → callerwhat gets played back to the caller
The RTP media plane publishes the caller’s audio to uplink and subscribes downlink for playback, so which track you publish vs. subscribe depends on which side you are:
  • Agent / server side (the common case) — subscribe uplink to hear the caller, publish downlink to talk back.
  • Browser-caller side — the mirror: subscribe downlink to play cloud audio, publish uplink with captured mic frames.
Uplink and downlink are distinct namespaces on purpose — a single track would feed the agent its own output. The same split lets a human agent or a supervisor subscribe uplink alone and hear only the caller (see Handoffs).

Publish & subscribe

publish_audio(ns, name, …) returns a publication whose write(ts_us, frame) sends one encoded audio frame (e.g. a 20 ms Opus packet); objects roll into a new group about once a second. subscribe_audio(ns, name, on_frame) delivers each object back as (ts_us, frame). Hold both handles for the life of the call — dropping a subscription frees the native callback, and dropping a publication stops the track.
import { MoqtClient } from "@telequick/sdk/moqt";

// One WebTransport session to the relay carries every track for the call.
const client = await MoqtClient.connect(
  "https://relay.telequick.dev/moq",
  apiKey, // tenant token, verified at the relay
);

// Agent side: hear the caller, talk back.
client.subscribeAudio(`voice/${sid}/uplink`, "audio", (tsUs, frame) => {
  asr.push(frame); // caller audio → your runtime
});

const downlink = client.publishAudio(`voice/${sid}/downlink`, "audio", {
  capability: "voice/opus",
  sampleRate: 48000,
  channels: 1,
  frameMs: 20,
});
tts.onChunk((opus) => downlink.write(BigInt(Date.now()) * 1000n, opus));
ns
string
required
Per-call track namespace, voice/<sid>/uplink or voice/<sid>/downlink. <sid> is the call_sid.
name
string
required
Track name within the namespace — audio for voice legs.
capability
string
default:""
Free-form routing intent (see Capability tags). Voice legs use voice/<codec>, e.g. voice/opus.
sampleRate
number
default:"48000"
Codec sample rate of the frames you write. Opus stays 48000; narrowband PCM/G.711 legs use 8000.
channels
number
default:"1"
Channel count. Voice is mono.
frameMs
number
default:"20"
Frame duration per object. 20 ms is the wire cadence for Opus and G.711.

Raw objects vs. decoded frames

The RTP media plane publishes the caller’s audio on uplink as raw 8 kHz PCM16 LE objects — the runtime’s internal audio format — with no SDK frame wrapper. To consume those bytes verbatim, use subscribe_raw, which hands you each MoQT object’s payload untouched:
client.subscribeRaw(`voice/${sid}/uplink`, "audio",
  (groupId, objectId, pcm16) => { /* 160 samples per 20 ms @ 8 kHz */ });
Use subscribe_audio when the other side is an SDK publisher (frames carry a timestamp header); use subscribe_raw for engine-published raw tracks. See the audio contract for the 8 kHz PCM16 / Opus / G.711 details.

Capability tags

A capability is an open-ended intent string attached to a track. The relay and in-engine modules route on it — “route on intent, not on media kind.” Voice legs tag themselves voice/<codec>, where <codec> is one of the SDK’s audio codecs:
Codec valueOn the wire
opusOpus 48 kHz mono, 20 ms
pcm16linear PCM16
g711_ulawG.711 µ-law (PT 0)
g711_alawG.711 A-law (PT 8)
So a browser caller publishes voice/opus, a passthrough leg off a µ-law trunk publishes voice/g711_ulaw. Capability is informational for the track itself — subscribers select which namespace/name to receive; the tag tells the routing layer what the bytes are so it can hand them to the module that registered for that intent (an ASR node, a recorder, a media.passthrough relay).
Two publishers can carry different capabilities on the same namespace. Set the tag to the actual codec you’re writing — a mislabeled leg gets handed to a decoder that can’t parse it.

Namespace authorization

There is no separate ACL call. Authorization rides the connect token:
1

Present your tenant token at connect

Pass your workspace API key as the token to connect. The SDK sends it as a URL query parameter to relay.telequick.dev, where the relay control plane verifies it before the MoQT session opens.
2

The relay scopes you to your workspace's calls

A verified session may publish and subscribe voice/<sid> namespaces for calls that belong to your workspace. <sid> values are unguessable per-call keys, and the relay rejects a namespace outside your tenant scope — cross-tenant subscription is not possible.
3

Namespaces are non-empty tuples

MoQT namespaces are tuples that must have at least one element; a subscribe for a namespace that hasn’t been announced yet is held by the relay and delivered once the publisher appears. For multi-participant rooms the SDK scopes each participant under a [room, identity] tuple; a single call’s audio lives under the flat voice/<sid>/… namespace.
The token is a workspace-scoped credential — mint short-lived tokens for browser clients rather than shipping a long-lived API key into a page. Treat voice/<sid> as sensitive: anyone with a valid token for your workspace and the sid can join the audio.

Transport & runtimes

Tracks ride MoQT over WebTransport on the single QUIC/HTTP-3 :443 plane. Browsers negotiate the moqt-16 WebTransport subprotocol; the relay handles connect, capped-backoff reconnect, and re-establishing every publication and subscription for you. Two runtimes sit behind one API. The native SDK cores (Python, Go, Rust, and the C++ FFI, telequick_moqt_ffi) speak QUIC and carry a QUIC → WebSocket fallback ladder for UDP-blocked networks. The TypeScript SDK is a standalone WebTransport implementation and is WebTransport-only today — there is no browser WS fallback yet. Hold the two as separate conformance surfaces; the publish/subscribe APIs match, but queueing and unsubscribe lifecycle can differ.
For browser callers you rarely call this primitive directly — the SDK’s WebRTC-diversion capture wraps publish_audio with encoded-Opus tapping. See Browser audio capture and WebTransport.

Realtime Tracks (MoQT)

The general publish/subscribe model this page pins to voice.

Sessions, calls, tracks & streams

How call_sid keys the SIP dialog, media session, and every track.

AudioBridge SDK

The high-level helper that wraps these tracks per call.

Handoffs

Subscribe voice/<sid>/uplink to put a human on a live caller.