ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Developer cookbook

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

Prerequisites

  1. Bring a VoIP number

    Outbound calling requires you to own the from_number through a VoIP connection. Demo numbers are inbound-only. See Bring your own numbers.

  2. 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).

  3. Keep a positive balance

    Outbound calls return 402 Payment Required if balance is ≤ $0.00. Top up via POST /v1/billing/top-up or enable auto-reload.

Place a call with a saved agent

The simplest path — reference an agent by id:

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:

{ "call_id": 987654321, "status": "initiated" }

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:

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 — 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

ErrorFix
402 Payment RequiredTop 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
404 from_number is not registered to this organizationConfirm the from_number matches a phone number you own
502 Bad GatewayTransient 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:

{
  "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