A session is the live transport connection that carries one call’s audio. It is what your code holds open while a caller and an agent talk: a MoQT session over WebTransport, authenticated by a relay token and scoped to a room-shaped namespace keyed on the call_sid. This page documents how that session is authenticated and brought up. It is the plumbing under AudioBridge.attach — you rarely wire it by hand, but you need the shapes to reason about auth, tenancy, and reconnection.
There is no public REST API for sessions. The control plane is a set of tRPC-style procedures exposed by the control-plane host and called for you by the SDK. You authenticate with an API key; the SDK does the procedure calls and the relay handshake. The shapes below are the real ones the SDK sends — treat them as the contract, not as endpoints to hand-roll.

Two planes, one credential

Establishing a session touches two planes, both authorized by the same workspace API key:
PlaneWhat it doesTransportAuth
Control planeMints call state, resolves the relay edge, authorizes the tenanttRPC over HTTPS to portal.telequick.devAuthorization: Bearer <apiKey>
Data planeCarries the encoded audio for the callMoQT over WebTransport to relay.telequick.devRelay token (?token=) + namespace scope
The control plane is a request/response surface (queries and mutations). The data plane is a long-lived session that auto-reconnects. You get onto the data plane by first asking the control plane where the relay lives and proving you are allowed to attach.

Establish a session

1

Authenticate the control plane

Construct the client with your workspace API key and org id. The key is sent as Authorization: Bearer <apiKey> on every control-plane call; the org id scopes every procedure to your tenant.
import { Voice } from "@telequick/sdk/voice";

const voice = new Voice({
  baseUrl: "https://portal.telequick.dev",
  apiKey:  process.env.TELEQUICK_API_KEY!,
  orgId:   "org_123",
  relayHost: "relay.telequick.dev",   // optional; this is the default
});
import os
from telequick.voice import Voice

voice = Voice(
    base_url="https://portal.telequick.dev",
    api_key=os.environ["TELEQUICK_API_KEY"],
    org_id="org_123",
    relay_host="relay.telequick.dev",   # optional; this is the default
)
2

Resolve the relay edge

Before opening a session the SDK asks the control plane which relay edge to dial. This is a query — the SDK sends it, but the shape is worth knowing because it geo-routes you to the nearest edge and carries the fields the WebTransport handshake needs.
orgId
string
required
Your tenant id. Scopes the response to your workspace.
relayRegion
string
Pin a specific relay edge (e.g. "us", "uk"). Omit to let the server geo-route to the nearest edge.
Response:
enabled
boolean
false when the tenant has no QUIC/relay entitlement — the rest of the fields are absent. Fall back to a control-plane-only integration.
relayUrl
string
The MoQT relay endpoint to open the session against (already region-selected).
relayRegion
string
Which edge was chosen — echoed for telemetry and for pinning on reconnect.
relayCertHashBase64
string | null
A base64 SHA-256 certificate fingerprint for self-signed dev relays. null in production, where the relay presents a public-CA certificate and no fingerprint pinning is needed.
On managed cloud the relay runs on a public-CA certificate, so relayCertHashBase64 is null. You only pass a cert hash when you point the SDK at a self-signed relay in local development.
3

Open the relay session

With the relay URL and a token, the SDK opens the MoQT session. Under the hood this is MoqtClient.connect(url, token, onState, opts); the token is appended to the URL as a ?token= query and verified at the relay’s control plane, and the WebTransport handshake advertises the moqt-16 subprotocol (the relay closes the session if the version subprotocol is missing).
// What AudioBridge.attach does for you — shown for reference.
const bridge = await voice.audioBridge.attach(call.sid, {
  codec: "opus",
  onUplink: (frame, tsUs) => { /* caller audio in */ },
});
// -> connects moq://relay.telequick.dev/voice/<call_sid>
// -> subscribes voice/<call_sid>/uplink, publishes voice/<call_sid>/downlink
The session address is the call_sid: the SDK connects to moq://<relayHost>/voice/<call_sid> and binds the uplink / downlink tracks under that namespace. One call_sid is the whole addressing story — SIP dialog, media session, agent session, telemetry, and this transport session all key off it (see Sessions, calls, tracks & streams).
4

Observe session state

The session reconnects transparently with capped backoff; publications and subscriptions are re-established on every reconnect. Subscribe to the state machine to drive UI and to know when audio is actually flowing.
// ConnectionState, shared across every SDK.
// Connecting → Connected → (Reconnecting ⇄ Connected)* → Closed | Failed
Connecting
First connect in progress.
Connected
Session is up; tracks are live.
Reconnecting
Transport dropped; the SDK is retrying with capped exponential backoff and will replay your pubs/subs on success.
Closed
You (or the peer) ended the session cleanly.
Failed
The initial connect failed — surfaced with a reason string.

Namespace auth (room scope)

The relay does not hand a token holder the whole tenant. Authorization is namespace-scoped: a session may only publish and subscribe within the room-shaped namespace tuple it is authorized for. For voice that room is voice/<call_sid>, and the two tracks inside it are fixed by role:
TrackDirectionWho publishes
voice/<call_sid>/uplinkcaller → cloudthe caller (or the media plane)
voice/<call_sid>/downlinkcloud → callerthe agent / your bridge
Distinct uplink and downlink tracks are what prevent a leg from hearing itself. An attach subscribes uplink and publishes downlink; attachCaller (the browser-caller mirror) does the reverse.
Namespaces are tuples with a room scope, never a single flat identifier. A valid namespace carries at least a one-element prefix and a suffix; a zero-element namespace is rejected at the relay. This is why every voice session is addressed as voice/<call_sid>/<track> and never as a bare id — the room prefix is what the relay authorizes against.

Connect options

These are the knobs the SDK passes to the relay session; attach sets the media-relevant ones for you, but they are the honest surface if you drop to the transport client directly.
token
string
Relay token, verified at the relay control plane and sent as a ?token= query on the WebTransport URL. The Voice SDK forwards your workspace API key here.
serverCertificateHash
string
Base64 SHA-256 certificate fingerprint for a self-signed dev relay. Leave unset in production (relayCertHashBase64 from gatewayConnectInfo is null there).
onState
(state, reason?) => void
Callback for the ConnectionState machine above.
requireEncodedTransform
boolean
Refuse to connect unless the browser exposes the standard WebRTC Encoded Transform API (RTCRtpScriptTransform). Media-sending browser sessions set this so the encoded-frame rule holds up front instead of failing later at capture time. Leave unset for receive-only or non-media sessions. See browser audio capture.
Fallback honesty. In the browser / TypeScript SDK a session is WebTransport-only today; if WebTransport is unavailable it does not silently degrade. The QUIC→WebSocket fallback ladder currently ships only in the native SDK core (Python, Go, Rust, Java, .NET wrappers), which sticks to a WebSocket rung when QUIC is blocked.

Sessions vs. calls

Keep the two clearly apart:
  • A call is the SIP dialog and its lifecycle — originate, ring, answer, transfer, hangup. You drive it with the Calls API (voice.calls.*), which returns CallData keyed by sid.
  • A session is the transport connection that carries that call’s audio, addressed by the same sid. You open it with the Transport / AudioBridge API.
The typical flow is: originate a call (control plane) → take the returned sid → attach a session (data plane) to move audio. For the server-side “drop-in agent” pattern you skip the manual session entirely — voice.agents.attach tells the engine to wire the bridge in-process, and no client session is opened. See the runtime session lifecycle for the engine’s view of that.

Transport / AudioBridge

Open and drive the audio session for a call.

Calls

Originate, transfer, and hang up — the call control plane.

Sessions, calls, tracks & streams

The object model: how call_sid, namespaces, and tracks relate.

Errors

Error shapes returned by the control plane and the session.