Run the audio bridge yourself and pipe the caller’s uplink (what they say) straight into a streaming recognizer you control — Deepgram, a self-hosted Whisper, or any ASR that accepts 16-bit PCM. audioBridge.attach subscribes the uplink track and hands you every frame in onUplink; ask for pcm16 at 16000 Hz and the bridge transcodes the G.711 PSTN leg for you, so your ASR gets exactly the audio it wants.
This is the “bring your own ASR” path — you own the recognizer and the dialog logic. If you’d rather have TeleQuick run a managed cascade (ASR → LLM → TTS) from one config file, see BYO ASR / LLM / TTS instead.

Attach the bridge and feed your ASR

attach takes the call_sid, a codec/rate, and your onUplink callback. Frames arrive as raw little-endian PCM16 — a mono 20 ms frame at 16 kHz is 320 samples (640 bytes). Push each one to your recognizer as it arrives.
import { Voice } from "@telequick/sdk/voice";

const v = new Voice({
  baseUrl: "https://engine.telequick.dev",
  apiKey:  process.env.TELEQUICK_API_KEY!,
  orgId:   "org_abc",
});

// `asr` is your own streaming recognizer client (e.g. a WebSocket to Deepgram
// or a self-hosted Whisper). It accepts linear PCM16 @ 16 kHz.
async function transcribeCall(sid: string) {
  const bridge = await v.audioBridge.attach(sid, {
    codec:      "pcm16",   // raw 16-bit PCM, not Opus/G.711
    sampleRate: 16000,     // what most streaming ASRs expect
    channels:   1,
    frameMs:    20,
    onUplink: (frame /* Uint8Array */, tsUs /* bigint */) => {
      asr.send(frame);     // stream each 640-byte frame straight through
    },
  });

  asr.on("transcript", (text: string, isFinal: boolean) => {
    if (isFinal) console.log(`[${sid}] ${text}`);
  });

  return bridge;   // keep this reference alive for the whole call
}
attach both subscribes the uplink and publishes a downlink track, so hold the returned bridge for the call’s lifetime — letting it be garbage-collected drops both tracks and ends the bridge. When you’re done, bridge.close() before call.hangup() so the tracks tear down cleanly.

Transcribe an inbound call

Bind the bridge as soon as the call answers. Below, an outbound call is originated with no server-side agent, so your ASR is the only consumer of the uplink; the same attach works for an inbound sid you already hold.
const call = await v.calls.originate({
  to:      "+15551234567",
  from:    "+15558675309",
  trunkId: "trunk_main",
});

const bridge = await transcribeCall(call.sid);

// … later, when you're finished listening:
await bridge.close();
await call.hangup();

Want Opus frames instead?

If your recognizer takes Opus directly (or you’re just capturing to disk), ask for codec: "opus" and skip the transcode — the caller’s audio reaches you as 20 ms encoded Opus packets. Use pcm16 only when your ASR needs raw samples.
const bridge = await v.audioBridge.attach(sid, {
  codec: "opus",
  onUplink: (frame, tsUs) => sink.write(frame),
});
tsUs / ts_us is the frame’s presentation timestamp in microseconds. Feed it to a recognizer that supports word-level timing, or use it to align the transcript with a recording keyed on the same call_sid.

Send TTS audio back

Push synthesized audio to the caller with publishDownlink.

BYO ASR / LLM / TTS

Let the runtime assemble a managed cascade from one config file.

Turn detection

VAD and barge-in — decide when a caller’s turn is over.

Sessions, calls & tracks

How voice/<sid>/uplink and downlink tracks are addressed.