> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Place outbound calls (API)

> Trigger an AI-driven outbound call from your own code — survey, follow-up, or confirmation flows.

Outbound calling lets you hand a destination number and an agent
configuration to ThunderPhone and have the AI place the call on your
behalf. Typical use cases:

* Appointment confirmations
* Survey callbacks
* "Second-try" follow-ups after a missed call
* Dispatch-style notifications

<Note>
  Calling a whole list? The dashboard's
  [**Campaigns**](/guides/outbound-campaigns) feature
  (`/dashboard/campaigns`) takes a CSV of contacts and handles
  timezone-aware calling windows, concurrency, and retry policy for
  you. This guide covers single programmatic calls.
</Note>

## Prerequisites

<Steps>
  <Step title="Bring a VoIP number">
    Outbound calling requires you to own the `from_number` through a
    [VoIP connection](/api-reference/voip-connections). Demo numbers
    are inbound-only. See
    [Bring your own numbers](/guides/bring-your-own-numbers).
  </Step>

  <Step title="Create an agent">
    An outbound-flavoured prompt tends to start with the agent
    identifying itself and its purpose — "Hi, this is Acme calling to
    confirm your appointment for tomorrow at 3pm…" Set
    `outbound_speak_order` to `agent_first` (the default).
  </Step>

  <Step title="Keep a positive balance">
    Outbound calls return `402 Payment Required` if balance is ≤
    `$0.00`. Top up via
    [`POST /v1/billing/top-up`](/api-reference/billing#top-up-balance)
    or enable [auto-reload](/api-reference/billing#update-auto-reload).
  </Step>
</Steps>

## Place a call with a saved agent

The simplest path — reference an agent by id:

```bash theme={null}
curl -X POST https://api.thunderphone.com/v1/call \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from_number": "+15551234567",
    "to_number":   "+14155550199",
    "agent_id":    12
  }'
```

Response:

```json theme={null}
{ "call_id": 987654321, "status": "initiated" }
```

<Warning>
  `status: "initiated"` just means the request was accepted — the call
  is **not yet connected**. Poll
  [`GET /v1/calls/{call_id}`](/api-reference/calls#retrieve-a-call)
  for the live status (`in_progress` → `completed` / `failed`).
</Warning>

## Place a call with inline config

If you want a one-off prompt that isn't worth saving as an agent,
pass `config` instead. The shape matches the response schema of the
[`call.incoming` webhook](/webhooks/call-incoming#response-schema):

```bash theme={null}
curl -X POST https://api.thunderphone.com/v1/call \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from_number": "+15551234567",
    "to_number":   "+14155550199",
    "config": {
      "prompt":  "You are confirming Jane Doe appointment for 3pm tomorrow…",
      "voice":   "john",
      "product": "spark"
    }
  }'
```

## Follow the call

In parallel, subscribe to the
[`telephony.complete` webhook](/webhooks/events#telephony-complete) —
the fastest way to know a call finished. If you can't accept inbound
webhooks, poll `GET /v1/calls/{call_id}` every couple of seconds; the
record includes `end_reason`, `duration_seconds`, and the recording
URL once the call ends.

## Failure modes worth handling

| Error                                                    | Fix                                                                                                        |
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `402 Payment Required`                                   | Top up the balance or enable auto-reload                                                                   |
| `403` outbound blocked (demo number)                     | Bring a VoIP number instead                                                                                |
| `403` outbound blocked (unverified VoIP)                 | Run [`POST /v1/phone-numbers/{id}/verify-voip`](/api-reference/phone-numbers#verify-a-voip-sourced-number) |
| `404 from_number is not registered to this organization` | Confirm the `from_number` matches a phone number you own                                                   |
| `502 Bad Gateway`                                        | Transient SIP / LiveKit failure; safe to retry                                                             |

## Controlling hold time

Outbound calls that run long because the callee is slow to respond
(IVR trees, queues) can be capped with `max_hold_seconds`:

```json theme={null}
{
  "from_number": "+15551234567",
  "to_number":   "+14155550199",
  "agent_id":    12,
  "max_hold_seconds": 120
}
```

The agent hangs up if no human audio has been received in the last
N seconds. Default is 900 (15 minutes).

***

## Next steps

<CardGroup cols={2}>
  <Card title="Outbound calls reference" icon="phone-arrow-up-right" href="/api-reference/outbound-calls">
    Every request field and error code.
  </Card>

  <Card title="Receive call.complete" icon="bolt" href="/webhooks/call-complete">
    Stream finished outbound calls to your system.
  </Card>

  <Card title="Billing" icon="credit-card" href="/api-reference/billing">
    Auto-reload so outbound never fails on balance.
  </Card>

  <Card title="Test outbound agents" icon="flask" href="/guides/test-agents">
    Dry-run your outbound agent before production.
  </Card>
</CardGroup>
