# Place an Outbound Call

> Originate a call over a SIP trunk from code with calls.originate. Then poll calls.get for its status.

Dial a phone number from your backend. `calls.originate({ to, from, trunkId })`
returns immediately with a `sid`. The control plane is request/response, so you
re-fetch with `calls.get({ sid })` to watch the call progress. You can also pass
`agent`. The engine then attaches a server-side voice agent on answer, with no
audio bridge in your process.

## Client

Every snippet below assumes a configured `Voice` client.

  
```ts
import { Voice } from "clutchcall_connect/voice";

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

v = Voice(
    base_url="https://engine.firstbyte.fast",
    api_key=os.environ["CLUTCHCALL_CREDENTIALS"],
    org_id="org_abc",
)
```
  

## Place the call

Originate over a SIP trunk and read the returned `sid`. The `sid` is the
universal key that addresses the call for status, transfer, hangup, and audio.

  
```ts
const call = await v.calls.originate({
  to:      "+15551234567",   // E.164 destination
  from:    "+15558675309",   // caller-id, must be routable on the trunk
  trunkId: "trunk_main",
});
console.log("dialing", call.sid, call.status);   // dialing
```
  
  
```python
call = v.calls.originate(
    to="+15551234567",       # E.164 destination
    from_="+15558675309",    # caller-id, must be routable on the trunk
    trunk_id="trunk_main",
)
print("dialing", call.sid, call.status)   # dialing
```
  

> **NOTE:**
> `trunkId` names a SIP trunk that you already provisioned. Create one in the
> console or via the admin API before you call `originate`. See
> [SIP trunking](/modalities/voice/transport-telephony/sip-trunking).

## Attach an AI agent on answer

Pass `agent`. The engine then connects the audio bridge automatically when the
callee answers. The server-side agent drives both legs of the conversation, so
you do not open an `AudioBridge` yourself.

  
```ts
const call = await v.calls.originate({
  to:      "+15551234567",
  from:    "+15558675309",
  trunkId: "trunk_main",
  agent:   "appointment-reminder",
});
```
  
  
```python
call = v.calls.originate(
    to="+15551234567",
    from_="+15558675309",
    trunk_id="trunk_main",
    agent="appointment-reminder",
)
```
  

## Cap the ring time

This example stops the attempt after 15 seconds instead of the default 30
seconds. The server clamps this value to `5..120`.

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

## Poll the call's status

The control plane is request/response. Re-fetch to read the latest status.
Status is one of `dialing`, `ringing`, `in_progress`, `completed`, `failed`, or
`no_answer`.

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

## Wait for the call to connect

Poll until the call leaves the ringing states. Then act on the outcome.

  
```ts
async function waitConnected(sid: string): Promise<CallStatus> {
  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 outcome = await waitConnected(call.sid); // in_progress | failed | no_answer | completed
if (outcome === "in_progress") console.log("callee picked up");
else console.log("call ended early:", outcome);
```
  
  
```python
import time

def wait_connected(sid: str) -> str:
    while True:
        c = v.calls.get(sid=sid)
        if c.status not in ("dialing", "ringing"):
            return c.status
        time.sleep(0.5)

outcome = wait_connected(call.sid)   # in_progress | failed | no_answer | completed
if outcome == "in_progress":
    print("callee picked up")
else:
    print("call ended early:", outcome)
```
  

> **TIP:**
> For high-volume outbound (thousands of numbers at a paced dials-per-second), do
> not loop `originate` yourself. Use the built-in paced campaign dialer. It owns
> the pacing and concurrency caps on the engine side. See
> [Outbound & dialer](/modalities/voice/transport-telephony/outbound).

## Hang up

End the call when you are done. If you attached an `AudioBridge` yourself
instead of passing `agent`, close the bridge first so that both tracks close
cleanly.

  
```ts
await call.hangup();
```
  
  
```python
call.hangup()
```
  

## Related

  - **[Bring your own ASR](/modalities/voice/cookbooks/stream-to-asr)** — Skip the agent and bridge caller audio into your own recognizer.
  - **[Transfer to a human](/modalities/voice/cookbooks/transfer-human)** — Hand a live call off to a person or a different agent.
  - **[Outbound & dialer](/modalities/voice/transport-telephony/outbound)** — Paced campaigns, concurrency caps, and the originate app modes.
  - **[Calls API](/modalities/voice/api/calls)** — Full request/response shape for originate, get, transfer, and hangup.
