Open in
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
- Choose an outbound-enabled number
Use a number you own through a VoIP connection, or request permission to use your ThunderPhone numbers for outbound calls from the Phone Numbers page. ThunderPhone numbers start inbound-only. ThunderPhone reviews each request manually; once approved, those numbers can place calls up to the approved daily limit (resetting at 00:00 UTC) and run campaigns, and you can ask for a higher limit. Customer VoIP numbers have no daily limit, so we recommend them for outbound calling. See Bring your own numbers.
- 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_ordertoagent_first(the default). - Keep a positive balance
Outbound calls return
402 Payment Requiredif balance is ≤$0.00. Top up or enable auto-reload under Organization → Billing Settings in the dashboard. Yoursk_live_key can check the balance withGET /v1/billingbut can't top up or change 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": "+14155552671",
"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": "+14155552671",
"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.
If your agent is set up to extract structured data after the call, that data is delivered separately after finalization completes.
Pass per-call variables to a saved agent
Saved agents can use placeholders like {{name}} or {{name|there}} in
prompt text. When you place an outbound call with agent_id, pass
variables with the values for that call:
{
"from_number": "+[REDACTED_PHONE]",
"to_number": "+[REDACTED_PHONE]",
"agent_id": 12,
"variables": {
"name": "Jordan"
}
}Use string values keyed by placeholder name. These variables apply to
saved agents; this guide's inline config example does not use them.
Failure modes worth handling
| Error | Fix |
|---|---|
402 Payment Required | Top up the balance or enable auto-reload |
400 with code: destination_not_allowed | ThunderPhone does not call that number: invalid numbers, emergency numbers, premium-rate numbers, short codes, or sanctioned countries. From ThunderPhone numbers, only ordinary US and Canadian numbers (no premium-rate). reason says which; see blocked destinations |
409 with retryable: false for a ThunderPhone number | Follow the code: request outbound calling on the Phone Numbers page, wait for a pending review, or use another eligible number |
429 with code: platform_outbound_daily_limit_reached | The organization used today's outbound calls from ThunderPhone numbers. Wait for resets_at (00:00 UTC), request a higher limit on the Phone Numbers page, or call from a number on your own VoIP connection |
429 with code: concurrent_call_limit_reached | The organization has as many calls in progress as its concurrent-call limit (20 by default, counting inbound, outbound, web and test calls). Retry after the Retry-After header's seconds or when a call ends; email support@thunderphone.com to raise the limit |
429 with code: same_number_too_soon | You called the same to_number less than 10 seconds ago. Retry after the Retry-After header's seconds |
409 with retryable: false for a customer VoIP number | Correct the state named by code; for verification failures, run POST /v1/phone-numbers/{id}/verify-voip |
409 idempotency conflict | Reuse the key only with the original normalized request, or send a new key |
404 from_number is not registered to this organization | Confirm the from_number matches an active phone number in your organization. A number that is already provisioning, failed, releasing, or released also returns this response |
500 with only a detail field | No outbound trunk is configured. Contact support; this is not a number-state problem you can correct |
502 Bad Gateway | Transient SIP / LiveKit failure; safe to retry |
See the outbound calls reference
for the complete stable eligibility-code list and both 409 response shapes.
If an active number becomes ineligible after the request is accepted but before
the call is placed, the final check returns the non-retryable 409 payload.
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": "+14155552671",
"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 seconds (15 minutes).