# Voice — Cookbook

> Short, copy-paste recipes for originating, transferring, bridging audio, attaching agents, and the softphone.

Task-focused snippets for the voice modality. Each assumes a `Voice` client:

  <Tab title="TypeScript">
```ts
import { Voice } from "@clutchcall/sdk/voice";

const v = new Voice({
  baseUrl: "https://engine.clutchcall.dev",
  apiKey:  process.env.CLUTCHCALL_CREDENTIALS!,
  orgId:   "org_abc",
});
```
  </Tab>
  <Tab title="Python">
```python
import os
from clutchcall.voice import Voice

v = Voice(
    base_url="https://engine.clutchcall.dev",
    api_key=os.environ["CLUTCHCALL_CREDENTIALS"],
    org_id="org_abc",
)
```
  </Tab>

## Place an outbound call

Originate over a SIP trunk and read the returned `sid`.

```ts
const call = await v.calls.originate({
  to: "+15551234567",
  from: "+15558675309",
  trunkId: "trunk_main",
});
console.log("dialing", call.sid);
```

## Originate with an AI agent attached

Pass `agent` and the engine wires the audio bridge automatically on answer — no
`AudioBridge` needed.

```ts
const call = await v.calls.originate({
  to: "+15551234567",
  from: "+15558675309",
  trunkId: "trunk_main",
  agent: "healthcare-assistant",
});
```

## Cap the ring time

Give up after 15 seconds instead of the default 30 (server-clamped 5..120).

```ts
const call = await v.calls.originate({
  to: "+15551234567",
  from: "+15558675309",
  trunkId: "trunk_main",
  ringTimeoutSec: 15,
});
```

## Poll a call's status

The control plane is request/response — re-fetch to read the latest status.

```ts
const c = await v.calls.get({ sid: call.sid });
if (c.status === "in_progress") console.log("connected");
```

## Wait for a call to connect

Poll until the call reaches a non-ringing state.

```ts
async function waitConnected(sid: string) {
  for (;;) {
    const c = await v.calls.get({ sid });
    if (c.status !== "dialing" && c.status !== "ringing") return c.status;
    await new Promise((r) => setTimeout(r, 500));
  }
}
const final = await waitConnected(call.sid); // in_progress | failed | no_answer
```

## Bridge caller audio into your own ASR

Subscribe uplink (what the caller says) and feed it to your recognizer.

  <Tab title="TypeScript">
```ts
const bridge = await v.audioBridge.attach(call.sid, {
  codec: "opus",
  onUplink: (frame, tsUs) => myAsr.feed(frame),
});
```
  </Tab>
  <Tab title="Python">
```python
bridge = v.audio_bridge.attach(
    call.sid,
    on_uplink=lambda frame, ts_us: asr.feed(frame),
    codec="opus",
)
```
  </Tab>

## Play synthesized audio back to the caller

Push downlink frames as your TTS produces them.

  <Tab title="TypeScript">
```ts
myTts.onChunk((opus) => bridge.publishDownlink(opus));
```
  </Tab>
  <Tab title="Python">
```python
tts.on_chunk(lambda opus: bridge.publish_downlink(opus))
```
  </Tab>

## Choose a codec to avoid transcoding

For a PSTN-direct call, ask for G.711 so the bridge passes audio through
untranscoded.

```ts
const bridge = await v.audioBridge.attach(call.sid, {
  codec: "g711_ulaw",   // µ-law, no transcoding on a North-American PSTN leg
  onUplink: (frame, tsUs) => recorder.write(frame),
});
```

## Feed a model that wants raw PCM

Many realtime models prefer 16-bit PCM. Ask for `pcm16` and the bridge transcodes
both directions for you.

```ts
const bridge = await v.audioBridge.attach(call.sid, {
  codec: "pcm16",
  sampleRate: 16000,
  onUplink: (pcm, tsUs) => model.appendAudio(pcm),
});
model.onAudioOut((pcm) => bridge.publishDownlink(pcm));
```

## Transfer a live call to a person

Forward the audio to another PSTN number; the same `sid` continues.

```ts
await call.transfer({ to: "+15557654321" });
```

## Hand a call from a bot to a human (or vice-versa)

Re-attach the call to a different agent without forwarding it off-net.

```ts
await call.transfer({ agent: "senior-agent" });
```

## Attach an agent to an already-running call

Use `agents.attach` against a live `sid` — e.g. promote a call to an AI agent
mid-conversation.

  <Tab title="TypeScript">
```ts
await v.agents.attach(call.sid, "billing-bot");
```
  </Tab>
  <Tab title="Python">
```python
v.agents.attach(call.sid, "billing-bot")
```
  </Tab>

## Hang up and clean up

End the call, then release the bridge to tear down both tracks.

```ts
await call.hangup();
await bridge.close();
```

## Place a call from the browser (softphone)

Use the caller-side bridge plus the mic capture and Opus playback helpers — no
WebRTC transport for media.

```ts
import { Voice } from "@clutchcall/sdk/voice";
import { captureMicrophone, OpusPlayer } from "@clutchcall/sdk/moqt";

const call = await v.calls.originate({ to: "+15551234567", from: "+15558675309", trunkId: "trunk_main" });

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

const bridge = await v.audioBridge.attachCaller(call.sid, {
  codec: "opus",
  onDownlink: (frame, tsUs) => player.push(tsUs, frame),
});

const mic = await captureMicrophone(
  { write: (tsUs, frame) => bridge.publishUplink(frame, tsUs) } as any,
);

// hang up: mic.stop(); player.close(); await bridge.close(); await call.hangup();
```

## Point the bridge at a regional relay

Override `relayHost` to pin audio to a specific POP.

```ts
const v = new Voice({
  baseUrl: "https://engine.clutchcall.dev",
  apiKey:  process.env.CLUTCHCALL_CREDENTIALS!,
  orgId:   "org_abc",
  relayHost: "relay-us.clutchcall.dev",
});
```
