# ThunderPhone Documentation — full content --- title: "Introduction" description: "Documentation for running production voice agents over real phone calls and embeddable web widgets: quickstarts, guides, and the full ThunderPhone API reference." --- ThunderPhone lets you build AI voice agents that place and answer real phone calls and power mic-driven conversations on your website. The documentation is split into four tabs: Quickstarts, concepts, and how-tos for the dashboard and the API. **Start here** if you're new. Every endpoint, request field, and response shape. The embeddable web widget — React component, headless hook, and CDN script tag. Real-time events, signing, and delivery semantics. ## What you can build Answer calls to a number you own with an AI agent that follows your prompt, uses your tools, and hands off to humans when needed. Place programmatic calls from your code — survey, follow-up, or confirmation flows. Drop our embeddable [web widget](/guides/embed-web-widget) onto your site for customers to click-to-talk. No phone number required. Run canned scenarios through your agent in [batches](/api-reference/test-calls) so regressions get caught before customers hear them. ## Two ways to start **In the dashboard** (recommended for your first agent) — sign up and the dashboard walks you through a five-step setup wizard: create an agent, set up billing, add a phone number, run a simulation, and review the call. The [dashboard quickstart](/quickstart-dashboard) mirrors that wizard step by step. **From your terminal** — the [API quickstart](/quickstart) takes the same first call in four REST calls. [Concepts](/guides/concepts) — a map of every object you'll touch, and where each one lives in the dashboard. [Dashboard quickstart](/quickstart-dashboard) or [API quickstart](/quickstart) — take your first call with an AI agent. Follow a [how-to guide](/guides/build-an-agent) for your scenario, or dive into the [API reference](/api-reference/introduction). ## Browse the guides Build an agent, fund it, get a number, simulate, and review calls. OAuth apps, custom APIs, MCP servers, and VoIP providers. Reports, observability, experiments, issues, and alerts. Invites and roles, API keys, security, and SSO. ## Base URL ``` https://api.thunderphone.com/v1 ``` Authentication uses the standard OAuth 2.0 Bearer scheme with an `sk_live_` organization API key. Your key identifies the target organization automatically — there is no org id in URL paths. See [Authentication](/api-reference/introduction) for details. Need help? Email [support@thunderphone.com](mailto:support@thunderphone.com). --- title: "Quickstart (dashboard)" description: "From signup to your first reviewed call in five guided steps: create an agent, talk to it, get a phone number, place a real call, and review the graded result." --- New accounts land in a five-step setup wizard, with a coach that tracks your progress and guided tours on every page. This quickstart mirrors that wizard, so you can follow along here or just click **Next** in the app. You'll need a [ThunderPhone account](https://app.thunderphone.com). Sign up is free and takes under a minute. Prefer the terminal? The [API quickstart](/quickstart) reaches the same first call in four REST calls. The wizard opens the agent builder with a draft agent already created. Three things to do here: 1. **Write your prompt.** The prompt is the script for your voice agent — describe, in any language, who the agent is and how it should handle the call. Call actions like transfers, keypad presses, and hang-ups are plain prompt lines too; no separate configuration. 2. **Talk to your agent.** Click **Talk** and say hello — the agent answers right in the browser (it'll ask for mic access). 3. **Deploy your changes.** Every edit autosaves to your draft, but drafts aren't live until you click **Deploy**. Each deploy is snapshotted in the **History** tab, so you can restore any version later. On the **Config** tab you can also pick an engine tier (cost vs. speed vs. intelligence), set the primary and additional languages, and attach [connected apps and API connections](/guides/concepts#connections). Zeus shows what a minute of your agent costs before you go live. Full detail: [Build your first voice agent](/guides/build-an-agent). ThunderPhone is pay-as-you-go: each call draws from your org's balance what its parts cost at our providers' list prices, plus an infrastructure charge and a markup. No subscriptions, no seats. Under **Organization → Billing Settings**: 1. **Add a payment method.** A card unlocks top-ups and phone numbers. It's only charged when you add funds — never automatically. 2. **Load at least $3.00** to start making real calls. Funds never expire. Optionally flip on **auto top-up** (threshold, refill amount, and an optional monthly spending limit) so a call never dies mid-sentence. Full detail: [Add funds and turn on auto top-up](/guides/billing-and-topups). On **Phone Numbers**, click **Add number**. Two paths: - **ThunderPhone number** — a real U.S. number, live in seconds. It costs {/* rate:phone */}$1/month{/* /rate */}, its call minutes are billed as the phone line, and it starts inbound-only. An organization admin can request outbound calling from the Phone Numbers page; ThunderPhone reviews each request manually. - **Connect VoIP provider** — import numbers you already own. Customer VoIP numbers continue to support outbound calls and campaigns. Provisioning auto-routes the number to your agent; the **Inbound** select on the number row shows (and changes) who answers. Full detail: [Get a phone number](/guides/get-a-phone-number). Back in the agent builder, click **Simulation**. An AI caller rings your number and has a real conversation with your agent — generate a caller scenario with AI or write your own, approve the charge, and place the call. A floating activity pill shows the call happening live. (Or just dial your number from your own phone — that counts too.) Full detail: [Simulate a call](/guides/simulate-a-call). Every call lands in **Call History**, newest first. Open the top row — your simulation. If it's still in progress you can **listen in** live and even **whisper** instructions only your agent hears. Once it ends, read the transcript turn by turn and scrub the recording waveform. Full detail: [Review, listen to, and coach your calls](/guides/review-calls). Lost the wizard? Every tour is replayable from the sidebar's **Guided tours** menu, and the in-app copilot can walk you through any of these flows on demand — just ask it "how do I add a phone number." ## Next steps Bring in teammates with Member or Admin roles. Ready to automate? Mint an `sk_live_` key and switch to the API. The map of everything else — campaigns, knowledge, experiments, alerts, and more. The same first call in four REST calls. --- title: "Quickstart (API)" description: "Answer your first phone call with an AI agent: get an API key, create an agent, provision a phone number, and place a live test call — all through the REST API." --- This guide walks you through the four REST calls it takes to answer your first phone call with an AI agent. You'll need a [ThunderPhone account](https://app.thunderphone.com). Sign up is free and takes under a minute. Prefer clicking to curling? The [dashboard quickstart](/quickstart-dashboard) reaches the same first call without writing any code. ## Step 1: Get an API key Open [app.thunderphone.com](https://app.thunderphone.com). Go to **Organization → Keys** in the dashboard. Click **Create key**, give it a name, and copy the `sk_live_...` value. The raw key is shown **only once** — store it in your secret manager immediately. Stuck? [Create a server API key](/guides/api-keys) walks this flow in step-by-step detail, and the in-app copilot can spotlight each control for you live. Throughout this guide, swap `sk_live_YOUR_API_KEY` with the value you just copied. The key identifies your organization automatically, so you never need to put an org id in URLs. ## Step 2: Create an agent An agent defines how the AI handles conversations — prompt, voice, product tier, tools, and widget eligibility. ```bash cURL curl -X POST https://api.thunderphone.com/v1/agents \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Customer Support", "prompt": "You are a friendly support agent for Acme Corp. Help with orders, returns, and product info. Be concise and helpful.", "voice": "john", "product": "spark" }' ``` ```python Python import os, requests agent = requests.post( "https://api.thunderphone.com/v1/agents", headers={"Authorization": f"Bearer {os.environ['THUNDERPHONE_API_KEY']}"}, json={ "name": "Customer Support", "prompt": "You are a friendly support agent for Acme Corp. Help with orders, returns, and product info. Be concise and helpful.", "voice": "john", "product": "spark", }, ).json() print("Agent id:", agent["id"]) ``` ```javascript Node.js const agent = await fetch("https://api.thunderphone.com/v1/agents", { method: "POST", headers: { Authorization: `Bearer ${process.env.THUNDERPHONE_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Customer Support", prompt: "You are a friendly support agent for Acme Corp. Help with orders, returns, and product info. Be concise and helpful.", voice: "john", product: "spark", }), }).then((r) => r.json()); console.log("Agent id:", agent.id); ``` Product tiers: `spark` is optimized for cost, `bolt` for speed, and `storm-base` / `storm-extra` for intelligence on complex prompts. See [Agents](/api-reference/agents#product-tiers-at-a-glance) for the full comparison. ## Step 3: Provision a phone number This call asks ThunderPhone for a number and assigns your new agent as the inbound handler. (To bring your own number from a VoIP provider, see [VoIP connections](/api-reference/voip-connections) instead.) ```bash cURL # First provision curl -X POST https://api.thunderphone.com/v1/phone-numbers \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"area_code": "415"}' # Then assign the agent you created in Step 2 curl -X PATCH https://api.thunderphone.com/v1/phone-numbers/ \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"inbound_agent_id": 12}' ``` ```python Python number = requests.post( "https://api.thunderphone.com/v1/phone-numbers", headers={"Authorization": f"Bearer {os.environ['THUNDERPHONE_API_KEY']}"}, json={"area_code": "415"}, ).json() requests.patch( f"https://api.thunderphone.com/v1/phone-numbers/{number['id']}", headers={"Authorization": f"Bearer {os.environ['THUNDERPHONE_API_KEY']}"}, json={"inbound_agent_id": agent["id"]}, ) print("Your ThunderPhone number:", number["number"]) ``` Your new number starts in `status="provisioning"` and transitions to `active` within a few seconds; by the time you hang up your browser the number is ready to take calls. ## Step 4 (optional): Set up a webhook For real-time events (dynamic call routing, post-call processing) add a webhook endpoint. Subscribe only to the events you need. ```bash curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "label": "Prod webhook", "url": "https://your-server.com/thunderphone-webhook", "events": ["telephony.incoming", "telephony.complete"] }' ``` The response contains a one-shot `secret` — copy it to your secret manager. Use that secret to verify the `X-ThunderPhone-Signature-V2` header on incoming requests (see [Webhooks overview](/webhooks/overview#signature-verification)). ## Step 5: Test your agent Call the number you just provisioned. The agent picks up, introduces itself, and follows your prompt. Inspect the call after it ends: ```bash curl https://api.thunderphone.com/v1/calls \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" ``` Drill into a specific call to fetch the transcript and recording URL: ```bash curl https://api.thunderphone.com/v1/calls/{call_id}/transcript \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" curl https://api.thunderphone.com/v1/calls/{call_id}/audio \ -H "Authorization: Bearer sk_live_YOUR_API_KEY" ``` --- ## Next steps Let your agent call your APIs during a conversation. Trigger calls from your own code. React to call events in real time. Every public endpoint, documented. --- title: "Build with Claude Code, Codex or Cursor" description: "Install ThunderPhone Agent Skills, connect the ThunderPhone MCP server, and give a coding agent current product documentation." --- ThunderPhone provides three layers for coding agents: | Layer | What it gives the agent | | --- | --- | | [Agent Skills](https://github.com/thunderphone/skills) | Focused procedures for agents, prompts, numbers, calls, tools, webhooks, knowledge, testing, improving an agent in a closed loop, campaigns, widgets, realtime frameworks and migrations. | | MCP | Authenticated tools cover agents, numbers, calls, testing, knowledge, tools/webhooks, campaigns, voices, imports and docs as they roll out; call `tools/list` for the live catalog. | | [Raw documentation](https://thunderphone.com/docs/llms.txt) | Current Markdown sources for product behavior and API details. | Skills explain how to do the work. MCP lets the coding agent inspect and act on your organization. Raw docs provide the source material for questions outside a loaded skill. ## Install in a project This block installs the skills, makes the key available to the current shell, and adds the project-scoped MCP server to Claude Code. Replace the placeholder by reading the real value from your secret manager; do not paste a key into a repository or chat. ```bash npx skills add thunderphone/skills export THUNDERPHONE_API_KEY='replace-with-value-from-your-secret-manager' claude mcp add --transport http --scope project \ --header 'Authorization: Bearer ${THUNDERPHONE_API_KEY}' \ thunderphone https://api.thunderphone.com/v1/mcp ``` For Codex, replace the final command with: ```bash codex mcp add thunderphone \ --url https://api.thunderphone.com/v1/mcp \ --bearer-token-env-var THUNDERPHONE_API_KEY ``` Cursor reads a project configuration from `.cursor/mcp.json`: ```json { "mcpServers": { "thunderphone": { "type": "http", "url": "https://api.thunderphone.com/v1/mcp", "headers": { "Authorization": "Bearer ${env:THUNDERPHONE_API_KEY}" } } } } ``` VS Code uses the same server under a `servers` key in `.vscode/mcp.json`: ```json { "servers": { "thunderphone": { "type": "http", "url": "https://api.thunderphone.com/v1/mcp", "headers": { "Authorization": "Bearer ${env:THUNDERPHONE_API_KEY}" } } } } ``` An organization API key can read organization data and perform enabled actions. Keep it in a secret manager, scope access to the intended project, and review mutating tool calls before approval. ## Example session: bilingual dental receptionist 1. Ask the coding agent to load `create-agent`, `thunderphone-prompt-builder`, `build-tool-integration`, `add-knowledge`, and `test-agent`. Tell it to plan first and not create resources yet. 2. Give it verified practice facts, supported tasks, escalation rules and the two required locales. Have it check the live voice catalog and supported languages instead of inventing a voice or locale. 3. Ask for one production prompt built from general principles and facts. The agent should switch to the caller's supported language, keep speech concise, ask one question at a time, and avoid caller-response scripts. 4. Have it define a narrow scheduling tool and attach only the knowledge the receptionist needs. Test tool success, no availability, invalid input, timeout and human handoff before deployment. 5. Generate scenarios for both languages, interruptions, corrections, missing information, knowledge questions, scheduling failures and escalation. Run them, inspect transcripts and tool evidence, fix the smallest general rule, and rerun the same set. 6. Review the exact draft and test evidence, then explicitly authorize deploy and number attachment. Place a controlled audio call in each language before moving real traffic. ## Improve an agent in a closed loop Once an agent takes calls, ask the coding agent to load `iterate-on-agent`. The skill runs the same loop ThunderPhone uses on its own agents, so each change is measured before it ships: 1. **Read** the agent, its [prompt revisions](/api-reference/prompt-revisions) and the [prompt style guide](/guides/prompt-style). Prompt revisions are available over REST and MCP, so a coding agent can inspect recent prompt changes directly. 2. **Build test sets.** `import_conversations` turns recent calls into [test conversations](/api-reference/conversations) and their hardest moments into examples with answer keys; `generate_conversations` writes them for a new agent, including difficult callers aimed at the prompt's weak spots (those always go to the dev split). Each conversation sits in a dev split, for tuning, or a test split, for measuring once. A call whose caller is in a test split is held out: to an API key or OAuth token, over REST and MCP alike, it comes back as its id, start time and `held_out: true` only, writes on it are refused (`held_out_call`), and so are test scenarios promoted from it; leave it out when tuning. 3. **Take a baseline** with `run_replays` on the dev split. With Bolt v2, a turn with recorded audio is [replayed](/api-reference/replay-runs) as a live call would run it. 4. **Edit the draft.** `review_prompt` checks the prompt against the style guide and offers find and replace edits; `update_agent` applies them to the draft with `prompt_edits`; a second review with `previous_prompt` checks what changed. `refactor_prompt` rewrites a prompt that has collected too many patches, and reports any behavior it changed. When an edit adds or removes a behavior, `refresh_conversations` brings the test conversations up to the new prompt, restoring earlier versions where a rule came back. 5. **Verify** with `run_replays` against the baseline, reading the failures the way a caller would hear them. `compare_configs` tries a different audio tier, voice, endpointing or intelligence tier on the same dev examples; comparisons are dev-only. 6. **Measure once** on the test split. A test run replays one whole test cohort and its first result spends it, so each later test run, for any prompt, needs fresh test data from imported calls or generated conversations. Each example's engine is set before the run from its data (recorded audio replays exactly, text-only examples as text), totals come per engine, and nothing falls back to text: a cohort with recorded audio under a configuration other than Bolt v2 is refused (`test_needs_exact_replay`) and stays unused, so measure that configuration on dev. Coding agents get its totals only, so they cannot tune on it. 7. **Listen** to a `run_simulation` recording, or open a [talk link](/api-reference/talk-links) and talk to the draft yourself. 8. **Deploy** when you say so. Everything that spends credits quotes first: send `dry_run: true` for an estimate, then run with its `quote_id` and `max_cost_cents`, a hard cap. A quote pays for one run: conversation jobs, replay runs, and prompt reviews and refactors that call the model all use it up, durably, when they start, and a start that is refused or fails first leaves it usable; after it is used, repeat a failed run with a new dry run. Each request sets aside its most possible cost before it is sent, and the run is charged what it really cost, so leave the estimate's headroom. An exact replay sets aside about 85 cents to $1.25 per replay in flight for a typical turn (a normal turn plus one production re-run), against a real cost of a few cents: give replay runs a cap with room. A turn that still runs out of budget is counted apart and spends no test cohort. An agent runs one conversation job or replay run at a time. With OAuth, the prompt tools need the `agents:write` scope. When a call's recording is turned off or the call is deleted, the test conversations and examples taken from it are removed too. The test conversation, replay, prompt review and talk link tools are part of the agent iteration toolkit, which is enabled per organization while it is in beta; ask [support](mailto:support@thunderphone.com) to turn it on. Use the [Python or TypeScript SDK](/api-reference/sdks) for typed REST requests. Use REST instead of MCP when a build needs deterministic payloads, versioned fixtures, bulk orchestration, or an operation not present in the current MCP tool list. The API starts at `https://api.thunderphone.com/v1`; see the [API introduction](/api-reference/introduction) and [MCP guide](/guides/thunderphone-mcp-server). --- title: "ThunderPhone CLI" description: "Log in, manage voice agents and calls, configure MCP clients, and scaffold projects from the terminal." --- The CLI requires Node.js 18.18 or later. Run it with `npx`, or install it globally: ```bash npm install -g thunderphone thunderphone --help ``` ## Log in ```bash npx thunderphone login thunderphone whoami --json ``` Login prints a verification code, opens the browser, and waits for authorization. Use `--no-browser` on a headless machine and open the printed URL elsewhere. Device login requires the API's OAuth device-flow rollout. An organization API key in `THUNDERPHONE_API_KEY` can also authenticate commands and takes precedence over stored login credentials. Credentials are stored by profile in `~/.config/thunderphone/credentials.json` with file mode `0600`. `XDG_CONFIG_HOME` and `THUNDERPHONE_CONFIG_DIR` override the directory. Access tokens refresh automatically and rotating refresh tokens are saved atomically. Tokens are never printed by login. ```bash thunderphone login --profile work thunderphone agents list --profile work --json thunderphone logout --profile work ``` `THUNDERPHONE_PROFILE` sets the default profile. Logout removes local credentials; it does not revoke the server grant. Changing or resetting your password, or changing your account email, does revoke it; run `thunderphone login` again. The default API is `https://api.thunderphone.com`. Use `--api-base-url` or `THUNDERPHONE_API_BASE_URL` for another API. A profile's token cannot be sent to a different configured API URL; log into that URL with a separate profile. ## Commands Every command accepts `--json`. Successful data goes to stdout; errors go to stderr with a nonzero exit code. Without `--json`, output is a compact table or field list. JSON retains the API response structure, with credential fields redacted. MCP serving reserves stdout for the MCP protocol. | Command | Purpose | | --- | --- | | `login [--no-browser] [--scope "scopes"]` | Authorize through a device code | | `logout`, `whoami` | Remove local credentials; list accessible organizations | | `agents list`, `agents get ID` | Read agents | | `agents create --file agent.json` | Create an agent | | `agents update ID --file agent.json` | Save an agent draft | | `agents deploy ID`, `agents delete ID` | Deploy the draft; delete an agent | | `numbers list` | Read phone numbers | | `numbers provision --country US --area-code 415` | Provision a US inbound number | | `numbers assign NUMBER_ID --agent ID` | Assign an inbound agent | | `sms send TO --from FROM --body TEXT --idempotency-key KEY` | Send SMS through a customer-connected carrier number with safe replay | | `sms list [--limit N]`, `sms get UUID` | List or inspect SMS records | | `call TO --agent ID --from FROM [--wait]` | Place an outbound call; optionally wait for its transcript | | `calls list [--limit N] [--agent ID]` | List calls (limit 1–200) | | `calls get ID`, `calls transcript ID` | Read one call or its transcript | | `test run AGENT_ID [--scenario ID] --consent-to-charge` | Run billable saved scenarios using the web channel | | `test status RUN_ID [--agent ID]` | Inspect a batch, with scenario details when an agent is supplied | | `import VENDOR export.json` | Prepare a vendor JSON import for review | | `imports get UUID`, `imports commit UUID` | Inspect an import; commit selected agents after review | | `mcp setup`, `mcp serve` | Configure a client; start the stdio bridge | | `init [directory]` | Scaffold a project | Outbound calls require an outbound-enabled number. `THUNDERPHONE_FROM_NUMBER` can supply `--from`. Customer VoIP numbers continue to support outbound calls. ThunderPhone-provisioned numbers start inbound-only; an organization admin can request outbound calling from the Phone Numbers page, where each request is reviewed manually. `--wait` defaults to a one-hour timeout; set `--timeout SECONDS` to change it. A timeout stops waiting without ending the call. SMS also requires an SMS-capable number on your connected Twilio or Telnyx account. It never sends from a ThunderPhone-owned number. Reuse an idempotency key only when retrying the exact same sender, recipient, and body. Imports support `vapi`, `retell`, `elevenlabs`, and `bland`. The second argument is a path to a vendor JSON export, not a remote vendor ID. The API prepares a mapping asynchronously; inspect it with `imports get UUID` and commit only after it is ready and you have reviewed the mapped fields and warnings. See [agent imports](/guides/import-agents). ## MCP setup ```bash thunderphone mcp setup --client cursor --api-key-env THUNDERPHONE_API_KEY thunderphone mcp setup --client codex --scope user --api-key-env THUNDERPHONE_API_KEY thunderphone mcp setup --client claude-desktop --scope user ``` Clients: `claude-code`, `codex`, `cursor`, `vscode`, `gemini`, `claude-desktop`, and `windsurf`. Project scope is the default. Desktop and Windsurf require user scope. Omit `--client` to detect a single client from the project; ambiguous detection prompts interactively or fails with instructions in a non-interactive session. Setup prints the file it wrote and preserves unrelated servers. Existing JSON must be valid strict JSON; Codex TOML values are preserved but comments may be reformatted. Setup prefers direct remote HTTP. With `--api-key-env`, it writes a variable reference, never the key. Without that option, direct HTTP relies on the client's OAuth support; it does not use the CLI credential file. To reuse `thunderphone login` credentials in any stdio-capable client, use: ```json { "mcpServers": { "thunderphone": { "command": "npx", "args": ["-y", "@thunderphone/mcp"] } } } ``` The wrapper checks `THUNDERPHONE_API_KEY`, then the current CLI profile (refreshing when needed), then delegates OAuth to `mcp-remote`. `THUNDERPHONE_MCP_URL` overrides the endpoint. It emits no telemetry. Standalone setup is also available through `npx -y @thunderphone/mcp setup --client cursor` or `thunderphone-mcp setup` after installing `@thunderphone/mcp` globally. ## Create a project ```bash npx create-thunderphone-agent my-receptionist npx create-thunderphone-agent my-outbound --template python --agent-type outbound # Equivalent: thunderphone init my-receptionist --template node --agent-type receptionist ``` Templates are `node` and `python`; agent types are `receptionist`, `outbound`, and `custom`. The default agent is an English/Spanish dental receptionist with explicit demo facts. Projects include `agent.json`, deploy scripts, MCP configurations for Claude Code, Cursor and VS Code, agent instructions, `.env.example`, and a README. Scaffolding is offline and refuses to overwrite a nonempty directory. Edit the business facts before deployment. Export the API key, connected caller number, and your test recipient as documented in the generated README. `npm run deploy` or `python deploy.py` creates or updates the agent, deploys its draft, and places a billable test call. The scripts use API-key authentication; the CLI and stdio MCP wrapper also support device login. Python scripts require Python 3.9 or later. --- title: "Core concepts" description: "A map of everything in the platform — what each object does, where it lives in the dashboard, and which API touches it." --- ThunderPhone is a full platform for building, running, and improving AI voice agents. This page is the map: every concept you'll meet, one short section each, with the dashboard surface and the API that backs it. Skim it once, then come back whenever a term needs unpacking. The dashboard sidebar mirrors this structure: [Agents](#agents), [voices](#voices), [phone numbers](#phone-numbers), [web widgets](#web-widgets), [calls](#calls), [client portals](#client-portals), [knowledge](#knowledge-bases). [Live monitoring](#live-monitoring) and outbound [campaigns](#campaigns). [Apps, APIs, MCP servers, and VoIP providers](#connections) your agents can use. [Simulations](#simulations), [validation sets](#validation-sets), [experiments](#experiments), [issues](#issues), [reports](#reports), [observability](#observability). [Team & roles](#team-and-roles), [API keys](#organizations), [alerts](#alerts), [billing](#billing). [Webhooks](#webhooks) and [function tools](#function-tools) for your own code. --- ## Organizations An **organization** is the unit of tenancy. Every other resource — agents, phone numbers, calls, keys — belongs to exactly one org. Your account can belong to many orgs; each has its own balance, its own keys, and its own members list. The `sk_live_` API key you create under **Organization → Keys** is bound to one organization. That binding is what makes the REST API so flat: you never put an org id in URL paths, because your key already identifies it. **In the dashboard:** the organization switcher (sidebar footer) and **Organization** settings — tabs for My account, General, Keys, Alerts, Billing Settings, and Billing History. See the [Organization settings reference](/guides/organization-settings). **In the API:** [`/v1/orgs`](/api-reference/organizations), [`/v1/developer/api-keys`](/api-reference/developer-api-keys). --- ## Agents An **agent** is the AI configuration that runs a call. It bundles: - A **prompt** that governs what the agent says and how it behaves — including call actions like transfers, keypad presses, and hang-ups, which are plain prompt lines rather than separate configuration. - An **engine tier** (`spark`, `bolt`, `storm-*`): Spark is optimized for cost, Bolt for speed, Storm for intelligence on complex prompts. - A **voice** plus a **primary language** and optional **additional languages** — the agent switches automatically when a caller changes language. See [Supported languages](/guides/supported-languages). - Attached capabilities: [connected apps](#connections), [API connections](#connections), [knowledge bases](#knowledge-bases), [MCP servers](#connections), and inline [function tools](#function-tools). - Behavior knobs: speak order, acknowledgement mode, background track, hold timeout. Edits in the builder **autosave to a draft**; nothing goes live until you click **Deploy**. Every deploy is snapshotted in the builder's **History** tab, so you can inspect and restore any previous version. **In the dashboard:** **Voice Agents** → the agent builder (`/dashboard/agents`). See [Build your first voice agent](/guides/build-an-agent). **In the API:** [`/v1/agents`](/api-reference/agents) — CRUD, duplicate, transfer, version history, and prompt helpers. --- ## Voices The **voice library** contains the voices an agent can use, their playable samples, compatible languages, gender and accent groupings, and which voices are premium. A paid sampler can synthesize your own 1–500 character phrase before you choose. Eligible organizations can also create **custom voices** from a short WAV or MP3 sample. Custom voices have a quota and asynchronous creation status; once ready, they appear in the same agent picker as library voices. **In the dashboard:** **Voices** (`/dashboard/voices`). See [Voice library and custom voices](/guides/voice-library). **In the API:** [`/v1/voices`](/api-reference/voices), [voice samples](/api-reference/voice-samples), and [custom voices](/api-reference/custom-voices). --- ## Phone numbers A **phone number** belongs to an org and routes inbound calls to an agent (and can carry outbound calls). Two sources: - **ThunderPhone numbers** — real U.S. numbers provisioned from ThunderPhone's pool, live in seconds, {/* rate:phone */}$1/month{/* /rate */}; their call minutes are billed as the phone line. They start inbound-only. An organization admin can request outbound calling from the Phone Numbers page; ThunderPhone reviews each request manually, and approved numbers can place calls and run campaigns. An org can hold 25 by default. - **VoIP numbers** — brought from your own provider through a [VoIP connection](#connections). Twilio and Telnyx connect directly with an API key (Telnyx also has a guided new-account setup); SignalWire, Vonage, Plivo and any other SIP trunk connect through SIP setup, a manual SIP configuration. Once imported and verified, VoIP numbers support inbound and outbound. Each number row lets you set a routing mode, pick the inbound agent, and label the number. **In the dashboard:** **Phone Numbers** (`/dashboard/phone-numbers`). See [Get a phone number](/guides/get-a-phone-number). **In the API:** [`/v1/phone-numbers`](/api-reference/phone-numbers), [`/v1/voip-connections`](/api-reference/voip-connections), [`/v1/phone-number-labels`](/api-reference/phone-number-labels). --- ## Calls Every inbound call, outbound call, simulation, and widget session becomes a **call log**. A call carries the full role-tagged transcript, the structured turn history (including tool calls), a recording, the billing total, and optional AI grading and issue reports. While a call is **live**, you can open it and **listen in** — you join silently, and nobody on the call hears you. Once listening, you can **whisper**: type an instruction that goes straight to your agent mid-call; the caller never hears it, and the agent follows it live. **In the dashboard:** **Call History** (`/dashboard/call-history`) for the archive and per-call detail; **Live** for calls in progress. See [Review, listen to, and coach your calls](/guides/review-calls). **In the API:** [`/v1/calls`](/api-reference/calls) — list, transcript, history, audio, grading, export; [`/v1/issue-reports`](/api-reference/issue-reports). --- ## Client portals A **client portal** is a branded, read-only call-history view for an external client. Organization admins choose the agents whose calls appear, add approved viewer emails, upload a logo and accent color, and optionally verify a custom domain. Portal viewers can inspect call details, transcripts, and available recordings without receiving dashboard access. **In the dashboard:** **Client portals** (`/dashboard/client-portals`). See [Client portals](/guides/client-portals). **In the API:** [`/v1/client-portals`](/api-reference/client-portals) for the admin management surface. --- ## Web widgets The **web widget** gives your site visitors a mic-based conversation with an agent — no phone number needed. It authenticates with a **publishable key** (`pk_live_...`) that browsers can only use from your allowed domains, so it's safe in client-side code. The domain list is not authentication, and the session settings a page may send are off until you allow them on the key. Keys run in one of two modes: `agent` (statically bound to one agent) or `webhook` (your server picks the configuration per visitor — see [Dynamic per-call configuration](/guides/dynamic-call-config)). Widget sessions flow through the same call infrastructure as phone calls. **In the dashboard:** **Web Widgets** (`/dashboard/web-widgets`) — create widgets, set the mode and agent, manage allowed domains, and copy the embed snippet. See [Create a web widget](/guides/embed-a-web-widget-dashboard). **In the API:** [`/v1/publishable-key`](/api-reference/publishable-keys), [`/v1/mic-session`](/api-reference/mic-sessions), and the [Widget SDK docs](/widget/overview). --- ## Knowledge bases A **knowledge base** is a set of documents your agent can search mid-call to ground its answers — upload files, paste text, or import web pages by URL, then attach the knowledge base to an agent in the builder. The agent queries it with a built-in search tool whenever the conversation calls for it. **In the dashboard:** **Knowledge** (`/dashboard/knowledge`) for the document library; the builder's **Knowledge** section to attach one to an agent. See [Give your agent a knowledge base](/guides/knowledge-base). --- ## Connections Connections are how agents reach the outside world. Four kinds, one sidebar group: - **Apps** (`/dashboard/app-connections`) — OAuth connections to Slack, HubSpot, Salesforce, Google Calendar, Google Sheets, and Cal.com. Connect once, then toggle per-operation tools (post a Slack message, upsert a HubSpot contact, book a Cal.com slot…) onto any agent. See [Connect apps](/guides/connect-apps). - **APIs** (`/dashboard/api-connections`) — turn any HTTP API into an agent action. Paste a cURL command and the AI wizard drafts the tool definition, or build it manually; a **Test Request** button fires a sandbox call before you ship. See [API connections](/guides/api-connections) — the dashboard face of [`/v1/integrations`](/api-reference/integrations). - **MCP** (`/dashboard/mcp-connections`) — add a Model Context Protocol server by URL and let the agent use the tools it exposes. See [Add an MCP server](/guides/mcp-servers). - **VoIP** (`/dashboard/voip-connections`) — provider credentials for [bringing your own phone numbers](#phone-numbers). See [Connect a VoIP provider](/guides/voip-providers). ThunderPhone also exposes its own MCP endpoint so an external MCP client can list agents, inspect calls and transcripts, and place calls. See [Use ThunderPhone as an MCP server](/guides/thunderphone-mcp-server). **In the API:** [`/v1/integrations`](/api-reference/integrations), [`/v1/mcp-servers`](/api-reference/mcp-servers), and [`/v1/voip-connections`](/api-reference/voip-connections); see also [Build a tool integration](/guides/build-tool-integration). --- ## Campaigns A **campaign** places outbound calls at scale: upload a CSV of contacts, pick the agent and from-number, and set the calling window (days and hours, timezone-aware), concurrency, and retry policy (max attempts and which outcomes — no answer, voicemail, failed — get retried). The campaign works through the list and records every call in Call History. **In the dashboard:** **Campaigns** (`/dashboard/campaigns`). See [Run an outbound calling campaign](/guides/outbound-campaigns). **For one-off programmatic calls:** the [outbound calls API](/guides/place-outbound-calls). --- ## Live monitoring **Live** shows every call in progress across the org, and lets you open any of them to [listen in and whisper](#calls) in real time. It's the supervision surface: watch a new prompt take its first real traffic, or keep an eye on a running campaign. **In the dashboard:** **Live** (`/dashboard/live`). See [Watch and supervise live calls](/guides/monitor-live-calls). --- ## Simulations A **simulation** is an AI caller having a real conversation with your agent — same telephony path, real transcript, real grading — so you can test before (and after) you ship. Point it at an agent or a phone number, write the caller scenario yourself or **generate scenarios with AI** from the agent's prompt (edge cases included, if you ask), and watch the call live. Scenarios group into **suites** that pin a minimum pass rate and can gate releases in CI; regressions against the accepted baseline are reported per scenario. **In the dashboard:** **Simulations** (`/dashboard/simulations`), plus the **Simulation** button inside the agent builder. See [Simulate a call](/guides/simulate-a-call). **In the API:** [`/v1/test-calls`](/api-reference/test-calls) and the suite runner — see [Test an agent end-to-end](/guides/test-agents). --- ## Validation sets A **validation set** turns real call moments into repeatable, single-turn regression checks. Each example freezes conversation context, relevant caller audio, the original response, and the expected behavior. Replays run against the current agent draft without placing another call, and the deploy dialog can show whether the latest run still matches that draft. **In the dashboard:** **Validation sets** (`/dashboard/validation`) for the organization dataset and the agent builder's **Validation** tab for runs. See [Validation sets](/guides/validation-sets). **In the API:** [`/v1/validation-sets`](/api-reference/validation-sets) and the agent/example replay endpoints on the same reference page. --- ## Experiments An **experiment** A/B tests agent configurations on live traffic: define variants (different prompts, engines, or settings), split traffic between them, and compare outcomes per variant. Use it instead of hand-rolling bucket logic in a webhook. **In the dashboard:** **Experiments** (`/dashboard/experiments`) and the **A/B** tab in the agent builder. See [Experiments (A/B testing)](/guides/experiments-ab-testing). --- ## Issues An **issue** is a flagged problem on a specific call — filed by a human reviewer or detected by AI grading. Issues carry severity, source, and status, and the Issues page is the triage queue: filter, inspect the offending call, and track fixes. **In the dashboard:** **Issues** (`/dashboard/issues`), plus per-call flagging in Call History. See [Issues triage](/guides/issues). **In the API:** [`/v1/issue-reports`](/api-reference/issue-reports). --- ## Reports A **report** answers a natural-language question about your call data ("What were the top three reasons callers asked for a human last week?") with an AI-written analysis, scoped to the agents and date range you choose. **In the dashboard:** **Reports** (`/dashboard/reports`). See [Reports](/guides/reports). --- ## Observability **Observability** is the metrics surface: call volume, outcomes, and quality over time, filterable by agent and time window, with export for downstream analysis. **In the dashboard:** **Observability** (`/dashboard/observability`). See [Observability](/guides/observability). --- ## Alerts An **alert rule** watches a metric (success rate, failure rate, average score, call volume, suite regressions) over a time window and fires when it crosses your threshold. Notifications go to email and Slack, and fire an `alert.triggered` event to your [webhook endpoints](/webhooks/endpoints). **In the dashboard:** **Organization → Alerts**. See [Alerts](/guides/alerts). --- ## Webhooks ThunderPhone sends **HTTP POST webhooks** to your server when things happen during and after a call. Two delivery models: - **Webhook endpoints** (recommended): manage many URLs at [`/v1/developer/webhook-endpoints`](/webhooks/endpoints) with per-endpoint secrets and per-endpoint event subscriptions. - **Legacy single-URL webhook**: one URL per org. Managed at [`/v1/webhook`](/api-reference/organizations#legacy-single-url-webhook) or under **Organization → General**. Kept for backward compatibility. Events split into two classes: - **Blocking events** expect your server to respond with configuration that shapes the in-flight call — the [incoming-call events](/webhooks/call-incoming) (`telephony.incoming` / `web.incoming`). A phone call waits up to 13 seconds, one retry included, and a widget session 10 seconds. There is no fallback agent: without a usable configuration the phone call is ended and the widget session fails. - **Non-blocking events** are fire-and-forget notifications, retried with exponential backoff — see [delivery semantics](/webhooks/overview). Every request carries a timestamped HMAC-SHA256 signature in `X-ThunderPhone-Signature-V2`, plus the legacy body-only `X-ThunderPhone-Signature`. See [Signature verification](/webhooks/overview#signature-verification). --- ## Function tools A **function tool** is an HTTP endpoint your agent can call mid-conversation. You give ThunderPhone an OpenAI-style function schema plus an endpoint URL; the agent decides when to call it, and ThunderPhone makes the signed HTTP request from its servers and hands the result back to the agent. Agents also ship **built-in call abilities** — transferring the call, sending keypad (DTMF) input, ending the call, waiting on hold — that you enable with plain prompt lines rather than tool definitions. **In the dashboard:** the builder's **API connections** section (see [Connections](#connections)). **In the API:** [`/v1/integrations`](/api-reference/integrations) and the [Function Tools spec](/tools/overview). --- ## Team and roles Each organization has a members list with two roles: **Members** build and operate agents; **Admins** also manage the team and billing. Invite by email — invitations expire after 7 days and can be revoked; the ⋯ menu on a member row changes roles or removes someone. Single sign-on can be configured org-wide — see [SSO](/guides/sso). **In the dashboard:** **Organization → General**. See [Invite your team](/guides/invite-your-team). **In the API:** [`/v1/members`](/api-reference/members), [`/v1/invites`](/api-reference/invites). --- ## Billing ThunderPhone is **prepaid**. Each org carries a USD balance; each call debits what its parts cost at our providers' list prices, plus an infrastructure charge and a markup (see [pricing](/guides/pricing)). When the balance hits zero, inbound calls are rejected and outbound calls return `402 Payment Required`. Top up manually, or enable **auto top-up** with a balance threshold, a refill amount, and an optional monthly spending limit — so a call never dies mid-sentence. **In the dashboard:** **Organization → Billing Settings** and **Billing History**. See [Add funds and turn on auto top-up](/guides/billing-and-topups), plus the [complete pricing reference](/guides/pricing). **In the API:** [`/v1/billing`](/api-reference/billing). --- ## The in-app copilot The dashboard ships with a built-in **copilot** — ask it "how do I X" and it answers from these docs, offers click-by-click walkthroughs that spotlight the real controls, and can replay any of the guided tours. It's the fastest way to find a control this page mentions. See [Ask the in-app copilot](/guides/ask-the-copilot). --- ## Putting it together The five-step wizard: agent → billing → number → simulation → review. The same first call in four REST calls. Build an agent, fund it, get a number, simulate, and review calls. OAuth apps, custom APIs, MCP servers, and VoIP providers. Reports, observability, experiments, issues, and alerts. Invites and roles, API keys, security, and SSO. The API recipes: inbound, outbound, dynamic config, tools, testing. Get the HMAC check right once and reuse everywhere. --- title: "Ask the copilot" description: "The in-app assistant answers product questions, shows you where things are, and can drive the dashboard for you — by text or voice." --- Every dashboard page has an **Ask Copilot** button in the corner. It opens a chat panel that knows ThunderPhone and your workspace — the fastest way to answer "how do I…?" without leaving what you're doing. ## What it can do **Answer questions.** Ask about anything — features, pricing tiers, "why did this call fail", "where do I add a teammate". The input placeholder says it plainly: *Ask Copilot about your workspace…* **Show you, not just tell you.** When the answer is a place in the app, the copilot can take you there and point: it navigates to the right page, highlights the exact control, and offers step-by-step walkthroughs — look for the **Show me** style offers after an answer. It can also start any of the guided tours you see in the sidebar's **Guided tours** menu. **Make requested changes.** Ask for a specific change — "switch the support agent to Spanish", "enable silence check-ins", or "point this number at the sales agent" — and the copilot reads the current state, makes the change, and reports exactly what it changed. For admins, simple edits go live right away and can be undone; a fix that changes how the agent behaves is saved as a draft, checked there, and published once you say yes. A member's edit is saved as a draft for an admin to publish. If the agent already has someone else's dashboard draft or the request would rewrite most of a prompt, the copilot pauses for confirmation instead of overwriting it. **Propose its own recommendations.** When the copilot discovers an improvement you did not explicitly request, it uses a **proposal card** with the exact before-and-after change. Nothing in a proposal is applied until you select **Apply**; you can also dismiss it. **Talk instead of type.** Two voice options sit next to the message box when voice is available: - **Dictate** — speak once; your words land in the message box for review before sending. - **Voice conversation** — hands-free back-and-forth: it listens, answers out loud, and keeps listening. End it with the hang-up button. A speaker toggle at the top of the panel also lets you have any reply read aloud. **Hand you to a human.** Flip the panel's toggle to **Talk to a human** to reach the ThunderPhone team in the same window. When the copilot loops the team in for you, a banner appears above the message box and the **Human** tab lights up to show where the support chat lives; your messages keep going to the copilot until you open it. Support chat supports image attachments and can escalate to a **live voice call** inside the panel; if the support engineer needs to see your screen, you'll get a one-click prompt to share the current tab (with fullscreen viewing) — sharing is always your choice. ## Workspace analysis and actions The copilot can work with the current organization rather than answering only from generic documentation. | Capability | What you can ask for | | --- | --- | | **Organization summary** | List agents and phone numbers, inspect onboarding state, or report the current prepaid balance. | | **Call analysis** | Filter recent calls, read transcripts and detected insights, calculate call and agent metrics, and cite concrete call IDs in its findings. | | **Agent creation and configuration** | Create an agent or change its name, prompt, tier, voice, languages, call behavior, and attached API, app, MCP, or knowledge resources. | | **Phone-number setup** | Get a ThunderPhone number or assign an existing number to an inbound or outbound agent. | | **Testing** | Create saved scenarios, run selected scenarios or a suite, and retrieve results. Charged test runs require an explicit request and show their cost estimate. | | **Dashboard guidance** | Open the relevant page, spotlight a control, or offer a verified guided walkthrough. | The copilot verifies a write before saying it succeeded. It never enters payment-card details, releases resources, or deletes data. It initiates a billable test run only when you explicitly request the run and provide the required charge consent. ## Good things to ask - "How do I connect HubSpot?" - "Walk me through creating an API key." - "Why is my agent's success rate down this week?" - "Set up an alert for failure rate above 10%." - "What does whisper do on a live call?" The copilot works on real organizations — it's not available while browsing in demo mode. Guided tours remain available anytime from the sidebar's **Guided tours** button. --- title: "Supported languages" description: "Every language ThunderPhone agents can speak, which voices support each one, and which languages are served by premium voices." --- {/* GENERATED FILE — do not edit by hand. Source: api/thunderphone_api/data/voice_catalog.json Regenerate: python3 scripts/docs-freshness/generate_supported_languages.py CI regenerates this page and fails on drift (scripts/docs-freshness/generators.d/10-supported-languages.sh). */} ThunderPhone agents speak **47 languages** with **127 voices**. A call starts in the agent's primary language and switches automatically when a caller uses one of the agent's additional languages. Set both in the agent builder's **Config** tab, or via the `primary_language` and `additional_languages` fields on the [Agent](/api-reference/agents). ## Keep one voice or switch to native voices By default, the agent keeps its configured voice when it switches languages. Open **Voice Agents**, select the agent, and in **Config** add at least one language under **Additional languages**. Turn on **Switch to native voices**, test the draft in **Talk**, and select **Deploy**. The switch uses a catalog voice native to the detected additional language, matched to the configured voice's gender. The agent returns to its configured voice for the primary language and keeps the current voice if no match is available. Custom voices do not switch automatically. The switch itself has no charge. API clients can set `native_voice_switching: true` through the [Agents API](/api-reference/agents). There is no language surcharge. The voice is one part of a call's [price](/guides/pricing), billed at its provider's list price plus the markup: - **Standard** languages (15) are served by our default voices. - **Premium** languages (32) are served by premium voices, whose list prices are higher, so a minute costs a little more. Accounts still on their earlier pricing (until December 1, 2026) pay +5¢/min for a premium language. Voice availability differs by language — the table shows how many voices speak each one. Filter live via [`GET /v1/voices?languages=`](/api-reference/agents#voices); the dashboard's voice picker applies the same filter as you select languages. | Language | Code | Voices | Voice tier | |----------|------|--------|---------| | Arabic | `ar` | 8 | Standard | | Bengali | `bn` | 8 | Premium | | Bulgarian | `bg` | 8 | Premium | | Cantonese | `yue` | 6 | Premium | | Chinese | `zh` | 8 | Standard | | Croatian | `hr` | 8 | Premium | | Czech | `cs` | 8 | Premium | | Danish | `da` | 8 | Premium | | Dutch | `nl` | 8 | Standard | | English | `en` | 50 | Standard | | Estonian | `et` | 6 | Premium | | Finnish | `fi` | 8 | Premium | | French | `fr` | 8 | Standard | | German | `de` | 8 | Standard | | Greek | `el` | 8 | Premium | | Gujarati | `gu` | 8 | Premium | | Hebrew | `he` | 8 | Standard | | Hindi | `hi` | 8 | Standard | | Hungarian | `hu` | 8 | Premium | | Indonesian | `id` | 8 | Premium | | Italian | `it` | 8 | Standard | | Japanese | `ja` | 8 | Standard | | Kannada | `kn` | 8 | Premium | | Korean | `ko` | 8 | Standard | | Latvian | `lv` | 6 | Premium | | Lithuanian | `lt` | 6 | Premium | | Malayalam | `ml` | 8 | Premium | | Marathi | `mr` | 8 | Premium | | Norwegian | `nb` | 8 | Premium | | Polish | `pl` | 8 | Standard | | Portuguese | `pt` | 8 | Standard | | Punjabi | `pa` | 8 | Premium | | Romanian | `ro` | 8 | Premium | | Russian | `ru` | 8 | Standard | | Serbian | `sr` | 6 | Premium | | Slovak | `sk` | 8 | Premium | | Slovenian | `sl` | 6 | Premium | | Spanish | `es` | 12 | Standard | | Swahili | `sw` | 6 | Premium | | Swedish | `sv` | 8 | Premium | | Tamil | `ta` | 8 | Premium | | Telugu | `te` | 8 | Premium | | Thai | `th` | 8 | Premium | | Turkish | `tr` | 8 | Premium | | Ukrainian | `uk` | 7 | Premium | | Urdu | `ur` | 8 | Premium | | Vietnamese | `vi` | 8 | Premium | --- title: "ThunderPhone pricing and usage charges" description: "How ThunderPhone prices calls: each call's parts at our providers' list prices, an infrastructure charge and a markup. Typical prices, price targets and caps, tests, phone numbers and balances." --- Every new account includes **$10 of free call credit** in its first organization, with no credit card required. Organizations you add later start without it. The credit pays for any call: browser tests, phone calls, SIP calls, web and widget calls, and simulations. Calls use the credit first, then your cash balance. It cannot pay for phone numbers or voice samples, so add funds with a card when you want a number. ThunderPhone is prepaid and pay-as-you-go: no subscription and no seat fee. Calls draw from the selected organization's balance. ## How a call is priced Each call costs what its parts cost at our providers' list prices, plus infrastructure (**15% of the other costs, at least 0.5¢ and at most 2¢ a minute**), plus a markup. The parts: | Part | What it covers | Priced at | | --- | --- | --- | | **Understanding** | The AI models that decide what your agent says and does, including tool calls and the context they read. | Provider list price | | **Hearing the caller** | Speech-to-text. | Provider list price | | **The voice** | Text-to-speech. Premium voices from Cartesia and ElevenLabs cost more per minute than the default Inworld voices. | Provider list price | | **The phone line** | Carrier minutes on our lines, including transfers and hold. | Carrier list rate | | **Call review** | Grading, the summary and data extraction after the call. Optional per agent and on by default; turning it off removes its cost, along with call summaries, outcomes, flags and automatic improvement from real calls. | Provider list price | | **Infrastructure** | Call servers, LiveKit, phone gateways and recording. | 15% of the other costs, 0.5¢ to 2¢ a minute | ### How the price is calculated 1. **Add up the parts.** Each part is priced at our provider's list price. 2. **Add infrastructure.** 15% of those costs, at least 0.5¢ and at most 2¢ a minute. 3. **Add the markup.** The markup is **20%** when a call costs 1¢ a minute or less, **33%** when it costs 15¢ a minute or more, and rises in a straight line in between: 26.5% at 8¢ a minute. The total is rounded up to the cent. Receipts and Zeus show each call's price by part, with the markup spread across the parts. Prices follow our providers' list rates. When a model or voice's list price drops, your calls cost less, with no action from you. Bigger prompts and large tool results cost more because the models read more on every reply. There is no language surcharge. Languages served by premium voices cost a little more per minute, because those voices' list prices are higher. If you use your own Cartesia key, Cartesia bills you for the voice and ThunderPhone charges nothing for it. On your own carrier, VoIP provider or SIP trunk, your carrier bills its own minutes and there is no phone-line part. ## Typical prices What a minute typically costs for three common setups at today's list prices, markup included, worked out from measured calls. | Setup | Typical price | | --- | ---: | | **Leanest.** Lite intelligence, Lean hearing and Patient reply speed with a standard voice, on your own carrier, with call review off. For simple, structured calls. | **about 2¢/min** | | **Receptionist.** An inbound front desk on our default settings: Standard intelligence, Standard hearing, an Inworld voice and a ThunderPhone number. | **about 9¢/min** | | **Premium voice.** The same receptionist with a Cartesia or ElevenLabs voice. | **about 11¢/min** | Your price depends on your setup. Zeus shows yours before you go live. ## Price targets, hard caps and estimates Zeus, ThunderPhone's agent builder, sets these for each agent: - **An estimate before you go live.** Zeus runs simulated calls and shows what a minute of your agent costs, then updates it after the first real calls. - **A price target.** Zeus keeps the agent's settings under it and tells you when a change would price above it. - **A hard cap per call.** A call that reaches the cap ends with a goodbye. - **Any call's cost by part.** Ask Zeus why a call cost what it did and it breaks the price down by part and names the cause, like a large tool result or a long call. ## Calls that don't connect or fail A call that fails to connect because of ThunderPhone is never charged. A call that uses paid services but connects for no billable minutes, like an unanswered outbound attempt, is charged what it cost plus the markup. ## Hold time, phone menus and transfers Hold time, automated phone menus and call screeners are billed like any other minute, at what their parts cost plus the markup. When your agent hands a call off with a cold transfer, your carrier takes the call over and ThunderPhone's charges stop when the agent leaves. When ThunderPhone keeps the transferred call connected itself, that connected time bills what it costs, the phone legs plus infrastructure, plus the markup: about {/* rate:cost-plus-bridge-number */}1.6¢/min{/* /rate */} on a ThunderPhone number ({/* rate:cost-plus-bridge-number-outbound */}1.8¢/min{/* /rate */} on an outbound call) and {/* rate:cost-plus-bridge-own-carrier */}0.6¢/min{/* /rate */} on your own carrier. This covers a warm transfer after the agent drops off, and a cold transfer your carrier refused. Accounts still on their earlier pricing pay {/* rate:transfer-bridge */}1¢/min{/* /rate */} plus the phone line until December 1, 2026. See [Call transfers](/guides/call-transfers). ## Simulations and testing Test and simulated calls are billed the same way as real calls. An AI simulation also runs a simulated caller, counted in the call's cost at {/* rate:cost-plus-simulated-caller */}3¢/min{/* /rate */}, so infrastructure and the markup apply to it as to any other part. There is no separate caller fee or multiplier. Accounts still on their earlier pricing pay a flat {/* rate:simulated-caller */}5¢/min{/* /rate */} for the simulated caller until December 1, 2026. The launch dialog shows an estimate and requires explicit approval. Saved scenario suites and validation runs display their own estimate before or during execution. ## Phone numbers {/* rate:phone-numbers */} | Number type | ThunderPhone charge | | --- | --- | | **ThunderPhone number** | **$1/month**. Its call minutes are part of the phone line. An organization can hold 25 by default; request a higher limit from the Phone numbers page. | | **Imported VoIP number** | No ThunderPhone import or monthly-number charge. Your VoIP provider continues to bill its own number and telephony usage. | {/* /rate */} Provisioning or releasing a ThunderPhone number starts or stops its monthly billing. Deleting an imported number disconnects it from ThunderPhone but does not cancel it with the provider. ## Existing customers Accounts created before this pricing launched keep their current prices until **December 1, 2026**. Then the new pricing applies automatically. You can switch earlier from **Billing**; the switch is one way. ## Contracts and enterprise Customers on a contract keep the terms of their contract. For a contract or volume pricing, [talk to us](mailto:sales@thunderphone.com). ## One-time charges - **Generated voice sample:** **3¢** for a new voice/text/language sample; identical cached samples are free. - Other billed operations present an estimate or confirmation before charging the organization balance. ## Balance, top-ups, and spending controls Add a payment method and at least **$3.00** to begin making real calls. Funds do not expire. Manual top-ups add a chosen amount immediately. Optional auto top-up has three controls: - Balance threshold that triggers the refill. - Refill amount, at least $1. - Optional monthly spending limit. When reached, new calls and usage pause until the next month or a higher limit; alerts are sent at 80% and 100%. The monthly limit remains effective even if auto top-up is later disabled. See [Billing and top-ups](/guides/billing-and-topups) and the [Billing API](/api-reference/billing) for account and transaction operations. --- title: "Dashboard home: calls, success, issues, and live activity" description: "Use the ThunderPhone dashboard home to track call volume, success rate, duration, issues, and live or queued calls by agent or phone number." --- The dashboard home (`/dashboard`) is the operational summary for the organization you currently have selected. It combines recent call volume, quality, open issues, and calls happening now, with links into the detailed workspaces behind each number. New organizations see the five-step setup checklist first. Complete or skip it to reach the operational dashboard. You can replay its walkthroughs from **Guided tours** in the sidebar; see the [dashboard quickstart](/quickstart-dashboard). ## Choose the time window The call-volume chart supports **7**, **14**, and **30 day** windows. Changing the window updates the chart and the summary table below it. Use **Group by** to switch between: - **Agents** — one series and one summary row per voice agent. - **Numbers** — one series and one summary row per phone number. Number labels appear in place of raw numbers when you have added them. The chart shows calls over time. Hover a point to inspect the date and the series contributing calls on that day. ## Read the summary table Each agent or phone-number row reports: | Column | Meaning | | --- | --- | | **Calls** | Calls in the selected time window. | | **Success** | Percentage of calls with a gradeable successful outcome. Calls without a final outcome do not inflate the percentage. | | **Duration** | Average completed-call duration in minutes. | | **Issues** | Open issue reports associated with the agent or number. | Select a row to open **Call History** with the corresponding agent, number, and date window already applied. For a number that handles both directions, the link uses its predominant call direction for the filter. ## Needs attention **Needs attention** groups unresolved issue reports by title and shows the number of affected calls. Select a row to open that issue pattern on the [Issues page](/guides/issues), or choose **All issues** for the complete triage queue. When there are no open issues, the panel reports that operational checks are clear. ## Live and queued calls **Live & queued calls** shows up to four calls that are in progress or waiting to start, including: - Inbound, outbound, or web-call direction. - The caller or callee, using your saved phone-number label when available. - The assigned agent. - A **Live** or **Queued** status. If more than four calls are active, the final row reports the additional count. Open **Call history** to see the complete list, or use [Live monitoring](/guides/monitor-live-calls) to listen and coach an in-progress call. ## Organization switching Every number on this page belongs to the organization selected in the sidebar. Switching organizations changes the agents, numbers, calls, issues, and billing context together. See [Organization settings](/guides/organization-settings) for the settings that apply at that boundary. --- title: "Build your first voice agent" description: "The agent builder end-to-end: write the prompt, pick an engine, voice, and languages, attach tools and knowledge, talk to your draft, and deploy it to production." --- Everything about an agent lives in the builder at **Voice Agents** (`/dashboard/agents`) — create one and you land in it. This guide walks the same path as the in-app "Build an agent" tour. First time here? The [dashboard quickstart](/quickstart-dashboard) wraps this page plus billing, numbers, and simulations into the five-step setup wizard. Already have agents on Vapi, Retell, ElevenLabs Agents, or Bland? [Import them](/guides/import-agents) instead of rebuilding. ## Start from a template **Create agent → Start from a template** opens a picker of ready-made agents. Everything is editable afterwards. - **Interpreter** — a real-time two-way phone interpreter between two languages. Pick your language and the caller's language, a voice that speaks both, and confirm the intro the interpreter says at the start of every call (spoken in the non-English language; in both when neither is English). The agent opens fully configured on Storm at the standard rate; give the number to your team and add the other party with 3-way calling. - **Receptionist** — the sample front-desk prompt from the onboarding tour: greets callers, books appointments, answers questions, transfers the rest. - **Appointment scheduler** — takes booking, rescheduling and cancellation requests: collects the caller's name, number, and preferred day and time, reads the request back, and tells the caller the office will confirm. There is no calendar integration, so it never claims a booking is confirmed; give it a transfer number for anything it can't handle. - **After-hours message taker** — greets, takes the caller's name, callback number and reason, reads them back, and ends the call. Give it an urgent-calls number and it transfers callers who say it's urgent; give it an email and it sends you a summary after each call. - **Call router** — a phone-menu replacement. Asks in one sentence what the caller needs, transfers to the matching department (up to three, each with a name and number), and takes a message when nothing fits. - **Lead qualifier** — screens calls from your ads: confirms what the caller wants, asks three qualifying questions (what, when, and a rough budget or size), reads the answers back, then transfers to your sales number or promises a callback. - **Info line** — answers hours, location, directions, pricing and policy questions from the [knowledge](/guides/knowledge-base) you attach, says so when it doesn't know, and never invents facts. After creating it, add your website or documents under **Knowledge**. - **Phone intake form** — walks the caller through a fixed list of fields (name, phone, email, address, reason), confirms each, reads the whole form back, and emails it to you. The six business templates share one setup step — your business name, a one-line "what you do", and optionally a transfer number and an email for messages — and write a prompt in the [prompting guide's](/guides/prompting) shape. Transfers are plain prompt lines ("transfer the call to …"), the email goes through the agent's send-email setting, and the agent is named after your business. **Start from scratch** in the same menu opens an empty editor instead. ## 1. Write the prompt The prompt is the script for your voice agent. Describe — in any language — who the agent is and how it should handle the call: its persona, what it knows, what it should and shouldn't do. Transfers, keypad presses, hold, email, sounds, and hang-ups are prompted actions. See [Built-in tools](/guides/built-in-tools), and use [Transfer calls](/guides/call-transfers) for cold and screened warm handoffs. If the call has phases that must happen in order, such as verifying a caller before booking, split the agent into [steps](/guides/agent-steps) instead. Specific beats general. "Confirm the order number back to the caller before making changes" outperforms "be careful with orders." ## 2. Pick an engine On the **Config** tab, the engine picker trades off cost and performance across three tiers — **Spark** is optimized for cost, **Bolt** for speed, **Storm** for intelligence on complex prompts. Each card shows its per-minute price. Switch anytime; see the [full pricing reference](/guides/pricing) and [agent setting reference](/guides/agent-settings). ## 3. Choose a voice and languages Pick a voice and play its sample right in the builder. Set the **primary language** the call starts in, plus any **additional languages** — the agent switches automatically when a caller uses one. The voice list filters to voices that support every language you've selected. See [Supported languages](/guides/supported-languages) for the full catalog. ## 4. Attach tools and knowledge Four sections on the Config tab give the agent capabilities: - **Connected apps** — the easiest path: [connect Slack, HubSpot, Google & more](/guides/connect-apps) in a few clicks, then let your agent use them during calls. - **API connections** — for any other API, [create an API connection](/guides/api-connections) for the actions your agent needs (paste a cURL command and the AI wizard drafts the tool). - **Knowledge** — attach a [knowledge base](/guides/knowledge-base) the agent can search mid-call. - **MCP servers** — [add an MCP server](/guides/mcp-servers) by URL. See [Connections](/guides/concepts#connections) for how the four kinds differ. Advanced options below the endpoint sections control supervision, connection tone, tool sounds, who speaks first, voicemail, the call-start consent announcement, and silence check-ins. See [Every agent setting](/guides/agent-settings#advanced-call-behavior). ## 5. Check your price Each call costs what its parts cost at our providers' list prices, plus a infrastructure charge and a markup. Zeus shows what a minute of your agent costs before you go live, and you can set a price target and a hard cap per call. See [pricing](/guides/pricing). ## 6. Talk to it Click **Talk** and say hello — your agent answers right in the browser (it'll ask for mic access). This is the fastest iteration loop: tweak the prompt, talk, repeat. When you want a caller on the other end instead of yourself, [run a simulation](/guides/simulate-a-call). ## 7. Deploy Every edit **autosaves to your draft** — watch for "Saved" in the top bar. Draft changes aren't live until you click **Deploy**. Every deploy is snapshotted in the **History** tab: inspect what changed between versions and restore any previous one. That makes deploys safe to do often — you can always roll back. ## 8. Connect it to the world The Config tab's **Phone numbers** and **Web widgets** sections attach endpoints without leaving the builder — attach an existing number, get a new ThunderPhone number, import via VoIP, or link a web widget. --- ## Next steps ThunderPhone number in seconds, or bring your own via VoIP. An AI caller has a real conversation with your agent. Transcripts, recordings, live listen-in, and whisper. Create and manage agents programmatically. Complete options, ranges, behavior, and pricing. --- title: "Import agents from another platform" description: "Bring agents, tools, and knowledge bases over from Vapi, Retell, ElevenLabs Agents, or Bland with an API key or a JSON export, review the mapping, and import." --- If your agents live on Vapi, Retell, ElevenLabs Agents, or Bland, the importer fetches them, maps every field onto ThunderPhone, and shows you the result before anything is created. Your API key is used only for the import. It is encrypted while the inventory is fetched and deleted as soon as the fetch completes — and on any failure or discard. Admin role is required to start or commit an import. ## Start an import 1. Open **Agents** and choose **Import** (or **Import from Vapi, Retell, ElevenLabs, or Bland** on an empty agents page). 2. Pick the platform. 3. Follow the numbered steps in the dialog to create a key on that platform (they open the right settings page for you), paste it, or switch to **Paste exported JSON** and paste one or more agent objects from that platform's dashboard or API. 4. Choose **Fetch my agents**. Fetching and mapping usually takes under a minute; you can leave the page and come back through the task tray. ## Review the plan Each agent shows the name, matched voice (with how confident the match is), intelligence tier, the tools and knowledge bases it brings, and notes about anything that changed in translation. Expand a row to read the prompt: - **Rewritten** — one coherent prompt in the style of the [prompting guide](/guides/prompting), with every phone number, URL, tool name, and placeholder from the source preserved. - **Structured** — the source config laid out under headings, exactly as extracted. Used automatically when the rewrite cannot be verified, or when the rewrite came out much shorter than the source. An agent imported from a flow shows **Shared instructions** instead of the prompt, followed by its steps: each step's name, what it tells the agent to do, and where it goes next. **Show as one prompt** shows the same agent as a single prompt, which is what it runs wherever steps are not available. Agents with handoffs list the agents they hand the caller to; if you untick one of those, the review says that handoff will not be set up. Every agent starts selected except empty ones and default starter agents the platform created for you. Untick agents you do not want, edit names, voices, tiers, or the prompt choice, then **Import**. Created agents open in the builder with their API connections and knowledge attached. ## Flows, Squads and agent transfers Multi-step agents keep their structure. See [Agent steps](/guides/agent-steps) for how steps and handoffs run on a call. - **Flows become steps.** Retell multi-prompt states and conversation flows, Bland pathways and ElevenLabs workflows import as the agent's steps. The source's global prompt becomes the shared instructions. - Each node's instructions, fixed line, values to collect and tools carry over. - Edge conditions written in words become transitions the agent decides. A single comparison on a collected value or a webhook response (`age >= 18`) becomes a transition that is checked exactly. - Webhook and function nodes run their tool as the step starts. Global nodes can be reached from any step; one with no ways forward of its own hands the call back to the step it interrupted. - Transfer and end nodes become last steps that transfer or end the call. A Retell transfer node keeps its way out for when the transfer does not go through. - A branch node that only picks the next step is merged into the step before it when it can be. Otherwise it stays a step of its own, with a note. - A flow that is too large for steps, or that ThunderPhone cannot represent as steps, is imported as one prompt that lists every step, with a note. - **Squads, agent transfers and agent swaps become handoffs.** Each member of a Vapi Squad is imported as its own agent, named after the member and the Squad. The first member answers the Squad's calls and the others are reached by handoff. ElevenLabs transfers to another agent and Retell agent swaps become handoffs too. The caller stays on the line and the conversation so far carries over. - A call reached by handoff keeps the call settings of the agent that answered it: voicemail, call limits, languages and recording. The agent it hands off to brings its own prompt, steps, tools and voice. - A handoff can only point at an agent imported in the same run. If the other agent is not in the import, or you untick it, the note says so and you can add the handoff later in the agent's settings. ## What is imported **Getting a key** 1. Open [dashboard.vapi.ai/org/api-keys](https://dashboard.vapi.ai/org/api-keys) (Organization settings → API Keys). 2. Create a key and choose **Private** — public keys cannot list assistants. 3. Copy it into the dialog. Vapi keys are not scoped; delete the key in Vapi after the import if you created it only for this. | Vapi field | ThunderPhone | |---|---| | `model.messages[system]` | Prompt | | `firstMessage`, `firstMessageMode` | Opening line in the prompt; who speaks first | | `voice.provider` + `voiceId` | Closest catalog voice, with best-effort Vapi library lookup (match confidence shown) | | `transcriber.language`, `languages`, keywords/keyterms | Languages and speech-recognition vocabulary | | `model.model` | Intelligence tier | | `model.tools`, `toolIds`, `toolRefs` (function, apiRequest) | API connections; Vapi request envelopes and assistant-level server fallbacks are preserved | | `transferCall` destinations | Phone transfers in the prompt; SIP, assistant, and dynamic destinations are reported for review | | `handoff` tools | Handoffs to the named assistants imported in the same run | | `endCall`, `dtmf` | Built-ins | | Google Calendar, Google Sheets, Slack, `sms` | Reported with the ThunderPhone integration or SMS setup needed | | `query` and `knowledgeBase` tools, plus legacy `knowledgeBaseId` | Knowledge base with accessible uploaded files as documents | | `endCallMessage`, `endCallPhrases`, `voicemailMessage`, idle-message plans and timeout hooks | Prompt sections; voicemail and silence settings | | `maxDurationSeconds`, recording opt-outs | Maximum call length (Vapi's 600-second default is kept); a recording opt-out is reported on the review page | | Squads | One agent per member, with its own prompt, overrides, voice, tools, and knowledge. `assistantDestinations` become handoffs between them; the first member keeps the Squad's numbers | | Custom knowledge/model servers, compliance plans, other hooks, workflows and unsupported hosted tools | Reported with a specific review or rebuild action; raw plans and credentials are not stored | **Getting a key** 1. Open [dashboard.retellai.com/apiKeys](https://dashboard.retellai.com/apiKeys) (System settings → API Keys) and choose **Create key**. 2. Turn on **Restrict permissions** and set **Agent**, **Knowledge base**, and **Phone** to **Read**. Leave every other group unchecked; the importer never writes to Retell. 3. Copy the key into the dialog. | Retell field | ThunderPhone | |---|---| | Latest published agent + its exact Retell LLM / flow version | Published prompt and behavior; newer drafts are reported for review | | Retell LLM `general_prompt`, `begin_message`, `start_speaker`, states | Shared instructions; opening behavior; one step per state, with its tools and transitions and the values each transition collected | | Conversation flow `global_prompt`, nodes, components, global nodes, and edges | Shared instructions and steps; equation conditions on collected values are checked exactly; exact opening text and transfer steps | | `agent_swap` tools and nodes | Handoffs to the Retell agents imported in the same run | | Custom tools | API connections with query parameters, timeout, and Retell/args-only request shape; a signature change and any non-default speech, retry, response-variable, or form-encoding setting are reported | | `transfer_call`, `end_call`, `press_digit` | Conditional transfers (including extensions) + built-ins; inferred and warm-transfer differences are reported | | `check_availability_cal`, `book_appointment_cal` | Reported as Cal.com tools to reconnect under Integrations; Retell credentials never carry over | | `send_sms`, MCP | Reported with SMS or MCP setup instructions | | `voice_id` (`11labs-`, `openai-`, `cartesia-`, …) | Closest catalog voice | | `language` (one locale, multilingual mode, or a locale list) | Primary and additional languages | | `model` / `s2s_model` | Intelligence tier | | `knowledge_base_ids` (url, document, text sources) | Knowledge base with documents | | `default_dynamic_variables`, supported system variables | ThunderPhone variable defaults and plain call-context instructions | | `voicemail_option`, `end_call_after_silence_ms`, `max_call_duration_ms`, `ring_duration_ms`, recording disabled, `boosted_keywords` | Voicemail behavior; call limits; recording setting; prompt terms | | `webhook_url`, custom-LLM engines, unsupported flow nodes and vendor-only settings | Reported for manual review, not silently dropped | **Getting a key** 1. Open [elevenlabs.io/app/settings/api-keys](https://elevenlabs.io/app/settings/api-keys) and choose **Create API key**. 2. Under permissions, give it **Agents Platform: Read** (and **Knowledge base: Read** if shown); the importer needs nothing else. 3. Copy the key into the dialog. | ElevenLabs field | ThunderPhone | |---|---| | `conversation_config.agent.prompt.prompt`, Agent Workflow nodes and ordered branch conditions | Shared instructions and one step per workflow node; tool nodes run their tool as the step starts; expression branches are labelled for review | | `first_message`, `language`, `language_presets` | Opening line; primary and additional languages; translated openings | | `prompt.llm` | Intelligence tier | | `tts.voice_id` | Closest catalog voice | | webhook `tool_ids`, including tools attached to workflow steps | API connections with portable body/query parameters; fixed vendor parameters and credentials are removed and reported | | `built_in_tools` for end call, phone transfer, keypad, and voicemail | Built-ins, transfer rules, and voicemail behavior; SIP and dynamic destinations are reported for rebuilding | | Transfers to another agent (`transfer_to_agent`, agent workflow nodes) | Handoffs to the ElevenLabs agents imported in the same run | | `knowledge_base` url/text/file/folder entries, including workflow-step knowledge | Knowledge base documents; folders are expanded and text/file content is fetched by id | | `turn.silence_end_call_timeout`, `conversation.max_duration_seconds`, `asr.keywords`, `privacy.record_voice` | Silence and maximum-duration limits; pronunciation terms; recording turned off is reported on the review page | | custom LLMs, MCP servers, procedures, workspace webhooks, per-step model/voice/language overrides, ElevenLabs booking tools | Reported with the action needed after import | **Getting a key** 1. Open [app.bland.ai/dashboard/settings](https://app.bland.ai/dashboard/settings). 2. Copy the **API key** shown there (Bland has one key per account; it is not scoped, so rotate it afterwards if you prefer). 3. Paste it into the dialog. Bland display labels are not treated as the agent's identity. The importer derives who the agent says it is from the greeting and prompt; for a pathway it uses the global prompt and start node. If that check is unavailable or finds no name, the imported prompt stays neutral. | Bland field | ThunderPhone | |---|---| | Web agent `prompt`, `first_sentence`, `language`, `model`, `voice`, `pathway_id` | Prompt; opening line; language; tier; closest voice; attached published pathway flow | | Persona production version | Personality and orchestration prompts; voice; language; caller-first setting; recording; maximum duration; default tools and knowledge. A single attached pathway is included; multiple-pathway routing is reported for manual setup | | Custom and pathway tools (url, method, schema, headers, body variables) | API connections. Bland Secret references are reported and must be added again | | Cal.com, Slack, HubSpot, Salesforce, Google Calendar, and Google Sheets tools | Matching ThunderPhone integration is named for you to connect and enable | | Inbound number detail (`prompt`, `pathway_id` / version, tools, transfers, keywords, request data) | One agent per inbound number, with call-variable defaults and transfer rules | | Every pathway, attached to a number or not | Published version when available; an unpublished draft is clearly flagged | | Global prompt; Default, Knowledge Base, Transfer Call, End Call, Webhook, and Wait for Response nodes; edges, loop conditions, global nodes, extracted variables, static speech | Shared instructions and one step per node, including any-time steps, values to collect, exact speech, webhook steps, transfers, and hang-ups. Webhook response pathways on a response field or extracted variable are checked exactly | | Inline knowledge text and resolvable knowledge ids | Knowledge documents. Items whose text or file cannot be read are reported for manual upload | | `max_duration`, `wait_for_greeting`, recording setting | Call limit, speaking order, recording setting | | `webhook`, dynamic-data lookups, multi-path persona routing, unsupported node settings, v2 agents | Reported with the manual follow-up needed | | Inbound numbers | Listed with both options, since Bland does not say whether it or your Twilio account owns them | ## What needs a step on ThunderPhone The report at the bottom of the review page lists numbers, webhooks, and tools that did not move; each agent's notes cover the rest. Together they show everything that did not move and why: - **Phone numbers.** After the import, each source number shows where it lives and what you can do with it: - **In your own Twilio or Telnyx account** (for example a Twilio number you connected to Vapi or ElevenLabs): an admin can move it to an imported agent. If no [VoIP connection](/guides/bring-your-own-numbers) holds that account yet, connect it first. Before anything changes, a confirm step says that calls to the number will stop going to the old platform. ThunderPhone then points the number at itself in your carrier account (a Twilio number joins ThunderPhone's SIP trunk; a Telnyx number moves to ThunderPhone's SIP connection) and places a short test call to switch it on. To undo it, delete the number on **Phone numbers**, keeping it in your carrier account, then re-import it on the old platform. - **On your SIP trunk**: connect the trunk as a [SIP connection](/guides/voip-providers#manual-sip-configuration) and import the number on **Phone numbers**. - **Sold by the platform** (Vapi, Retell and Bland numbers bought there): ThunderPhone cannot move it. Port it to a carrier and import it, or get a ThunderPhone number and attach it to the imported agent. - Bland does not say whether a number is its own or from your Twilio account, so Bland numbers show both options. - **Per-agent webhooks.** ThunderPhone delivers signed webhooks per organization; set one up under [Webhooks](/webhooks/overview). - **Vendor-stored credentials.** Tool headers that were visible are copied. Credentials the old platform stored by id are flagged; add them to the API connection's headers. - **Built-in integrations.** Calendar, CRM, and messaging tools the old platform hosted (for example Cal.com booking) are named with the ThunderPhone integration to connect instead; their credentials do not carry over. The review also flags a prompt that still tells the agent to use a tool that was not imported. - **Text messages.** ThunderPhone agents text through your own carrier connection; set up SMS in the agent's settings. - **Tools without an equivalent** (platform-specific automations) are listed so nothing is dropped silently. - **Call variables.** `{{name}}` placeholders are kept, with the defaults your platform had. ThunderPhone fills them from the [variables passed when a call starts](/guides/call-variables) and leaves them blank otherwise; the review lists the ones you need to pass. - **Call limits and recording.** The maximum call length is imported. When the source agent never changed it, the platform's own default is set: 10 minutes for Vapi and ElevenLabs, 1 hour for Retell, 30 minutes for Bland. A source value that is not a positive number imports with no maximum, and the review says so. Silence and maximum-duration limits outside ThunderPhone's ranges are adjusted, and the review note names the source value and the imported one. When the source agent had call recording turned off, the agent's review note says whether it is imported with recording off or, where turning recording off is not yet available, with recording on. ## What changes when you import A few settings your old platform left at its own default take ThunderPhone's default instead. The review lists them in one note per agent, under **Show details**, only where the two platforms differ. The ThunderPhone figures below are its defaults, which you can change per agent: | Setting | Old platform default | ThunderPhone default | |---|---|---| | Ring time on outbound calls | Retell: 30 seconds | 60 seconds | | Ending a silent call | Retell: hang up after 10 minutes; ElevenLabs: never | Check in every 15 seconds (after about 6 seconds if nobody has spoken yet, after at least a minute if the caller asked the agent to wait), hang up after 1 minute of silence | | Call recording | Bland inbound numbers and personas: off | On. Contact support, or change it in the agent's settings where available | | At a voicemail | Bland personas: hang up | The agent decides from its prompt | A setting you changed on the old platform is imported as you set it, not listed here. The one exception is a silent-call setting of "never hang up": it is listed like a default, and the agent keeps ThunderPhone's check-ins. Change any of these in the agent's settings after import. ## Importing without a key Choose **Paste exported JSON** and paste the object(s) your platform's API or dashboard exports, as one object or a JSON list: - **Vapi** — an assistant object (`GET /assistant/{id}`), a Squad object (`GET /squad/{id}`), or a list of either. Tools attached only by id and knowledge files need a key to resolve. Vapi retired Workflows, so convert a Workflow to a Squad before pasting it. - **Retell** — the agent object (`GET /get-agent/{id}?version=...`) **together with** its same-version Retell LLM (`GET /get-retell-llm/{id}?version=...`) or conversation flow object in the same list. The importer links by id and version, prefers a pasted published agent when publication status is present, and imports a standalone LLM or flow under a generated name. - **ElevenLabs** — the agent object (`GET /v1/convai/agents/{id}`). Current exports usually reference webhook tools and knowledge documents only by id. A paste reports those items as unresolved; use an API key for a complete import. - **Bland** — a web agent, the `/v1/agents` response, or a pathway object. Each pasted object imports as its own agent, even when two share an id; tool and knowledge ids that need API access are listed for manual setup instead of being dropped. Anything referenced only by id and not present in the paste is listed in the notes. ## After the import 1. Open each agent and read its notes. 2. Place a test call from the builder. 3. Move your numbers over, or attach a new one (see **Phone numbers** above). 4. Test each API connection with the request tester. 5. Check that knowledge documents finished indexing. 6. Set an organization webhook if the old agents used one. The importer is also available over the [REST API](/api-reference/agent-imports). --- title: "Prompting guide" description: "How to write a voice agent's prompt that sounds natural on the phone and holds up under real callers, with three templates to start from." --- Write the prompt the way you would brief a new receptionist: who they are and who they work for, how a call goes from the first line to the last, and the facts they may give out, in plain language and in any language. A caller hears the agent once, at speaking pace, and the agent reads the prompt on every turn and copies its wording closely. This page shows how to write for both, and gives three complete templates to start from. The [prompt style guide](/guides/prompt-style) has every guideline in one place, with the ids (P1, W2 and so on) that [prompt review](/api-reference/agents#review-a-prompt) cites when it checks a prompt. Migrating from another platform? The [importer](/guides/import-agents) brings your agents over and rewrites each prompt to this guide's structure. For a one-off prompt, you can also paste it — or the whole exported agent JSON — into the [copilot](/guides/ask-the-copilot). ## Write the call in the order it happens A short opening says who the agent is and how it talks. The call then follows in the order it happens: how it opens, what to find out, what to confirm, how it ends. Each rule sits in the step where it applies, not in a list of rules at the end. Plain headings help: ```text # Who you are Your name, the business you represent, and in a few words how you talk: short turns, warm, plain words. # The call How you open, what you find out and in what order, what you confirm, and how the call ends. Each rule goes in the step where it comes up. # Facts What you may tell callers: hours, location, services, prices, policies. # Handing off When a person should take over, which transfer number to use, and what to offer when nobody can. ``` Lists are fine for facts the agent looks things up in, such as hours or prices. For behavior, write prose: a numbered list of rules, code-like headings or "see section 4" make the prompt harder to follow and to edit. The builder's prompt editor shows estimated tokens as you type and flags common problems — an empty prompt, placeholders that won't be filled in, or no way to hand a caller off. Longer prompts cost more per minute, because the models read the prompt on every reply; see [pricing](/guides/pricing). ## Tactics that matter on a phone call ### Specific beats general Vague instructions produce vague agents. Name the behavior you want: | Instead of | Write | | --- | --- | | "Be careful with orders." | "Read the order number back and ask the caller to confirm it before you change anything." | | "Be professional." | "Use the caller's name once you have it, and keep each turn to a sentence or two." | | "Help with scheduling." | "Offer the two earliest open times, then ask which works." | ### Write for the ear, not the eye Callers hear the agent and can't skim or re-read. Ask for short turns and one question at a time, with the question as the last thing the agent says: anything after it talks over the caller's answer. ```text Keep each turn to one or two short sentences unless the caller asks for more. Ask one question at a time, and make it the last thing you say. ``` Keep symbols out of anything the agent will say: slashes, ampersands, "@", ".com" and phone numbers written without spaces come out oddly in speech. Spell out how an unusual name or acronym is said, and listen to new terms in the agent's voice with **Talk** before you keep them. ### Confirm what would be costly to get wrong Speech recognition mishears names, digits and addresses. Say in the flow what to confirm, at the step where the agent collects it: ```text Once you have the callback number, read it back and ask the caller to confirm it before you book the appointment. ``` Ask for the read-back in the form the value was given — an email address letter by letter, a phone number digit by digit. A summary read-back ("so that's your Gmail") confirms nothing. How fast the agent reads it back is the platform's job, so the prompt only says what to confirm. ### Pick Storm when the call turns on a spelled-out string Phone audio is 8 kHz. Emails, surnames, confirmation codes, and policy numbers are where that bandwidth runs out: "M" and "N" are close, the caller's "dot" survives as a word, and the domain comes back one letter off. Bolt transcribes the audio to text first and reasons over the transcript, so whatever the transcript lost is already gone. Storm hears the audio itself and is markedly better on spelled-out strings. If a call is only useful when the email or the reference number is exactly right, that difference is worth more than the price gap — see [model tiers](/guides/agent-settings#model-tier). Then put the check somewhere the prompt can't skip: declare `"format": "email"` on the tool parameter that receives the address. ThunderPhone repairs the usual transcript artifacts, and rejects what it can't repair before your endpoint runs, so the agent re-asks instead of writing a bad address into your system. See [Build a tool integration](/guides/build-tool-integration). ### Describe the hard moments, not the words The happy path writes itself; calls are won and lost everywhere else. For each situation you can foresee — a question the prompt doesn't cover, an upset caller, a wrong number — say what the agent should achieve and what it may offer, in the step where it comes up. Leave out "if the caller says X, say Y" lines: the agent repeats a scripted line word for word, so it sounds canned, and a list of branches leaves out the cases between them. ```text When a caller asks something these instructions don't cover, offer to have a teammate call them back, and take their name and number. When a caller is upset, acknowledge it once and keep to what you can do for them. If they would rather talk to a person, transfer them to Front desk. ``` The opening greeting is the one line to write out word for word. When an example of wording helps elsewhere, give a few varied, full sentences and introduce them as examples ("For example, you might say ..."); clipped tags like "Still accurate?" sound terse once spoken. ### Give every call somewhere to go Every agent needs a way to hand off — a transfer number, a callback promise, or both. The prompt editor warns when it can't find one. ### Describe call actions in the flow Transfers, keypad presses, hold, and hang-ups need no separate tool — describe them in the step where they happen. Transfers go to the [transfer numbers](/guides/call-transfers) saved on the agent, by name; the agent sees only the names. ```text When the caller asks for a person, transfer them to Front desk. When the phone menu asks for a department, press 2. If you reach the billing menu, wait on hold until a person answers. Once the caller has nothing else, say goodbye and end the call. ``` For routing rules, screened handoffs, and failure fallbacks, see [Transfer calls](/guides/call-transfers). For all other platform actions, see [Built-in tools](/guides/built-in-tools). ### Leave settings to the settings Some things are agent settings, and prompt text about them is ignored or conflicts with them: what the agent says while a tool runs, filler phrases, silence check-ins and what happens after a long silence, how a read-back is paced, the language-switching style, the recording notice, and whether the agent or the caller speaks first. Set those in [agent settings](/guides/agent-settings#settings-win-over-the-prompt), and keep the prompt on the outcome ("confirm the email address before you save it"). The opening greeting itself is written in the prompt. ### Facts belong in knowledge, behavior in the prompt Don't paste your website into the prompt. Attach a [knowledge base](/guides/knowledge-base) and the agent searches it mid-call — the prompt stays short, and updating a policy doesn't mean re-editing the prompt. Keep the prompt for who the agent is, how the call runs and the few facts every call needs; put catalogs, FAQs, and long policy text in knowledge. The agent may say only facts it was given. ### Personalize per call with variables or dynamic config A placeholder such as `{{customer_name}}` in the prompt is filled from the `variables` you send when a call starts; see [per-call variables](/guides/call-variables). A placeholder with no value on a call becomes empty text. The builder lists the prompt's placeholders as a reminder that nothing fills them unless the call sends `variables`, test calls included. To change more of the prompt per caller (VIP routing, logged-in user context), rewrite it at call time with [dynamic call config](/guides/dynamic-call-config). ### Write in the language you want to hear The prompt can be written in any language, and the agent follows it. Which language the agent *speaks* is set separately — the primary language starts the call, and any [additional languages](/guides/supported-languages) switch automatically when the caller uses one. ## When a call goes wrong Fix the cause with the smallest general change: - **Check the configuration first.** Mishearing spelled letters is an audio limit (try a higher audio tier or Storm); a slow reply may be the tier or the endpointing setting. - **Add the principle, not a patch.** State the general rule or the missing fact the agent needed. The failing call's names, numbers and exact words stay out of the prompt. - **Take away before you add.** When an existing rule is too specific, too broad or in the wrong place, change or remove it rather than adding a counter-rule next to it. - **Make an ignored instruction clearer, not louder.** Capitals, "IMPORTANT" and stacked "never"s draw the model to one rule and make it worse at the rest of the call. Make the instruction more specific, or move it to the step where it applies. Prompts that have collected many small fixes drift: rules repeat, conflict, or sit far from the step they govern. Prompt review reports `refactor_suggested` when that happens, and a refactor rewrites the prompt into this shape while keeping every behavior it asks for. ## Templates Three complete prompts to copy, then make yours: swap in your business facts, transfer numbers, and policies. ### Front-desk receptionist (inbound) Save **Emergency line** and **Office manager** as [transfer numbers](/guides/call-transfers) on the agent. ```text # Who you are You are June, the receptionist for Lakeview Dental in Portland. You sound warm and unhurried, keep each turn to a sentence or two, and ask one question at a time. # The call Open with: "Thanks for calling Lakeview Dental, this is June. How can I help?" Find out what the caller needs. If they are in pain or say it's an emergency, transfer them to Emergency line right away. For an appointment, ask what it's for, then offer the two earliest open times and ask which works. Once they pick one, take their full name and a callback number. Read the number back and ask them to confirm it before you book. When they ask about a price, give it if it's listed under Facts; for any other procedure, tell them the dentist confirms the price at the visit. For anything you can't do on this call, take a message: their name, their number, and what it's about. Before you finish, ask whether there's anything else. Once there isn't, say goodbye and end the call. # Facts We're open Monday to Friday, 8 am to 5 pm, at 1420 Alder Street. We offer cleanings, fillings, crowns, and emergency visits. New-patient cleanings are $95. We don't do orthodontics. # Handing off When a caller asks for the office manager, transfer them to Office manager. For anything else that needs a person, offer a callback. ``` ### Order support with a lookup tool (inbound) Attach an [API connection](/guides/api-connections) or [connected app](/guides/connect-apps) for the order lookup, a knowledge base holding the returns policy, and a **Support team** transfer number. ```text # Who you are You are the support agent for Brightbox, an online electronics store. You're friendly and direct, and honest about what you can and can't do. Keep turns short and ask one question at a time. # The call Open with: "Thanks for calling Brightbox support. What's your order number?" Read the order number back digit by digit and ask the caller to confirm it before you look anything up. Then use the Get order tool, and tell the caller the order's status and delivery estimate in plain words. For return questions, search the knowledge base and follow the policy it gives. Offer only what your tools can do: you can start a return, and refunds and replacements are decided by the support team. If a tool fails twice, stop retrying and offer a callback. # Handing off Refund requests over $200, damaged items, and anything the caller says is urgent go to Support team. When a caller asks for a person at any point, transfer them to Support team. ``` ### Appointment reminder (outbound) For outbound calls, also set **who speaks first** and **voicemail handling** in [advanced call behavior](/guides/agent-settings#advanced-call-behavior) — the prompt covers the conversation, the settings cover the pickup. Send `patient_name` and `appointment_time` as [call variables](/guides/call-variables) when you place each call. ```text # Who you are You call on behalf of Lakeview Dental to confirm upcoming appointments. You're brief and courteous; the whole call takes under two minutes. # The call Open with: "Hi, this is June from Lakeview Dental. Am I speaking with {{patient_name}}?" Once you know you have the right person, tell them their appointment is {{appointment_time}} and ask whether it still works. If you reach someone else, ask when is a good time to reach the patient, and say nothing about the appointment. If the time works, thank them and end the call. If it doesn't, offer the two earliest open times and rebook, or take a note for the front desk. If they seem unsure who is calling, give the clinic's name again and offer to end the call. If they ask not to be called again, apologize, tell them you'll note it, and end the call. # Handing off For billing or treatment questions, tell them the front desk will call them back, and note the question. ``` Outbound calling has legal requirements — consent, calling hours, and identification rules vary by region. Review the [outbound calling laws guide](/guides/outbound-calling-laws) before running campaigns. ## Iterate until it survives real callers No prompt is right on the first draft. The loop that works: 1. **Talk to it.** Click **Talk** in the builder and probe the edges yourself — interrupt it, go off script, mumble a phone number. This is the fastest tweak-and-retry loop. 2. **[Simulate calls.](/guides/simulate-a-call)** An AI caller runs whole conversations against your agent, scenario by scenario. 3. **[Build a validation set.](/guides/validation-sets)** Turn the scenarios you care about into a repeatable suite you rerun after every prompt change. 4. **[Split-test in production.](/guides/experiments-ab-testing)** When two phrasings both seem fine, let real calls decide. A coding agent can run this loop for you, from reviewing the prompt to replaying real calls against each edit; see [Build with Claude Code, Codex or Cursor](/guides/coding-agents). Every deploy is snapshotted in the **History** tab, so prompt changes are safe to ship often — you can inspect the diff and roll back at any time. ## Next steps Every guideline prompt review checks, with its id. The builder end-to-end: prompt, engine, voice, tools, deploy. Give the agent facts to search instead of a longer prompt. Supervision, who speaks first, voicemail, silence check-ins, and more. --- title: "Prompt style guide" description: "How to write and edit a ThunderPhone voice agent's prompt: the guidelines prompt review checks, with the ids its findings cite." --- {/* GENERATED FILE: do not edit by hand. Source: api/agents/prompt_style_guide.md Regenerate: python3 scripts/docs-freshness/generate_prompt_style_guide.py CI regenerates this page and fails on drift (scripts/docs-freshness/generators.d/50-prompt-style-guide.sh). */} This is the guide [prompt review](/api-reference/agents#review-a-prompt) checks a prompt against, and its findings cite the ids below. Coding agents connected to the [MCP server](/guides/thunderphone-mcp-server) read the same text as the resource `thunderphone://guides/prompt-style`. For templates and examples, see the [prompting guide](/guides/prompting). This guide is for anyone who writes or edits a voice agent's prompt: a person in the builder, Copilot, or a coding agent working through the API. The prompt review tool uses it as its rubric and cites its guideline ids (P1, W2, and so on). A caller hears the agent once, at speaking pace, and cannot scroll back. The agent reads the prompt on every turn and copies its wording closely. Both facts shape everything below. ## Shape **P1. Write it the way a person would brief a new receptionist.** A short opening says who the agent is and who it works for. The call then follows in the order it happens: how it opens, what to find out, what to confirm, how it ends. Each rule sits in the part of the call where it applies. A reader should be able to ask of every paragraph, "could a person running this business have typed this?" **P2. Plain headings, prose underneath.** Headings such as "Who you are", "The call", "Facts" and "Handing off" help. Machine-style structure does not: numbered rule manuals, camelCase or code-like headings, long dash-bullet lists of rules, a tools reference up front, and cross-references like "see section 4 below". Lists are fine for data the agent looks things up in (hours, prices, an address). **P3. Manner of speaking once, briefly.** How the agent talks across the whole call (short turns, warm, plain words) fits in one short paragraph near the top. Everything specific to one step lives in that step. **P4. Facts apart from behavior.** Hours, prices, locations, policies and other facts go in a facts part of the prompt or in a knowledge base, not scattered through the call flow. Long catalogs and FAQs belong in a knowledge base. The agent may say only facts it was given. ## Content **C1. Principles and facts, not scripts.** Describe what the agent is trying to achieve and what it needs to know. Leave out "if the caller says X, say Y" branches, scripted answers to anticipated questions and canned lines. The configured opening greeting is the one scripted element. **C2. A fix states the general principle.** When a prompt change responds to an observed failure, the change adds a general principle or a missing fact, not a patch for that one case. The failing call's specifics (its names, its numbers, its exact words) stay out of the prompt. **C3. Prefer taking away to adding.** When a failure traces to an existing rule that is too specific, too broad or badly placed, change or remove that rule rather than adding a counter-rule next to it. **C4. Settings decide some things, not the prompt.** These are agent settings, and prompt text about them is ignored or conflicts with them: what is said while a tool runs (tool start line, filler phrases), silence check-ins and the final action after silence, how a read-back is paced, the language-switching style, the recording notice, and whether the agent or the caller speaks first. Point the prompt at the outcome instead ("confirm the email address before you save it"), and set the setting. The opening greeting itself is written in the prompt. **C5. Examples are lines the agent will say.** The agent copies example wording almost word for word, so every example is a full, natural spoken sentence, and a few varied examples beat one. Introduce them as examples ("For example, you might say ..."), not as the required line. Clipped tags like "Still accurate?" sound terse once spoken. **C6. No examples from our own customers' calls.** Examples describe the kind of situation in general terms or use an invented business. ## Wording **W1. Say what to do, plainly, once.** State the wanted behavior as an instruction in ordinary words: "Read the order number back digit by digit and ask the caller to confirm it." Say it once, where it applies. **W2. No emphasis.** No IMPORTANT, NOTE or CRITICAL labels, no words in capitals for force, no exclamation marks, no "Bad:" and "Good:" pairs, no blocks of what not to do. Words like "never", "always" and "must" used for force belong here too. Emphasis pulls the model's attention to one rule and makes it worse at the rest of the call. When the agent ignores an instruction, make the instruction clearer, more specific or better placed, not louder. **W3. Fix the structure that causes a behavior.** Before adding a rule against something the agent does, find the wording or structure that leads it there: a template the model fills in literally, a term that means two things, an if-else branch that a fast model reads loosely. Change that. **W4. Specific beats general.** "Offer the two earliest open times, then ask which works" beats "help with scheduling". Vague instructions give vague behavior. ## Speech **S1. One question per turn, and the question ends the turn.** Anything said after a question talks over the caller's answer. Put any explanation before the question. **S2. Short turns.** One or two short sentences unless the caller asks for detail. Spoken options are limited to a few. **S3. Write for the ear.** In what the agent says, avoid markdown, symbols and notation the voice will read oddly (slashes, ampersands, "@", ".com", thousands separators, unspaced phone numbers). Facts and values the agent passes to a tool, such as an email address or an account number, stay in their exact written form; when one also has to be said aloud, add how it is said next to it rather than rewriting it. Spell out how unusual names or acronyms are said, and listen to new terms in the agent's voice before keeping them. **S4. Confirm what would be costly to get wrong.** Names, numbers, emails, addresses and codes are confirmed back in the form they were given. The prompt says what to confirm; the platform paces the read-back. ## Iterating **I1. Test before you keep a change.** Replay the agent's dev set on the edited draft and compare it with the current version: which examples a change fixed, which it broke. Read the replies the way a caller would hear them. Anything a caller would notice is a problem, not a detail. **I2. The hold-out set is checked once.** Tune on the dev set only. Run the test set when a change is ready, and report its result as it is. A change made after seeing test results needs fresh test examples. **I3. Refactor when patches pile up.** A prompt that has collected many small fixes drifts from P1: rules repeat, conflict, or sit far from the step they govern. Then rewrite it into this shape, keeping every behavior it asks for. List each behavior as kept, merged, reworded or dropped, and drop nothing silently. Replay the dev set before and after; a refactor should change wording, not behavior. **I4. Check the configuration before the prompt.** Some failures are not the prompt's: mishearing spelled letters is an audio-understanding limit (try a higher audio tier or Storm), a slow reply may be the tier or the endpointing setting. Rule out configuration before rewriting text. --- title: "Agent steps and handoffs" description: "Split an agent into steps with their own instructions, tools and collected values, and hand calls between agents mid-call." --- A single prompt gives the model the whole job at once. **Steps** give it one phase of the call at a time: each step has its own instructions, its own tools, an optional line spoken word for word, and values it must collect before moving on. **Handoffs** let another agent in your organization take over mid-call, with the transcript intact, so the caller never repeats themselves. Every agent starts as a single prompt. Nothing about it changes until you add steps or handoffs. ## Steps or a single prompt? | Use steps when | Keep a single prompt when | | --- | --- | | The call has phases that must happen in order: verify, then book. | The conversation is open-ended: FAQ, reception, general support. | | A value must be captured before the agent moves on. | Nothing has to be collected in a fixed order. | | A rule must never bend, such as "callers under 18 go to a guardian". | The model's judgment is good enough for every branch. | | A lookup has to run at a fixed point in the call. | Tools can be called whenever the caller asks. | | Part of the call belongs to a different agent, with its own prompt and voice. | One persona handles everything. | Steps are not a scripted phone tree. Inside a step the model runs the conversation as it does with a single prompt. The step decides what it is working on, which tools it can use and what it needs before it can leave. ## How a stepped agent is built When an agent has steps, its `prompt` holds the **shared instructions**: the persona and rules that apply in every step. Each step adds its own instructions on top. On every turn the model sees the shared instructions, the current step's name and instructions, and the values collected so far. In the builder, switch the agent from **Single prompt** to **Steps**. You get a card per step with its name, instructions, say line, tools, collected values and transitions, a handoffs picker, and a read-only flow graph of the steps. Switching back to a single prompt offers a flattened prompt that merges the shared instructions and every step. Over the API, `steps` and `handoffs` are fields on the [agent](/api-reference/agents). Like every agent field, a `PATCH` stages them in the draft; `POST /v1/agents/{agent_id}/deploy` makes them live. Sending `"steps": null` switches the agent back to a single prompt. Test calls and **Talk** run the draft. The [copilot](/guides/ask-the-copilot) can build steps from a description ("add a step that verifies date of birth before booking") and edit one step without touching the others. ### Step fields | Field | Meaning | | --- | --- | | `id` | Required. Lowercase letters, digits and underscores, starting with a letter, up to 40 characters. Unique within the agent. | | `name` | Required display name, 1 to 80 characters. | | `instructions` | Required, up to 20,000 characters. Supports `{{var}}` [call variables](/guides/call-variables), like `prompt`. | | `say` | Optional line spoken word for word when the step is entered, up to 1,000 characters. Supports `{{var}}`. | | `tools` | `null` gives the step every tool attached to the agent. A list limits the step to those tools, by name. Built-in actions such as `end_call` and `transfer_call` follow the agent's settings in every step. | | `collect` | Up to 20 values the step gathers. Each has a `name`, a `type`, a `description` and `required` (default `true`). | | `on_enter_tool` | Optional tool name, called automatically when the step is entered. See [Webhook steps](#webhook-steps). | | `transitions` | Ordered list of where the step can go next. See [Transitions](#transitions). | | `global_when` | Optional. Makes the step reachable from any step. See [Global steps](#global-steps). | | `voice` | Optional voice id for this step. Ignored when the agent uses a custom voice configuration. | The graph itself is `{"start_step": "", "steps": [...]}`. The call begins in `start_step`. ## Transitions Each transition has `to`, a step id, and exactly one of: - **`when`**: plain language, and the **model decides**. The agent gets a built-in `go_to_step` tool listing the steps it may move to, with each `when` as the reason to pick it. - **`condition`**: a rule on collected values, and **code decides**. The model cannot skip it or talk its way around it. - **`"always": true`**: move on without a check. Code decides first: as soon as a step is entered, or once its required values are collected, the first matching `condition` or `always` transition wins, before the model can pick a `when`. So put `always` after the conditions as their fallback, or alone to chain a step straight into the next one. A step with `always` never waits for a `when` choice. A `condition` is `{"value": , "op": , "target": }`: | `op` | True when the value | | --- | --- | | `eq`, `ne` | equals, or does not equal, `target` | | `lt`, `lte`, `gt`, `gte` | is less than, at most, greater than, or at least `target` | | `in`, `not_in` | is, or is not, one of the values in the `target` list | | `exists`, `missing` | has, or has not, been collected (no `target`) | `eq`, `ne`, `in` and `not_in` compare text case-insensitively after trimming spaces. Numbers compare as numbers and dates as `YYYY-MM-DD` strings. A value that was never collected makes every op false except `missing`. Write `when` and `global_when` as a condition, such as "The caller wants to book an appointment", not as something to say. The agent sees them as internal routing notes and is told never to read them out or mention that it is changing steps. When the model calls `go_to_step`: 1. Every `required` value in the step must be present. If one is missing, the model is told which one, and asks the caller for it. 2. The values are stored. 3. `condition` and `always` transitions are checked in order. The first match wins. 4. Otherwise the agent moves to the step the model chose. 5. If nothing matches and the model named no step, the agent stays where it is. A jump to a [global step](#global-steps) is the exception: the caller changed topic, so it goes ahead even when the current step still has required values missing, and before that step's conditions are checked. `condition` and `always` transitions are also checked every time a step is entered, so a run of code-decided steps plays out in one go. The chain stops: - at a step it has already visited on this run; - after 8 hops; - at a step that still has required values to collect, unless the step has an `on_enter_tool` (a webhook step always routes on its result). Only the `say` line of the step where the chain stops is spoken. A step with no transitions is **terminal**. The agent finishes there and ends or transfers the call with its normal built-in actions. ## Collecting values List what a step needs in `collect`. The model passes the values when it calls `go_to_step`, and they stay available for the rest of the call. | `type` | Holds | | --- | --- | | `string` | Any text | | `number` | A number | | `boolean` | `true` or `false` | | `date` | A date as `YYYY-MM-DD` | Collected values are call content. They appear on the call next to the transcript and follow the same retention, deletion and redaction rules. When the agent's call recording is turned off, collected values are neither stored nor sent in webhooks; the step timeline still is. See [Where to see it on a call](#where-to-see-it-on-a-call). ## Say lines `say` is spoken exactly as written when the agent enters the step. The model then carries on in the new step in the same reply, knowing the line was just said, so it does not repeat it. Use `say` for greetings, required disclosures and fixed wording, and leave the rest of the reply to the model. The start step's `say` is the agent's opening line when it speaks first, after any consent announcement. When the caller speaks first, it opens the agent's first reply to a person, never to an IVR or a voicemail greeting. A step's `say` is not spoken again when the call returns to it through `__previous__` or a hand-back. Write it the way it should sound: spell out anything the voice could misread. Say lines support `{{var}}` call variables but not collected values. ## Webhook steps Set `on_enter_tool` to one of the agent's tools and it runs as soon as the step is entered, without waiting for the model. Its arguments are the collected values whose names match the tool's parameter names. The result is stored as a collected value named `_result`, truncated to 4 KB, and the step's `condition` and `always` transitions are checked straight away, so the lookup can route the call on its own result. The stored value has two fields: | Field | Holds | | --- | --- | | `status` | The HTTP status code your endpoint returned. | | `response` | The response body. | Route on a body field with `lookup_account_result.response.`, and on the status code with `lookup_account_result.status`. The lookup runs for up to 8 seconds, or the tool's own timeout if that is shorter. If it takes longer than 3 seconds, the agent says a short line so the caller is not left in silence. If your endpoint returns a status of 400 or above, or does not answer in time, the agent tells the caller it could not check that just now and carries on with what it can do. ## Global steps A step with `global_when` can be reached from any step whenever that description holds. The model decides, as with `when`. Use it for topics a caller can raise at any point: billing questions, a request for a human, a complaint. A transition with `"to": "__previous__"` on a global step returns the call to the step that was active before it, so the caller picks up where they left off. The step resumes: its `on_enter_tool` does not run again, its `say` is not repeated, and the agent continues it without starting over. ## Handoffs `handoffs` lists other agents in your organization that can take over the call: ```json [{"agent_id": 413, "when": "The caller asks about billing or a refund"}] ``` The agent gets a built-in `hand_off` tool with those agents and reasons. As with transitions, each `when` is an internal routing note, never read out. When it hands off, the agent says one short line telling the caller where they are going, such as "Sure, I'll send you over to our billing team", in its own voice. Then the target agent's prompt, steps, tools and voice take over and the call continues with the same transcript. The new agent introduces itself briefly and answers the caller's last request; it does not announce a transfer. The target's knowledge base is available from then on. The first time a call reaches an agent, it starts in that agent's `start_step`: the step's `on_enter_tool` runs and its `say` is spoken. When the call is handed back to an agent it already visited, that agent resumes the step it was on, without running `on_enter_tool`, repeating its `say` or introducing itself again. Collected values are shared by every agent on the call. - A target must be an agent in the same organization, and an agent cannot hand off to itself. - Everything call-level stays with the agent that answered: product, languages, who speaks first, voicemail, consent announcement and every other call setting. - Targets can hand off further, and back. One call can reach up to 8 agents. - A test call runs the draft of the agent you test. The agents it hands off to run their deployed versions. - If two agents have a tool with the same name and different definitions, only one is kept for the call, and the answering agent's always wins. The builder warns you when you save. - An agent can have handoffs without steps. Its whole prompt is used as-is. `go_to_step` and `hand_off` are reserved on agents with steps or handoffs, so your own tools cannot use those names. ## Where to see it on a call Open the call in **Call History**. A step timeline next to the transcript shows each step the call entered and why, and the collected values are listed with it. The [call object](/api-reference/calls#retrieve-a-call) and the call-complete webhook ([`telephony.complete` / `web.complete`](/webhooks/call-complete)) carry the same data as `steps_trace` and `collected_values`. Both are absent on calls without steps. ```json "steps_trace": [ {"t_ms": 0, "agent": "a412", "step": "greet", "via": "start"}, {"t_ms": 51230, "agent": "a412", "step": "verify", "via": "model"}, {"t_ms": 98410, "agent": "a412", "step": "minor", "via": "condition"} ], "collected_values": {"full_name": "Dana Reyes", "dob": "2010-04-02", "age": 15} ``` `agent` is `a` followed by the agent id. `via` says what moved the call: | `via` | Meaning | | --- | --- | | `start` | The call began in this step. | | `model` | The model chose the step through `go_to_step`, a `when` or a `global_when`. | | `condition` | A `condition` or `always` transition matched. | | `on_enter` | A transition matched on a webhook step's result. | | `handoff` | Another agent took over. | ## Where steps run Steps run natively on **Spark**, **Bolt** and every **Storm** product, on inbound, outbound and campaign calls, test calls, and web and widget calls. Everywhere else the agent still works, just without the structure: it runs a flattened prompt that merges the shared instructions and every step, in order. That covers: - [Realtime](/guides/use-with-openai-realtime) sessions, including sessions for a saved agent; - inline configurations returned by a [dynamic call config](/guides/dynamic-call-config) webhook. A webhook that returns an `agent_id` uses that agent's steps. ## Limits | Limit | Value | | --- | --- | | Steps per agent | 100 | | Collected values per step | 20 | | Handoff targets per agent | 8 | | Agents reachable in one call | 8 | | Size of the `steps` JSON | 200 KB | | Step `instructions` | 20,000 characters | | `say` line | 1,000 characters | | Stored `on_enter_tool` result | 4 KB | A save that breaks a rule returns `400` with the path of the bad field, such as `steps.steps[2].transitions[0].to`. Tool names in `tools` and `on_enter_tool` must be tools attached to the agent. Per-step languages, scripted steps that skip the model, and collected values inside say lines are not supported. ## Imported agents [Importing](/guides/import-agents) from Retell, Bland, Vapi or ElevenLabs brings multi-prompt agents, flows, pathways and workflows over as steps, and squads and agent transfers over as handoffs between the imported agents. The review page shows both before you import. ## Example: dental intake Three steps in order: greet, collect the patient's details, book. ```json { "prompt": "You are Ava, the front desk assistant at Acme Dental. Be warm and brief. Ask one question at a time.", "steps": { "start_step": "greet", "steps": [ { "id": "greet", "name": "Greeting", "instructions": "Greet the caller and ask how you can help.", "say": "Thanks for calling Acme Dental, this is Ava. How can I help you today?", "tools": null, "collect": [], "transitions": [ {"to": "details", "when": "The caller wants to book an appointment"} ] }, { "id": "details", "name": "Patient details", "instructions": "Ask for the caller's full name, date of birth and the reason for the visit. Read the date of birth back and ask the caller to confirm it.", "tools": null, "collect": [ {"name": "full_name", "type": "string", "description": "Caller's full name", "required": true}, {"name": "dob", "type": "date", "description": "Date of birth", "required": true}, {"name": "reason", "type": "string", "description": "Reason for the visit", "required": true}, {"name": "new_patient", "type": "boolean", "description": "First visit to the practice", "required": false} ], "transitions": [ {"to": "book", "when": "The caller has confirmed their details"} ] }, { "id": "book", "name": "Book", "instructions": "Offer the two earliest open slots from book_slot and book the one the caller picks. Confirm the date and time, then say goodbye and end the call.", "tools": ["book_slot"], "collect": [], "transitions": [] } ] } } ``` Stage it with `PATCH /v1/agents/{agent_id}`, try it with **Talk**, then deploy. The `details` step cannot hand over to booking until the name, date of birth and reason are collected; `new_patient` is optional. ## Example: an age-gated branch The model can talk to anyone, but code decides who gets booked. Collect the age, then route on it before the model's own choice is considered. ```json { "id": "verify", "name": "Verify age", "instructions": "Ask for the caller's age.", "tools": null, "collect": [ {"name": "age", "type": "number", "description": "Caller's age in years", "required": true} ], "transitions": [ {"to": "guardian", "condition": {"value": "age", "op": "lt", "target": 18}}, {"to": "book", "when": "The caller has given their age"} ] } ``` A caller who says fifteen goes to `guardian`, whatever the model decides. Everyone else goes to `book`. Both are ordinary steps elsewhere in the graph. ## Example: account lookup webhook step The caller gives an account number, the lookup runs on entering the next step, and its result picks the route. The `not_found`, `collections` and `support` steps are left out for brevity. ```json [ { "id": "identify", "name": "Identify", "instructions": "Ask for the caller's account number and read it back digit by digit.", "tools": null, "collect": [ {"name": "account_number", "type": "string", "description": "Account number", "required": true} ], "transitions": [ {"to": "lookup", "when": "The caller has confirmed their account number"} ] }, { "id": "lookup", "name": "Look up account", "instructions": "Tell the caller what you found on their account and ask how you can help.", "tools": ["lookup_account"], "collect": [], "on_enter_tool": "lookup_account", "transitions": [ {"to": "not_found", "condition": {"value": "lookup_account_result.response.found", "op": "eq", "target": false}}, {"to": "collections", "condition": {"value": "lookup_account_result.response.account_status", "op": "in", "target": ["past_due", "suspended"]}}, {"to": "support", "when": "The caller needs help with an active account"} ] } ] ``` `lookup_account` receives `account_number` because its parameter has that name. If the response body has `"found": false`, the call goes to `not_found`. If its `account_status` is `past_due` or `suspended`, it goes to `collections`. An active account stays in `lookup`, where the model helps and moves on to `support`. To send a failed lookup to its own step, add conditions on the status first: an error response stores its status code, so `{"value": "lookup_account_result.status", "op": "gte", "target": 400}` catches it, and a timeout or no answer stores `"error"`, so `{"value": "lookup_account_result.status", "op": "eq", "target": "error"}` catches that. ## Example: front desk to billing Two agents. The front desk (agent `412`) answers every call and hands billing questions to a billing agent (agent `413`) with its own prompt, tools and voice. On the front desk: ```json { "handoffs": [ {"agent_id": 413, "when": "The caller asks about a bill, a payment or a refund"} ] } ``` On the billing agent, so it can hand the caller back: ```json { "handoffs": [ {"agent_id": 412, "when": "The billing question is resolved and the caller needs something else"} ] } ``` The front desk says a short line such as "Let me get you to our billing team", and the billing agent introduces itself and picks up the conversation where the front desk left it. When billing hands back, the front desk resumes where it was, without greeting the caller again. The call's product, languages, consent announcement and other call settings stay with the front desk, because it answered. If billing only needs its own instructions and not its own agent, use a global step on the front desk instead. When the question is answered, the call goes back to whatever step the caller was in: ```json { "id": "billing", "name": "Billing questions", "instructions": "Answer questions about bills and payments. Offer to send a payment link by email.", "tools": null, "collect": [], "transitions": [ {"to": "__previous__", "when": "The billing question is answered"} ], "global_when": "The caller asks about a bill or payment at any point" } ``` ## Next steps Write shared instructions and step instructions that hold up on the phone. Create the tools your steps and webhook steps call. Run the same scenarios against every branch before you deploy. Every agent field, draft and deploy. --- title: "Voice agent settings reference" description: "Every ThunderPhone voice agent setting explained: tiers, voices, languages, acknowledgements, tools, speak order, voicemail, consent announcements, silence handling, testing, and deployment." --- Open **Voice Agents**, select an agent, and use its **Config** tab to control how it sounds, reasons, starts calls, uses tools, handles silence and voicemail, and reaches callers. Changes autosave to a draft; production traffic does not change until you select **Deploy**. This page is the complete dashboard reference. For field names and request examples, use the [Agents API reference](/api-reference/agents). ## Prompt and identity The agent name identifies it throughout the dashboard, reports, call history, and client portals. The prompt is the agent's operating script: persona, business facts, required questions, escalation policy, and call actions. The prompt editor reports estimated tokens and flags common problems such as an empty prompt, unresolved template placeholders, a missing escalation path, or a very long prompt. Longer prompts cost more per minute, because the models read the prompt on every reply; see [pricing](/guides/pricing). Transfers, keypad presses, hold behavior, and hang-up decisions can be written directly into the prompt. Attach API, app, MCP, or knowledge tools when the agent needs external data or actions. For routing rules, cold transfers, and screened warm transfers, see [Transfer calls](/guides/call-transfers). ## Model tier | Tier | Best fit | | --- | --- | | **Spark** | Cost-sensitive, straightforward calls. | | **Bolt** | Fast responses and balanced everyday workflows. | | **Storm** | Complex instructions, audio understanding, and reasoning. | Storm exposes two optional controls: - **Acknowledgement** — **None** stays silent while preparing the full reply; **Tick** plays a soft waiting sound; **Verbal** speaks a brief acknowledgement. Verbal text can be generated from the main prompt or supplied manually. **Custom acknowledgement prompt** appears only when the selected Storm mode supports verbal acknowledgements; otherwise that field is unavailable. - **Extra intelligence** — adds deeper reasoning for complex calls. Bolt exposes **Audio understanding**, which sets how the agent hears the caller. Every Bolt agent starts on **Standard**; you can downgrade it to **Lean** or upgrade it to **Hearing**: - **Standard** (default) — three transcribers. Calls that process PHI use only BAA-covered transcribers. - **Lean** — one transcriber. Cheapest; it mishears more often. - **Hearing** — Standard, plus on some turns a powerful ensemble of audio models listens to the caller's audio to work out exactly what they said. Best for languages and names; those turns take 1–2 seconds longer. **While the audio models listen** sets what the caller hears meanwhile: **Spoken acknowledgement**, **Ticking sound**, or **Silence**. In the API these are `audio_tier` (`standard`, the default, `lean`, or `hearing`), `acknowledgement_feedback_mode` (`acknowledgement`, `tick`, or `none` for silence), `endpointing` (`faster`, `fast`, `normal`, or `patient`), and `intelligence` (`lite` or `standard`). Older API clients may still use `eagerness` for `endpointing`. Other tiers ignore `audio_tier`, `endpointing`, and `intelligence`. Each call is billed at what its parts cost at our providers' list prices, plus an infrastructure charge and a markup, so every setting that uses more model, transcription or voice work costs more per minute. Zeus shows what a minute of the agent costs before you go live; see [pricing](/guides/pricing). #### Bolt endpointing and intelligence **Endpointing** controls how long the caller must stay quiet before the agent starts preparing a reply: - **Faster** - **Fast** - **Normal** (default) - **Patient** **Intelligence** controls the Bolt reply tier: - **Lite** - **Standard** (default) Existing Bolt agents stay on **Normal** endpointing and **Standard** intelligence unless you change them. ## Voice, background, and languages Select a voice and play its sample before deploying. The picker only requires your chosen voice to support the **primary language**. If you turn on **Switch to native voices**, additional languages can be covered by same-gender native catalog voices instead. Premium voices cost more per minute than the default voices. Your custom voices appear in the same picker once ready. See [Voice library and custom voices](/guides/voice-library). **Background track** can be: - **None** — no ambient audio. - **Call center** — a call-center ambience, with an in-dashboard preview. Set one **primary language**, then any number of **additional languages**. The agent automatically detects when a caller switches to a selected language and responds in it. There is no language surcharge; languages served by premium voices cost a little more per minute. See the generated [Supported languages](/guides/supported-languages) catalog. When an agent has **Additional languages**, enable **Switch to native voices** to use a catalog voice native to the detected language, matched by gender. It switches back to the configured voice for the primary language. If no matching native voice is available, the current voice stays in use. Custom voices do not switch. Test each language with **Talk**, then select **Deploy**. Native switching has no separate charge. The voice that speaks is billed at its provider's list price. **Pronunciation dictionary** (API field `pronunciation_dicts`, off by default) reads the agent's Cartesia speech through ThunderPhone's pronunciation dictionary, for example car model numbers and brand names. It applies to ThunderPhone's own Cartesia voices on phone, widget and test calls, not to a customer's own Cartesia key or Realtime sessions. For your own words, use the agent's [words list](/api-reference/agents#words). ## Connected capabilities The Config tab has four independently attachable capability sections: | Section | Use it for | | --- | --- | | **Connected apps** | Approved operations in Google Calendar, Google Sheets, Slack, HubSpot, Salesforce, or Cal.com. | | **API connections** | Your own HTTP endpoints, described as callable functions. | | **Knowledge** | Files or web content the agent can search during a call. | | **MCP servers** | Cached tools discovered from remote Model Context Protocol servers. | An attachment is part of the draft and becomes available to production calls after deployment. Configure the resources first under **Connections** or **Knowledge**, then attach them to the agent. ## Phone and web endpoints Under **Phone numbers**, attach an existing number, get a ThunderPhone number, or import a number through a VoIP connection. A number can use different agents for inbound and outbound directions. Each number answers with the agent's latest published version, or with one version you pick, so you can try changes on a test number while the main line stays on the version it has. ### Call setup webhook **Call setup webhook** is a URL your server answers before each inbound phone call this agent takes, to fill in the agent's `{{variables}}` and the call's context: the caller's account, their last order, why you expect them to call. It receives the caller, the number and the agent's version, and answers with `variables` and `context` ([request and answer](/webhooks/call-incoming#agent-call-setup-webhook)). When the prompt uses a variable with no default, the agent needs this webhook to answer a phone number: the builder lists those variables, the agent can't be put on a number or published to one without a webhook, and a call whose webhook doesn't answer doesn't start. A variable with a default, like `{{first_name|there}}`, lets the agent answer even when the webhook fails. Under **Web widgets**, attach a widget so visitors can speak to the agent from a site. Widget availability can also be enabled or disabled as part of agent configuration. ## Time zone **Time zone**, under the phone numbers, is what the agent treats as "now": it states the date and time, offers appointment times and works out "today" and "tomorrow" in this zone. This agent's business hours in reports use it too. A new agent starts with your organization's time zone, or your computer's if the organization has none. Change it if your callers are somewhere else. When the agent's phone number is in a different zone from the one you picked, the builder says so and offers to switch. An agent without a time zone (an older one, or one created through the API without `timezone`) uses the zone of the number the call is on, then the zone of the agent's numbers, then the organization's, then US Pacific. The builder shows which zone that is and asks you to confirm it, or to pick one when the numbers don't settle it (a toll-free number, or numbers in different zones). When one agent serves contacts in several time zones, pass the zone with each call instead: `timezone` on [`POST /v1/call`](/api-reference/outbound-calls) or in your [configuration webhook's response](/guides/dynamic-call-config#set-the-time-zone-per-call). ### If your agent has no time zone yet New agents require a time zone. Older agents can still have none. If an older agent has no time zone, ThunderPhone first uses the agent's own setting if there is one, then your organization's, then the time zone inferred from the agent's phone numbers. If that still does not settle it, it falls back to Los Angeles. The builder shows the resolved zone and asks you to confirm it, or to pick one when the numbers do not settle it. ## Advanced call behavior Expand **Advanced** at the bottom of Config for the following controls. ### Supervision When enabled, a second AI monitor checks each agent turn against the instructions and can intervene silently. Its model use is billed like any other part of the call. Supervision is not available on Bolt. ### Connection tone Play a short tone immediately after the call connects and before either party speaks. ### Intro clip Play a prerecorded clip at the start of inbound calls, followed by ringing, so the line sounds like a company phone line before the agent answers. It plays on inbound phone calls and on **Talk** (browser) test calls in the builder. It does not play on outbound calls, AI test calls, web widget calls, or Realtime API calls. - **Off** (default) — the agent answers right away. - **Recording notice** — a recorded voice says "This call may be monitored or recorded, for quality assurance purposes." Preview it in the builder. - **Your own recording** — upload an audio file (MP3, WAV, M4A, and other formats your browser can play) or record one in the browser. Clips can be 0.5 to 15 seconds long. The builder converts the clip before uploading it, and you can play it back or remove it. **Ring after intro** sets how many US rings play after the clip: **Off**, **1 ring** (default), or **2 rings**. The order at the start of a call is: connection tone (if on), intro clip, rings, then the [consent announcement](#consent-announcement) or [AI-processing disclosure](#ai-processing-disclosure) if they apply, then the greeting. The intro clip never replaces the consent announcement or the AI-processing disclosure, even when your clip says the call is recorded. Caller audio is not processed while the clip and rings play. A custom clip is fetched when the call starts; if it cannot be loaded within a few seconds, the call goes ahead without it, so a failed load can delay the greeting by up to about 3 seconds. The intro clip is a draft setting like the others: it reaches live calls when you deploy. Stock recording notice voice: Allison Smith, [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/), adapted (trimmed, joined, resampled) by ThunderPhone. ### Tool sound Choose the sound made by the prompt-callable sound tool: - **None** — the tool is not offered. - **Chime**, **Ding**, or **Beep** — each can be previewed in the builder. ### Typing sounds Enable **Typing sounds** to play a soft keyboard sound while the agent runs a custom tool. Platform-managed tools do not trigger it. Once it is on, pick the sound. Each one can be previewed in the builder: - **Keyboard and mouse** (default): typing with mouse clicks, like someone looking it up. - **Mechanical keyboard**: bright, clicky mechanical keys. - **Desktop keyboard**: steady typing on a standard keyboard. - **Laptop**: soft, flat laptop keys. - **Office keyboard**: quiet, muted office keyboard. - **Quiet room**: soft room tone with one person typing. ### Tool-start line Choose whether something is said when the agent calls a tool: - **Always** (default for new agents): something is said at every tool call, however fast the tool returns. If the agent spoke with the call, its own words are that line. Otherwise a short line written for the call plays as soon as it is ready, or a standard one such as "One moment, please" if it takes more than about a second. Tools that play their own audio or act on the call itself (keypad input, sounds, hold, transfers, ending the call, step handoffs) still get a line only if they are slow, and so does any tool while the agent is in a phone menu or talking to voicemail. - **Only if the tool is slow**: a short line plays only if the tool is still running about 3 seconds after the agent last spoke. Agents created before this setting existed keep this choice. - **Never**: ThunderPhone says nothing when a tool starts, and the agent is told not to speak when it calls a tool. Anything the agent still says with the call plays. The agent's own words always play, whichever option you pick. The caller hears at most one tool-start line per turn: a line already spoken in that turn, such as the agent's words or a filler on a slow reply, counts as it. ### Long-wait updates On by default. When a tool is still running after about 12 seconds of silence, the agent says it is still working on it, then adds a short patience line about every 15 seconds until the result arrives. These lines are worded for the call: they follow the agent's prompt and persona, the conversation so far and the call language, so they do not repeat one fixed sentence. If a line cannot be written in time, the agent uses a standard one such as "Sorry, this is taking a little longer than expected". Slow replies without a tool get short standard lines such as "Just a sec". Turn **Long-wait updates** off to stay quiet after the tool-start line until the result arrives. Slow replies without a tool then get no filler either. Typing sounds still play if they are on. Storm's acknowledgement setting is separate and is not affected. ### Settings win over the prompt Tool-start line and Long-wait updates decide what ThunderPhone says while a tool runs. Prompt text cannot stop or add these lines. For an agent that stays silent while tools run, set **Tool-start line** to **Never** and turn **Long-wait updates** off rather than asking for silence in the prompt. The builder's prompt check warns when the prompt asks for silence, or for a line at every tool call, and these settings say otherwise. ### Send email Enable **Send email** and enter one recipient address to give the agent the `send_email` tool. The address is fixed in the agent settings. The model writes the subject and plain-text body, but it cannot choose or change the recipient. Prompt the agent with the exact situations that should produce an email, such as sending a caller's message after collecting their name and callback number. See [Built-in tools](/guides/built-in-tools) for the full tool behavior. ### Calculator Enable **Calculator** to give the agent the `calculate` tool. The agent uses it to add up order totals, fees and change exactly instead of doing the math itself. It is off by default; turn it on for agents that quote prices. No prompt change is needed. See [Built-in tools](/guides/built-in-tools) for the full tool behavior. ### Send SMS Enable **Send SMS** to give the agent a `send_sms` tool. Choose which connected VoIP number (Twilio or Telnyx, SMS-capable and verified) to send from; ThunderPhone-provisioned numbers cannot send SMS. You can limit sending to the caller only, or allow other recipients and keep an allowlist of approved numbers. You can also enable an SMS follow-up template for messages sent after the call. See [Built-in tools](/guides/built-in-tools) for the full tool behavior. ### Extra audio context **Extra audio context** controls how much recent caller audio is carried between turns: - **Automatic (recommended)** uses more recent audio for inbound, browser, and WebSocket calls, and only the latest turn for outbound phone calls. - **On** includes the last few caller turns to help with corrections, spelling, and number-heavy data collection. - **Off** includes only the latest caller turn for lower latency. The Agents API also exposes the Storm-only `audio_context_mode` compatibility field: `full` or `reduced`. It is separate from the dashboard's `additional_audio_context` tri-state above. ### Who speaks first Configure inbound and outbound calls separately: - **Inbound:** agent speaks first, or caller speaks first. - **Outbound:** agent speaks first, or callee speaks first. To remove the agent's initial greeting, choose **Caller speaks first** for inbound calls or **Callee speaks first** for outbound calls. This setting is useful for outbound dialers that must wait for a person or phone menu before beginning the script. ### Voicemail handling Choose what happens when an outbound call reaches voicemail: | Option | Behavior | | --- | --- | | **Let the agent decide** | The prompt and conversation context determine whether to speak or hang up. | | **Hang up silently** | End without leaving a message. | | **Leave a message, then hang up** | Speak the configured message and end the call. | Hanging up on voicemail can cost you pickups. A short message usually works better: people call back numbers that leave one, and callbacks help the number's standing with carriers. The builder says so when you pick **Hang up silently**. Voicemail messages can contain up to **2,000 characters** and support `{agent_name}` and `{org_name}`. See [Pricing](/guides/pricing#hold-time-phone-menus-and-transfers) for how voicemail and hold time are billed. ### Consent announcement By default, a short announcement at the start of every call tells the caller that they are speaking with an AI assistant and that the call is recorded. You can edit the announcement text, up to **2,000 characters**; it plays verbatim in the agent's language. Disabling the announcement on an eligible agent requires an organization admin to affirm that the organization will provide any legally required notice and obtain any legally required consent. Re-enabling and later disabling it requires a new affirmation. Review the [recording consent laws guide](/guides/recording-consent-laws) before changing this setting. ### Record calls **Record calls** controls whether this agent stores call audio, transcripts, and other call content. The organization-wide recording setting must also be enabled for recording to occur. When recording is off, those artifacts are not stored. A recording covers at most the first 3 hours of a call; a longer call continues, but the audio after 3 hours is not recorded. See [Call recording consent laws](/guides/recording-consent-laws#turning-recording-off-and-the-ai-processing-disclosure). ### AI-processing disclosure When recording is off, **AI-processing disclosure** controls the shorter notice that audio is processed live but not recorded. Choose **Use organization setting**, **Always play**, or **Don't play**. Disabling the disclosure requires an organization-admin attestation. ### Uninterruptible disclosures **Uninterruptible disclosures** remains visible for compatibility, but enabling it has no effect. Leave it off. For required opening language, use **Consent announcement**, which plays before conversation and cannot be interrupted. ### Silence check-ins When enabled, the agent checks whether the caller is still present after a period of silence, repeats up to your limit, then hangs up or transfers the call: - **Seconds before check-in:** 10–120 seconds. - **Check-ins before hanging up or transferring:** 1–10. - **After the last check-in:** **Hang up** (the default) or **Transfer to** one of the agent's [transfer numbers](/guides/call-transfers). The transfer is skipped when the caller never said anything on the call: the agent hangs up instead, so a dead line never rings a person. If the transfer can't be placed, the agent hangs up. - **What the agent says:** the line spoken at that moment, up to 300 characters, read exactly as written. Leave it blank for the default: "I'm not hearing anything, so I'll let you go. Feel free to call back." before hanging up, or "Please hold while I transfer you." before a transfer, in the call's language. The defaults are 15 seconds and 3 check-ins. The check-ins follow the conversation: - If nobody has said anything since the call started, the agent asks "Hello? Are you there?" after about 6 seconds, then waits your interval between check-ins. - If the caller asked the agent to wait ("give me a minute", "let me grab my card"), the agent gives them the time they asked for, at least a minute, and then only lets them know it's still there. It hangs up after about a minute without a reply following its last check-in. - Otherwise the check-ins change the longer the line stays quiet. First the agent nudges on what it just said: "Did you catch all that?" after reading details back, or its question again, short but whole. Later it asks "Hello? Are you still there?", then "Hmm, I think I might've lost you. Can you hear me?" With 25 seconds or more before a check-in, the first check-in skips the nudge and asks whether the caller is still there. Through the API, `"silence_interval_seconds": null` leaves the pace to ThunderPhone, as for an inline call configuration that doesn't set it. The pace then follows the conversation: - Before the caller has said anything, check-ins come after 6, 10 and 15 seconds of silence. - Ordinary quiet during the call gets check-ins after 10, 15 and 15 seconds. - After the caller asks the agent to wait, the first check-in comes after at least 60 seconds, or the time they asked for, up to 10 minutes. ### Call length and timeouts Use these controls to keep stalled or unusually long calls from accumulating call time and telephony charges: - **Minutes of silence before hanging up:** 1–60 minutes. The default is 10 minutes and applies when silence check-ins are disabled. The call then ends with nothing said; the after-the-last-check-in settings don't apply. - **Max call duration:** 1–360 minutes. New agents start at 60 minutes, except agents made from the Interpreter template, which start with no agent-level cap; raise it, lower it, or switch it off for no agent-level cap. Agents created before this default keep their existing setting. 45 seconds before the limit (a quarter of the limit when it is under 3 minutes) the agent is told to wrap up and say goodbye. - **Platform limit:** every call ends after 6 hours, whatever the agent's setting. The agent gets the same wrap-up warning first. - **Ring duration:** let outbound and transferred calls ring for 5–120 seconds. Outbound calls ring for at least 14 seconds whatever you set: a call that rings briefly and drops looks like a one-ring scam call, and carriers are quicker to label the number as spam. A shorter setting still applies to transfers. Leave it unset for platform defaults: 60 seconds for outbound calls, 45 seconds for warm transfers, and the carrier default for cold transfers. This setting does not affect inbound ringing. ## Builder tabs and lifecycle | Tab or action | Purpose | | --- | --- | | **Live / Talk** | Speak with the current draft in your browser and inspect transcript and tool activity. | | **Calls** | Review calls handled by this agent. | | **A/B** | Create prompt or configuration variants and compare live outcomes. | | **Simulate** | Run saved or ad-hoc AI-caller scenarios against the draft. | | **Validation** | Replay frozen real-call turns against the draft before deployment. | | **History** | Compare deployed revisions and restore one into the draft. | | **Discard** | Revert every pending draft setting to the deployed version. | | **Deploy** | Atomically promote the complete draft and create a revision. | If another editor saves the same agent after you loaded it, ThunderPhone stops the conflicting save or deploy and asks you to refresh rather than overwriting their changes. ## Manage agents from the list The **Voice Agents** page also supports organization-level lifecycle actions: - **Duplicate** an agent as a new starting point in the same organization. - **Transfer** an agent to another organization you belong to. **Copy** keeps the source; **Move** removes it from the current organization and requires administrator access. - Delete one agent, or bulk-delete selected agents, after confirmation. ### Export the agent list Select one or more agents and export their summary fields as **CSV** or **JSON**. The export does not include full prompts and is not a configuration backup. Copying or moving an agent across organizations does not silently grant the destination organization access to source-only connections. Review the copied agent's app, API, knowledge, MCP, phone-number, and widget attachments before deploying it there. ## Related pages A concise end-to-end builder walkthrough. Base tiers, add-ons, call metering, numbers, and one-time charges. Freeze real-call turns and replay them before deploying. Request fields, drafts, deploys, versions, and examples. --- title: "Built-in tools" description: "Prompt ThunderPhone agents to end calls, transfer callers, navigate keypads, wait on hold, search knowledge, play sounds, send email, and calculate totals." --- Built-in tools let an agent perform common call actions without connecting your own API. Most are available automatically; email, sounds, the calculator, and knowledge search appear only after you configure the related setting or source. Tell the agent when to use each action in its prompt. | Tool | Use it for | How to enable it | Supported calls | | --- | --- | --- | --- | | `end_call` | Finish a completed conversation | On by default; `system_tool_settings` can turn it off | Phone, browser Talk, widget, simulation, and Realtime | | `transfer_call` | Route a phone caller | Configure routing in the prompt | Ordinary phone calls; inline Realtime with `call_events` can use cold transfer. Saved-agent Realtime omits it. | | `send_keypad_input` | Press keys in a phone menu | On by default for outbound calls; `system_tool_settings` can turn it on or off | Phone calls; inline outbound Realtime with `call_events`. Saved-agent Realtime omits it. | | `no_response` | Keep listening while an automated menu speaks or a caller thinks aloud; a caller who asks the agent to wait gets just "Sure." | Always available | All call types | | `wait_on_hold` | Wait silently for a person to return | On by default for outbound calls; `system_tool_settings` can turn it on or off | Phone calls and outbound inline Realtime sessions | | `search_knowledge_base` | Answer from attached sources | Attach knowledge to the agent | Saved-agent calls with attached knowledge | | `play_sound` | Play a configured notification sound | **Advanced → Tool sound** | Saved-agent calls | | `send_email` | Email one fixed recipient | **Advanced → Send email** | Saved-agent calls | | `calculate` | Add up totals, fees, and change exactly | **Advanced → Calculator** | Saved-agent calls | ## `end_call` Ends the current call when the prompt tells the model to do so. It is on by default. An agent that should never hang up on its own, such as an interpreter relaying between two people who end the call themselves, can turn it off with the agent's `system_tool_settings` field (`{"end_call": false}`). The call then ends when a person hangs up, or through the silence and time limits. Write the condition and any required farewell into the prompt: ```text When the caller confirms they need nothing else, say goodbye and end the call. ``` A model-ended call has `end_reason: "ai_hangup"`, including one that ends after the call time limit warning. The call ends with `max_duration` only if the time limit is reached first, with `balance_exhausted` when the prepaid balance runs out, and with `price_cap_reached` when the call reaches the agent's hard price cap (the agent says a short goodbye first). ## `transfer_call` Transfers a phone caller to a fixed number supplied by the model. Cold transfer is immediate. A screened warm transfer privately calls the destination before connecting the parties. Warm transfer requires a verified imported VoIP number that can place outbound calls. Put the destination number and exact routing condition in the prompt: ```text If the caller asks for billing, transfer them to +15551234567. ``` A successful cold transfer has `end_reason: "ai_transfer"`; a completed warm handoff has `end_reason: "ai_warm_transfer"`. A failed warm attempt returns control to the agent, so the call's later ending determines its end reason. On non-phone sessions, a transfer request ends the session instead. See [Transfer calls](/guides/call-transfers) for dashboard and API setup, routing, screening, failure handling, limits, and testing. ## `send_keypad_input` Sends DTMF (phone keypad) digits `0`–`9`, `*`, and `#` to a phone line. The model uses it to navigate an IVR, the automated menu that asks callers to press keys. It is on by default for outbound calls and off for inbound calls; set `"send_keypad_input": true` or `false` in the agent's `system_tool_settings` to change that for every call. Tell the model which menus it may navigate and where requested values come from. Do not ask it to guess menu options: ```text Follow the phone menu for account status. Press a key only after the menu states what that key does. Enter the account number supplied by the caller. ``` The platform removes unsupported characters and sends valid digits in order. If no valid digits remain or the call cannot send keypad input, the failure is returned to the agent so it can recover. ## `no_response` Keeps the agent silent while an automated system is still speaking or asks it to keep listening, and while a caller thinks aloud ("um, let me see") or turns to talk to someone else. When a caller asks the agent to wait while they do something ("one sec", "let me check", "can you hold on a second?") and gives it nothing else to answer, the agent says just "Sure." (not "Take your time") and waits for the caller to speak again. The agent's silence check-ins still run, and are always spoken, if the line stays quiet. It is not for a live person's greeting, a question, or a true hold queue. It is available by default and has no agent setting. If your prompt tells the agent what to say when a caller asks for a moment, the prompt wins. Usually the built-in description is enough. For IVR-heavy calls, reinforce the boundary in the prompt: ```text Stay silent while a phone menu is still speaking. Answer a live person as soon as they address you. ``` This action does not end the call. ## `wait_on_hold` Places an outbound agent into a silent hold state until a live person returns. The model uses it only after the other party explicitly says the agent is being put on hold. It is on by default for outbound calls; set `"wait_on_hold"` in the agent's `system_tool_settings` to turn it on or off for every call. ```text If a person explicitly puts you on hold, wait silently. When they return, continue from the question you were discussing. ``` Do not prompt the agent to use it for an IVR's "please wait" message. Hold lasts up to 15 minutes by default. For ordinary outbound calls, `POST /v1/call` accepts a positive-integer `max_hold_seconds`; inline Realtime restricts `max_hold_seconds` to 10–900 seconds. If the person returns, the agent resumes without changing the call's end reason. If the limit expires, the platform ends the call with `end_reason: "ai_hangup"`. ## `search_knowledge_base` Searches documents attached to the agent and returns relevant passages. Attach a knowledge base or individual knowledge documents to enable it, then tell the model which answers must come from those sources: ```text Search the knowledge base before answering policy questions. If the first search is inconclusive, try once more with different key terms. ``` If a search fails, the result is returned to the agent. See [Knowledge base](/guides/knowledge-base) for dashboard and API setup. ## `play_sound` Plays the agent's configured notification sound after any speech in the same turn. In the dashboard, set **Advanced → Tool sound** to **Chime**, **Ding**, or **Beep**, then deploy the agent. **None** removes the tool. With the [Agents API](/api-reference/agents), stage `play_sound_asset` as `"chime"`, `"ding"`, or `"beep"`, then deploy. Set it to `""` to disable the tool. ```text After confirming that an appointment was saved, say "You're all set" and play the notification sound. ``` A playback failure is returned to the agent and does not end the call. ## `send_email` Sends a plain-text email to the one recipient configured by the agent owner. The model supplies the subject and body. It never sees a recipient parameter and cannot choose or change the address. In the dashboard, enable **Advanced → Send email**, save a recipient address, then deploy the agent. With the [Agents API](/api-reference/agents), stage both `send_email_enabled: true` and a non-empty `send_email_recipient`, then deploy. Define the trigger and required contents in the prompt: ```text When the caller asks to leave a message, collect their name, callback number, and message. Read the details back for confirmation, then email them. ``` A call can attempt at most three emails. Each subject is limited to 300 characters and each body to 20,000 characters. Missing configuration, invalid content, the per-call limit, and delivery failures are returned to the agent. A failed delivery still uses one of the three attempts. Test with a controlled recipient before deploying broadly. Browser Talk and simulations send real email; there is no dry-run mode. ## `calculate` Evaluates an arithmetic expression exactly and returns the result, so the agent does not add up prices in its head. It is off by default. In the dashboard, enable **Advanced → Calculator**, then deploy the agent. With the [Agents API](/api-reference/agents), stage `calculator_enabled: true`, then deploy. The model writes the expression, such as `2*23.5 + 16.5 + 4`, and reads the result back to the caller. Expressions may use numbers, `+`, `-`, `*`, `/`, and parentheses. One call can carry up to 10 expressions separated by `;`, such as a subtotal and the total with a delivery fee, up to 500 characters in all. Results are rounded to 10 decimal places. The math is exact decimal arithmetic, so `0.1 + 0.2` returns `0.3`. Division by zero and anything else in the expression come back to the agent as an error to correct. It runs on ThunderPhone and answers instantly, with no filler line or typing sound. Turn it on for agents that quote order totals, fees, or change. No prompt change is needed. ## Compatibility note The **Advanced → Uninterruptible disclosures** setting and the legacy `speak_uninterruptible_enabled` field remain for compatibility, but enabling them has no effect. Leave the setting off; no prompt can activate this tool. For a required call-opening disclosure, use **Consent announcement**. It plays verbatim before ordinary conversation and cannot be interrupted. See [Call recording consent laws](/guides/recording-consent-laws). ## Test and monitor built-in tools Use browser Talk or a simulation to check prompting, knowledge search, sounds, and email. Those environments cannot reproduce phone-network behavior. Test keypad navigation and hold on an outbound phone call, and test a complete warm handoff on a real call using an eligible imported VoIP number. Realtime support is limited as shown in the table above. ## Calls, pricing, and webhooks Built-in actions have no per-use tool fee. Normal call and provider charges still apply; see [Pricing](/guides/pricing). Their calls and results can appear in the completed transcript, but they do not emit `telephony.tool` or `web.tool`. Those events are for [user-defined functions](/tools/overview). Only a completed hangup or transfer changes the call's `end_reason`; other failures are returned to the agent. See [Completion webhooks](/webhooks/call-complete) for the call record. --- title: "Transfer calls" description: "Save the numbers an agent may transfer to, then route callers with a cold transfer, or stay on the line and introduce them with a warm transfer." --- An agent transfers calls only to its **transfer numbers**: a list of names and phone numbers you save on the agent. The prompt refers to each number by name and says when to transfer and whether the handoff should be cold or warm. The agent sees only the names, never the numbers. An agent with no transfer numbers can't transfer calls. | Mode | What happens | Use it when | | --- | --- | --- | | **Cold** | The caller is sent directly to the destination. The agent does not introduce the caller or confirm that anyone will answer. | Fast routing matters more than an introduction, or the call's number can't place outbound calls. | | **Warm** | As soon as someone answers, the caller is connected, the agent introduces the caller on the line, and drops. You can instead have the agent [ask first or whisper](#before-connecting). | The destination should hear who is calling and why, or the caller should return to the agent when nobody answers. | If the agent omits the mode, the transfer is cold. ## Configure transfers In the dashboard: 1. Open **Voice Agents**, select the agent, and open **Config**. 2. Under **Transfer numbers**, add each destination with a name and a phone number, such as **Billing** and `+1 415 555 2671`. An agent can have up to 50 transfer numbers. 3. In the prompt, refer to each destination by its name and say when to use it and whether to use `cold` or `warm` mode, for example "transfer billing questions to Billing". You do not create a custom tool. 4. For warm transfers, choose what happens **Before connecting** and, optionally, what the **Briefing** should say. See [Before connecting](#before-connecting). 5. If needed, set **Advanced → Ring duration** from 5 to 120 seconds. 6. Select **Deploy**. With the API, `PATCH /v1/agents/{agent_id}` to stage `transfer_destinations`, the prompt, and the optional `warm_transfer_style`, `warm_transfer_briefing` and `ring_duration_seconds`, then `POST /v1/agents/{agent_id}/deploy`: ```json { "transfer_destinations": [ {"name": "Billing", "number": "+14155552671"}, {"name": "Front desk", "number": "+19375551234"} ], "warm_transfer_style": "introduce", "warm_transfer_briefing": "The caller's name and what they need." } ``` A write replaces the whole list, and `[]` removes every transfer number. See the [Agents API](/api-reference/agents#transfer-numbers) for the field rules and validation errors. ### Names and numbers - **Name:** 1 to 60 characters, unique on the agent ignoring case. This is the word the prompt uses. - **Number:** a full phone number. Numbers without a country code are read as US numbers, and every number is stored in E.164 format. Extensions and the fictional range 555-0100 through 555-0199 are refused. - **Variable number:** instead of a phone number, one [call variable](/guides/call-variables) on its own, such as `{{rep_phone}}`. It is filled from the call's variables when the call starts. If the variable is missing or isn't a valid number on that call, a transfer to that name is refused. - **SIP address:** a `sip:` or `sips:` address instead of a phone number, such as a contact center queue. See [Transfer to a SIP address](#transfer-to-a-sip-address). - **Return:** no number at all. The agent hands the call back to the contact center it came from. See [Return the call to your contact center](#return-the-call-to-your-contact-center). ### Transfer to a SIP address A transfer number can be a SIP address, such as `sip:support@pbx.example.com`, so the agent can hand callers back to your contact center or PBX. The transfer is a cold transfer: ThunderPhone sends a SIP `REFER` with that address, and your system takes the call from there. A SIP address works only when the call came from your own SIP system: - calls to a [SIP address](/guides/sip-addresses) (per-call URI or SIP endpoint), and - calls on a number you connected through a manual SIP connection ([your own SIP trunk](/guides/voip-providers)). On any other call, including calls on ThunderPhone numbers and on numbers connected through Twilio or Telnyx, a transfer to a SIP address is refused: the agent is told it can't transfer there and keeps talking to the caller. A warm transfer to a SIP address runs as a cold transfer. The address must be written out in full; it can't be a `{{variable}}`. It is at most 255 characters, needs a host, and can't contain spaces, quotes, commas or angle brackets. Parameters such as `;transport=tls` are fine. The one header the address itself can carry is `?User-to-User=`, for platforms that read user-to-user information from the transfer address: `sip:queue@sbc.example.com?User-to-User=00a1b2;encoding=hex`. #### SIP headers on the transfer Any transfer number can also list `headers` to send with the `REFER` of a cold transfer, for example the caller's account ID for your contact center to look up: ```json { "name": "Support queue", "number": "sip:support@pbx.example.com", "headers": { "X-Account-Id": "{{account_id}}", "User-to-User": "{{uui}}" } } ``` - Up to 10 headers. Names start with `X-` or are `User-to-User`. `X-TP-` names are reserved for ThunderPhone. - Values are at most 256 bytes and can use [call variables](/guides/call-variables), filled when the call starts. A missing variable is left empty, line breaks are removed, and a header whose filled-in value is over 256 bytes is not sent. - Headers go only on a cold transfer's `REFER`, and only reach your system when the carrier passes them on. They are not sent on a warm transfer's new call or when ThunderPhone has to connect a refused transfer itself. #### Headers inside the transfer address Some contact centers read context only from the transfer address (the `Refer-To` of the `REFER`), not from separate headers. Twilio, Genesys Cloud and Talkdesk work this way, and most of them expect `User-to-User` in hex. Set two options on the SIP address entry: ```json { "name": "Escalation", "number": "sip:escalation@talkdesk.example.com", "headers": { "User-to-User": "autopilot_result={{handoff_summary}}", "X-Call-ID": "{{handoff_id}}" }, "headers_in_uri": true, "uui_encoding": "hex" } ``` - `headers_in_uri: true` sends the headers inside the address instead of on the `REFER`: `sip:escalation@talkdesk.example.com?User-to-User=…&X-Call-ID=…`. Values are escaped for a SIP address, so `;` becomes `%3B` and a space `%20`. - The whole address is kept to 254 characters, inside Twilio's limit of 256 with the angle brackets. `User-to-User` goes first, then the other headers in alphabetical order. A long `User-to-User` is cut to fit rather than left out, so put the part your platform needs most at its start. Another header that doesn't fit beside it is left out whole, and the transfer still goes ahead. - `uui_encoding: "hex"` sends `User-to-User` as hex with `;encoding=hex`. Write the value as plain text: it is encoded at the transfer, after variables and placeholders are filled. Text that would make the encoded value longer than 256 bytes is cut first. It also works on a [return](#return-the-call-to-your-contact-center). - `headers_in_uri` can't be used with a `?User-to-User=` written into the address itself; move that value into `headers` instead. Add SIP addresses and headers with the [Agents API](/api-reference/agents#transfer-numbers) (`transfer_destinations`), or in an inline call config. ### Return the call to your contact center When your contact center sent the call, the agent can hand it back instead of transferring it: it says goodbye, ThunderPhone hangs up, and the SIP `BYE` it sends carries routing data your platform reads to decide where the caller goes next. Add a transfer number with `"action": "return"` and no number: ```json { "name": "Contact center", "action": "return", "headers": { "X-RouteType": "queue", "X-RouteValue": "{{queue_id}}", "User-to-User": "{{uui}}" } } ``` - The prompt names it like any other transfer number, for example "when the caller asks for a person, transfer to Contact center". - The `BYE` can carry `X-RouteType`, `X-RouteValue`, `X-RouteReason`, `X-CustomMetaData` (the four Five9 reads) and `User-to-User`, each optional. Values follow the rules for [SIP headers on the transfer](#sip-headers-on-the-transfer): at most 256 bytes, call variables allowed, line breaks removed. The `BYE` also carries `X-TP-Handoff-Id` (see [Pass context to your contact center](#pass-context-to-your-contact-center)). A return without headers otherwise just hangs up, and your platform handles the call as after any hangup. - It works on the same calls as a transfer to a SIP address: calls to a [SIP address](/guides/sip-addresses) and calls on a number on a manual SIP connection. Other calls don't offer it to the agent, and Browser Talk and simulations refuse it. A warm transfer to it runs as a plain return. - Nothing is dialed and no `REFER` is sent. The call ends with the end reason `returned_to_platform`. - The headers ride on your SIP trunk's settings. SIP addresses and manual SIP connections set up before this option existed need a one-time update from ThunderPhone; ask support before you rely on the headers. Add a return with the [Agents API](/api-reference/agents#transfer-numbers) (`transfer_destinations`), or in an inline call config. The dashboard keeps one you added, and its headers, when you edit the other transfer numbers. ### Pass context to your contact center On a call that can reach your contact center (a transfer to a SIP address, or a return), the agent can say what the caller needs, so the person or flow that takes the call doesn't start from scratch. Use three placeholders in that entry's `headers`: | Placeholder | What it holds | |---|---| | `{{handoff_summary}}` | What the caller needs and what the agent already did, written by the agent at the transfer. At most 300 characters. | | `{{handoff_intent}}` | A short label the agent picks, like `billing_dispute`. At most 60 characters. | | `{{handoff_id}}` | An id for this transfer, for the lookup below. | ```json { "name": "Support queue", "number": "sip:support@pbx.example.com", "headers": { "X-Summary": "{{handoff_summary}}", "X-Intent": "{{handoff_intent|general}}", "X-Handoff-Id": "{{handoff_id}}" } } ``` - They are filled at the moment of the transfer, not when the call starts. A value the agent left out is empty, or the `|default` you wrote. - Line breaks are removed, and a header whose filled-in value is over 256 bytes is cut to 256 bytes. The transfer goes ahead either way. - A return always sends the id as `X-TP-Handoff-Id` on the `BYE`, whatever its headers say. - On a phone number entry, or on a call that can't reach your contact center, they are ordinary call variables: empty unless the call has variables of those names. Other calls never see the extra fields, so their agents behave as before. To get the full context, call [`GET /v1/calls/handoff`](/api-reference/calls#look-up-a-handed-off-call) with your organization's API key and either the id or your platform's own id for the call (`external_call_id`, from the call-id header it [sent with the call](/guides/sip-addresses#where-they-show-up)). It returns the call's id and status, the summary and intent, the SIP headers and variables the call started with, and once the call has ended, its summary and extracted data. ### Which numbers a call can reach What a transfer can reach depends on which line carries it. A transfer carried on ThunderPhone's line can reach US numbers only, excluding premium-rate and other special-service numbers. A transfer carried by [your own carrier](/guides/voip-providers) can reach any number you saved. | Transfer | Carried by | | --- | --- | | Cold transfer on an inbound call | The carrier the call arrived on: ThunderPhone's line for a ThunderPhone number, your carrier for a number on your own carrier. | | Cold transfer on an outbound call | The line the call was placed on. | | Warm transfer (the new leg to the target) | The line the new leg is dialed on. | A number on your own carrier places outbound calls and warm legs only through its connection's own outbound trunk: manual SIP connections with an outbound trunk, Twilio connections with outbound calling set up, and Telnyx connections. Without that trunk the number can't dial out at all, so it supports cold transfer only. Those calls never fall back to ThunderPhone's line. If your organization has no numbers on its own carrier, saving a number that ThunderPhone's line can't reach fails with an error. Once you connect your own carrier, those numbers save and are marked as working only on transfers your carrier carries (`platform_ok: false` in the API). One agent can answer on both kinds of number, so the check runs on each call. ## Before connecting Each agent chooses what happens when the person at the destination answers a warm transfer. The setting applies to every warm transfer the agent makes. | Setting | API value | What happens | Caller's wait | | --- | --- | --- | --- | | **Introduce on the line** (default) | `introduce` | The caller is connected as soon as the destination answers and hears its greeting. When the person who answered has finished, the agent introduces the caller in one or two sentences that both of them hear, then drops. Nobody is asked anything. | Ringing only. | | **Ask first** | `ask_first` | The agent privately briefs the person who answers and asks whether they can take the call. They can ask questions, accept, or decline. If they accept, the caller is connected, the agent introduces the caller on the line, and drops. | Ringing plus the private conversation, often 15 seconds or more. | | **Whisper** | `whisper` | The agent privately tells the person who answers who is calling and why, in one short sentence. Nothing is asked. Then the caller is connected and the agent drops without saying anything on the line. | Ringing plus one private sentence. | Use **Introduce on the line** unless the person answering needs to be able to turn calls away or should hear about the caller before the caller is on the line. **Ask first** lets them decline, but keeps the caller on hold longer, and voicemail, silence or a decline all send the caller back to the agent. **Whisper** gives them the context privately without asking; voicemail sends the caller back to the agent. ### The briefing The **Briefing** setting shapes what the agent tells the person who answers about the caller. With **Introduce on the line**, that is the introduction both of them hear once they are connected. By default it is very short: who is calling and why, such as "Hi, I have Alex on the line about an order. Alex, go ahead." With **Ask first**, it is the one private sentence before the caller is connected, ending with whether they can take the call; the introduction after they accept follows the same idea. With **Whisper**, it is the one private sentence before the caller is connected, such as "I have Alex calling about a late order, putting them through now." It is a statement, not a question. To change what it says, write a **Briefing** instruction on the agent (`warm_transfer_briefing`, up to 300 characters), for example "The caller's name, their account number, and what they need." The agent follows it when it writes each introduction or briefing. Keep it to the few facts that matter: an introduction or **Whisper** sentence over about 40 words, or an **Ask first** briefing over about 25 words, is replaced by a short standard line. Leave it blank for the default. ## What happens during a warm transfer With **Introduce on the line** (the default): 1. The agent tells the caller it is starting the transfer. The call recording stops, and ThunderPhone starts calling the destination from the number handling the call right away, while the agent is still speaking. 2. While the destination rings, the caller waits briefly in silence. There is no hold music in this style. 3. As soon as the destination answers, the caller is already on the line, so the caller hears the greeting from its first word. 4. When the person who answered has finished and is waiting for a reply, the agent introduces the caller in one or two sentences that both of them hear, such as "Hi, I have Alex on the line about an order. Alex, go ahead." The agent judges this from what they said, not from a pause: a recording notice or "please hold" is not the end of their turn, "How can I help you?" is. If they say nothing, the agent starts after about 3 seconds. If they start talking over the introduction, the agent stops, lets them finish and says it again. After about 15 seconds, for example a long phone menu, the agent introduces the caller anyway. A missing or overly long introduction is replaced by a short standard line, spoken in the call's language. 5. The agent drops automatically; nothing more is said. Whoever answers is connected. If it is a voicemail greeting, the agent says nothing into it and drops, so the caller can leave a message. If the destination does not answer, is busy, the call cannot be placed, or the person who answered hangs up before the introduction ends, ThunderPhone ends that call and the caller returns to the agent. The agent receives the outcome and continues the original conversation. It can take a message, suggest another route, or make a cold transfer if the caller still wants one. With **Introduce on the line**, the recording ends when a warm transfer starts, as it does for a cold transfer. Nothing the destination says is recorded or transcribed (ThunderPhone only listens, until the introduction, to tell when it is speaking and when it has finished), so it does not hear the agent's consent announcement. If the transfer fails and the caller returns to the agent, the rest of the call is not recorded either; its transcript continues. The [call object](/api-reference/calls#call-object) and the [completion webhook](/webhooks/call-complete) say when this happened: `recording_stopped_at` is the moment the recording stopped and `recording_stopped_reason` is `warm_transfer`. ### With Ask first 1. The agent tells the caller it is starting the transfer, then the caller hears hold music while the destination rings. 2. When the destination answers, the agent lets the person who answers finish their greeting first, judged the same way as above. If the agent's consent announcement is on, the person who answers hears it first. Then the agent speaks its briefing and asks whether they can take the call. The caller, on hold, hears none of it. 3. The person can ask questions, which the agent answers from the conversation so far. If they accept, the caller is connected, the agent introduces the caller in one or two sentences that both of them hear, and drops. 4. If they decline, don't reply, or the call reaches voicemail, ThunderPhone ends that call and takes the caller off hold. The agent gets the outcome, including any message the person asked it to pass on, and continues with the caller. In this style the transfer does not stop the call recording. If the agent records calls, the private conversation with the person who answers and the connected call are both recorded. If call recording is off for the agent, neither is. ### With Whisper 1. The agent tells the caller it is starting the transfer, then the caller hears hold music while the destination rings. 2. When the destination answers, the agent waits for the person who answers to finish their greeting. If the agent's consent announcement is on, the person who answers hears it first. Then the agent tells them, in one short sentence, who is calling and why. It does not ask whether they can take the call. The caller, on hold, hears none of it. 3. The caller is connected and the agent drops. Nothing is said on the line. 4. If the call reaches voicemail, the agent hangs up on it and takes the caller off hold. If the destination does not answer, is busy, or the call cannot be placed, the caller also returns to the agent. The agent gets the outcome and continues with the caller. Recording works as it does for **Ask first**: if the agent records calls, the private sentence and the connected call are both recorded. ## Prompt the agent to transfer well Put the routing rules in the agent prompt and name each destination exactly as it appears under **Transfer numbers**. Do not write phone numbers in the prompt; the agent transfers by name. Specify the transfer mode for each route and give the agent a fallback for failed warm transfers. ```text Transfer policy Confirm the caller's name and reason for calling before any transfer. | Caller needs | Destination | Transfer mode | | --- | --- | --- | | A new purchase or plan change | Sales | warm | | Help with an existing order | Order support | warm | | The main office | Front desk | cold | For a warm transfer: - Keep the introduction to one or two short sentences: who is calling and why. - Do not plan to speak after the introduction because you will be dropped. - If the target does not answer or is busy, tell the caller what happened, then offer to take a message or try another route. Do not cold-transfer unless the caller asks. ``` The agent supplies these fields when it calls the built-in action: | Field | When used | Purpose | | --- | --- | --- | | `destination` | Always | The name of one of the agent's transfer numbers. | | `mode` | Optional | `cold` or `warm`; defaults to `cold`. | | `screen_message` | Required for warm | With **Introduce on the line**, the [introduction](#the-briefing) the caller and the person who answers both hear once connected. With **Ask first**, the private briefing the person who answers hears before deciding. With **Whisper**, the private sentence the person who answers hears before the caller is connected. The caller does not hear either private line. | | `introduction` | Optional for warm | With **Ask first**, the introduction both hear once the person accepts. With **Introduce on the line**, used only when `screen_message` is empty. Not offered with **Whisper**, since nothing is said on the line. | You do not need to define or attach a custom tool named `transfer_call`. The action's description follows the agent's **Before connecting** and **Briefing** settings, so the prompt does not need to repeat them. ## When a transfer is refused ThunderPhone checks every transfer before placing it. It refuses a transfer when the name isn't one of the agent's transfer numbers, when a variable number has no valid value on this call, when a transfer carried on ThunderPhone's line would reach a number outside the US or a premium-rate number, or when the destination is a SIP address or a return and the call didn't come from your own SIP system (see [Transfer to a SIP address](#transfer-to-a-sip-address)). A refused transfer does not end the call. The agent does not announce the transfer, receives the reason (including the list of valid names when the name didn't match), and keeps talking to the caller. Tell it in the prompt what to do instead, such as taking a message. The refusal appears as a tool error in the call's history. ## Whisper transfers “Whisper transfer” is a common industry name for a warm transfer where the destination hears private context before the caller is connected, without being asked whether to take the call. That is the **Whisper** setting under [Before connecting](#before-connecting). This is different from [`POST /v1/calls/{id}/whisper`](/guides/monitor-live-calls#whisper-to-the-agent). That endpoint lets a staff member or owner send private text guidance to the AI agent during a live call. It does not call or brief a transfer destination. ## Requirements, limits, and testing | Question | Answer | | --- | --- | | Which destinations can a transfer reach? | On a ThunderPhone-provisioned number, only US numbers are supported, toll-free included; Canada, other +1 countries, and premium-rate numbers are not. ThunderPhone refuses any other destination before dialing: the caller stays on the line and the agent gets the reason and keeps talking (see [When a transfer is refused](#when-a-transfer-is-refused)). To transfer to other countries, [connect your own carrier](/guides/voip-providers) and take the call on a number imported through it. The transfer then goes out through your carrier, so the carrier must accept call transfers and allow calls to that country. On a Twilio SIP trunk, turn on **Call Transfers** and **Enable call transfers to the PSTN**. | | Which numbers support warm transfer? | A number that can place outbound calls: a verified number imported through a VoIP connection with outbound calling, or a ThunderPhone-provisioned number once your organization's [outbound calling request](/guides/get-a-phone-number) is approved. Other ThunderPhone numbers support cold transfer only. If warm mode is unavailable, the action falls back to cold transfer. [Connect a VoIP provider](/guides/voip-providers). | | Which call types support it? | Cold transfer is available on ordinary phone calls. Browser Talk, widgets, simulations, and test calls cannot test a warm handoff. Saved-agent Realtime omits `transfer_call`; inline Realtime with `call_events` can request cold transfer. | | What about calls without a saved agent? | An [outbound call](/api-reference/outbound-calls) or [`call.incoming`](/webhooks/call-incoming#response-schema) response with an inline `config`, and an inline [Realtime](/api-reference/realtime#call-events) session, accept the same `transfer_destinations` list. With a list, the call transfers only to those names. Without one, the agent keeps a transfer action that takes a phone number, and calls on ThunderPhone numbers can still reach only US numbers. | | How do I test it? | Check routing language in Browser Talk or a simulation: you see which name the agent picks, or the refusal. Both always refuse a transfer to a SIP address or a return, since they don't come from your SIP system; that refusal doesn't mean the entry is misconfigured. Then test the complete handoff with a real phone call to the agent's number. Any ThunderPhone or imported number works for a cold transfer; a warm transfer needs a ThunderPhone number whose outbound calling request is approved, or an imported number with outbound calling. | | How long does the destination ring? | `ring_duration_seconds` accepts 5–120 seconds. `null` uses 45 seconds for warm transfer and the carrier's default for cold transfer. Ordinary outbound calls default to 60 seconds and never ring for less than 14 seconds. The setting does not change inbound ringing. | | What if my carrier refuses a cold transfer? | Normally your carrier hands the call over to the destination. Some carrier connections refuse transfers, for example a Twilio trunk without call transfer (SIP REFER) enabled. When the call's number can place outbound calls, ThunderPhone then connects the call itself: it dials the destination, the caller hears ringing until it answers, and the agent leaves without an introduction. Recording stops at the transfer. If nobody answers, or the number cannot place outbound calls, the call ends. A refused transfer to a SIP address always ends the call. Your organization's admins get an email the first time a connection refuses a transfer. When you connect a Twilio account in the dashboard, **Allow call transfers** (on by default) turns transfers on for its SIP trunk. For a Twilio connection you already have, use **Turn on call transfers** on the VoIP connections page. Other carriers have the setting in their own portal. | | How long can the human bridge last? | Up to 4 hours after the agent connects the two people. With **Introduce on the line** the bridge is not recorded: the recording stops when the transfer starts. With **Ask first** and **Whisper** it is recorded when the agent records calls. | | What happens on failure? | In a warm transfer, if the target does not answer, is busy, or the outbound leg fails, the caller returns to the agent with the result. The prompt should define the next step. A cold transfer has already ended the agent's part of the call, so a failed cold transfer ends the call. | | What does it cost? | There is no transfer-tool fee. Normal call charges include the warm-transfer flow until the agent leaves. After the AI leaves a warm transfer, ThunderPhone keeps the two people connected and bills that time at a per-minute bridge rate; see [Pricing](/guides/pricing). A cold transfer stops metering at the handoff. When ThunderPhone connects a refused cold transfer itself, the connected time is billed at the same bridge rate. On imported VoIP numbers, your carrier can continue billing the transfer leg while the two people remain connected. | ## Webhooks and end reasons The completion payload records the destination in `transfer_number` and uses one of these end reasons: | `end_reason` | Meaning | | --- | --- | | `ai_transfer` | The agent initiated a cold transfer. | | `ai_warm_transfer` | The destination answered a warm transfer, the parties were connected, and the agent dropped. | | `returned_to_platform` | The agent [returned the call to your contact center](#return-the-call-to-your-contact-center). `transfer_number` is not set. | An unanswered, busy, or failed warm attempt is not a completed transfer. The caller returns to the agent, so the eventual end reason reflects what ends the original call. For a successful warm transfer, the completion event and the AI call's end reason are recorded when the AI leaves, right after the parties are connected. With **Introduce on the line**, the recording covers the call up to the transfer and ends there (`recording_stopped_at`); the conversation between the two people is neither recorded nor transcribed. With **Ask first** and **Whisper**, the recording (when the agent records calls) continues until the connected call ends. A legacy single-URL webhook can delay a cold transfer for up to five minutes by returning `{"transfer_ready": false}` and later updating the call's transfer-ready state. Webhook endpoint deliveries and warm transfers do not use this coordination. See [Completion webhooks](/webhooks/call-complete) for the complete payload and legacy behavior. ## Troubleshooting ### The call drops when the agent transfers Check the destination's country. Calls on ThunderPhone numbers support transfers to US numbers only. The carrier can reject a transfer to another country, which disconnects the caller, and the call still ends with `ai_transfer`. To reach other countries, [connect your own carrier](/guides/voip-providers) and route the call through a number imported from it. The carrier must accept call transfers and allow calls to that country. On a Twilio SIP trunk, turn on **Call Transfers** and **Enable call transfers to the PSTN**. ### The agent makes a cold transfer instead of a warm one Check that the call is using a number that can place outbound calls: a verified, imported VoIP number with working outbound routing, or a ThunderPhone number with approved outbound calling. Warm mode is removed from the tool options for other numbers and for non-phone sessions. Also make the mode explicit in the prompt's routing table. ### The target never answers Confirm that your VoIP trunk can place outbound calls to the destination and that its authentication and routing rules are current. Check the number saved under **Transfer numbers**. Increase `ring_duration_seconds` if 45 seconds is too short for a warm transfer. ### The target reaches voicemail With **Introduce on the line**, the caller is connected to whatever answers, including a voicemail greeting. The agent recognizes the greeting, says nothing into the mailbox and drops, and the caller can leave a message. With **Ask first** and **Whisper**, the agent hangs up on the voicemail and the caller returns to the agent. If a destination often goes to voicemail, give the agent another route for it in the prompt. ### A cold transfer waits before connecting If you use the legacy single-URL webhook, check whether it returned `{"transfer_ready": false}`. The caller hears hold music while ThunderPhone waits. Update the call's transfer-ready state when the destination is prepared. ### The agent says it can't transfer Check that the agent has transfer numbers and that the prompt uses the same names. An agent with no transfer numbers has no transfer action at all. If the transfer rides ThunderPhone's line (see [Which numbers a call can reach](#which-numbers-a-call-can-reach)), the destination must be a US number that is not premium-rate. For a variable number, check that the call's variables include a valid phone number for it. ### Deploy fails with a transfer number error Every deploy re-checks the saved transfer numbers. A non-US number saved while your organization had a number on its own carrier fails the check once that organization no longer has one. Remove the number, or connect your own carrier again. ### Staff whispering does not start a transfer Live-call whispering only coaches the AI. Add transfer instructions to the agent prompt and let it call `transfer_call`. See [Monitor live calls](/guides/monitor-live-calls). --- title: "Voice library, previews, and custom voices" description: "Search and preview ThunderPhone voices, generate spoken samples, request custom voice access, create a voice from audio, and manage voice pricing and status." --- Open **Voices** (`/dashboard/voices`) to explore voices for your agents and, when enabled for your organization, create custom voices from a short audio sample. ## Browse the voice library Use search plus the **Language** and **Gender** filters to narrow the catalog. Each row shows the voice name, supported language information, a built-in audio sample, and whether it is a premium voice. The agent builder filters this same catalog to voices compatible with the agent's primary and additional languages. Selecting a voice in the library does not change an agent; open the agent's **Config** tab to assign it. ## ElevenLabs voices The library also lists voices from ElevenLabs under ElevenLabs' own names. They run on ElevenLabs' **Eleven v4 Turbo** model and speak every language in the [language catalog](/guides/supported-languages), so an agent's languages never rule one out. Each one plays ElevenLabs' own sample; generating a sample of your own text (below) isn't available for them yet. - An ElevenLabs voice is billed at ElevenLabs' list price, plus the markup, in every language. That costs more per minute than a default Inworld voice. - ElevenLabs voices are **not HIPAA eligible yet**. An agent that processes PHI can't select one, and an agent that uses one can't be switched to processing PHI. If you need them on PHI calls, ask us to enable HIPAA for them. - If an agent's calls become PHI calls some other way (for example, a BAA is recorded for your organization), those calls use a standard voice for the call language instead and carry no ElevenLabs charge. - In the API an ElevenLabs voice is `elevenlabs:`. `GET /v1/voices` lists them in its `elevenlabs` block; see the [Voices API](/api-reference/voices). ## Hear a voice say your text Choose **Hear _voice_ say your words**, enter **1–500 characters**, select a supported language, and generate a sample. - Each new sample costs **3¢** from the organization balance. - The confirmation dialog shows the charge before generation. - An identical cached sample is served free and marked **Served from cache**. - Each organization can create up to **30 samples per hour**. - A voice can be previewed only in languages available for that voice. The [Voice samples API](/api-reference/voice-samples) exposes the same workflow programmatically. ## Request custom voice access Custom voices are enabled per organization. If access is not enabled, choose **Request access**, enter the number of voice slots you need, and optionally describe the use case in up to **2,000 characters**. Only one request can be pending at a time. After access is enabled, the page shows the number of used and available custom-voice slots. Use **Request more slots** when you need a higher quota. ## Create a custom voice Confirm that you have the rights to use and clone the voice. A custom voice cannot be created without this confirmation. Record up to 15 seconds in the browser, or upload a **WAV** or **MP3** file up to **4 MB**. Use one speaker, a steady pace, and as little background noise as possible. Enter a name of 1–100 characters, choose its language, and optionally mark the voice as female or male. Select **Create voice**. Creation is asynchronous. A voice moves through **Creating**, **Ready**, or **Failed**. Ready voices can be played and assigned to agents; a failed voice can be retried with a new sample. The custom-voice language picker contains the languages supported for custom voice creation. It may be narrower than the complete [voice-agent language catalog](/guides/supported-languages). ## Rename, retry, and delete - **Rename** changes the label shown throughout the dashboard without changing the voice id. - **Try again** starts a new creation attempt after a failure. - **Delete** removes the voice from your organization's picker and frees its quota slot. Agents already using it keep their saved configuration until you re-save them with another voice. For the complete object, status, quota-request, preview, rename, and deletion contracts, see the [Custom voices API](/api-reference/custom-voices). ## Pricing The voice is one part of a call's price: its characters are billed at the voice provider's list price, plus the markup. There is no flat voice or language surcharge. Premium voices from Cartesia and ElevenLabs cost more per minute than the default Inworld voices, because their list prices are higher. With your own Cartesia key, Cartesia bills you for the voice and ThunderPhone charges nothing for it. Organization-specific custom-voice pricing, when applicable, is returned by the custom voice policy. See [pricing](/guides/pricing). --- title: "Add funds and turn on auto top-up" description: "Pay-as-you-go billing: add a card, load your balance, and set auto top-up so a call never dies mid-sentence." --- Every new account includes **$10 of free call credit** in its first organization, with no credit card required. Organizations you add later start without it. The credit pays for any call: browser tests, phone calls, SIP calls, web and widget calls, and simulations. Calls use the credit first, then your cash balance. It cannot pay for phone numbers or voice samples, so add funds with a card when you want a number. ThunderPhone billing is pay-as-you-go: each call draws from your organization's balance what its parts cost at our providers' list prices, plus an infrastructure charge and a markup. No subscriptions, no seats. Everything on this page lives under **Organization → Billing Settings**. ## 1. Add a card A card unlocks top-ups and phone numbers. It's only charged when you add funds — **never automatically** (unless you later opt into auto top-up). ## 2. Load your balance Click one of the preset amounts ($25–$1000) or enter your own, then **Add to balance** — a confirmation dialog shows exactly what you're about to charge before you click **Confirm and charge**. Add at least **$3.00** to start making real calls. Funds never expire. What a minute costs depends on the agent's setup: its intelligence, hearing, voice, prompt size and phone line. Zeus shows what a minute of each agent costs before you go live; see [pricing](/guides/pricing). Running [outbound campaigns](/guides/outbound-campaigns)? Every campaign call draws from this same balance, so size your top-ups (or auto top-up) to the list you're dialing. ## 3. Turn on auto top-up (optional, recommended) When the balance is about to run out mid-call, the agent is told to apologize, wrap up, and say goodbye, and the call ends once it is spent. Auto top-up prevents that from ever happening: Flip **Enable auto top-up**. Choose **When balance falls below** (the threshold) and **Add amount** (the refill). When the balance dips under the threshold, ThunderPhone charges your card for the refill amount. Check **Monthly spending limit** to bound how much auto top-up can spend per calendar month. Click **Save changes**. ## Prompt size Longer prompts cost more per minute, because the models read the prompt on every reply. That includes page `context` supplied by an embedded [web widget](/guides/embed-web-widget), which is capped at 12,000 characters (roughly 3,400 tokens of typical English text), and large tool results. The prompt editor in the dashboard shows a live token estimate while you edit. The complete pricing reference, with the parts of a call, typical prices, price targets and hard caps, simulations and phone numbers, is at [ThunderPhone pricing](/guides/pricing). ## Watch the money The **Billing History** tab lists every transaction — top-ups (manual and automatic), per-call usage, and monthly phone-number charges. The same data is available programmatically via the [Billing API](/api-reference/billing). --- ## Next steps With funds loaded, numbers are a click away. Balance, top-ups, auto-reload, and transactions over REST. How calls are priced, typical prices, and one-time charges. --- title: "Get a phone number (from ThunderPhone or your own)" description: "Get a ThunderPhone number in seconds, or connect your VoIP provider to use numbers you already own." --- Numbers are how callers reach your agent. On **Phone Numbers** (`/dashboard/phone-numbers`), click **Add number** to see your two options: - **ThunderPhone number** — a real U.S. number from ThunderPhone's pool, live in seconds. It costs **{/* rate:phone */}$1/month{/* /rate */}**, its call minutes are billed as the phone line, and it starts as **inbound-only**. You can request permission to use it for outbound calls. - **Connect VoIP provider** — import numbers you already own, or provision new ones, through your own provider. These numbers support outbound calls and campaigns without requesting ThunderPhone-number access. ## The ThunderPhone path In the Add-number chooser, pick **ThunderPhone number**. Select the **agent that answers this number** and (optionally) a preferred area code. Click **Get number**. The number appears in your table and is ready to take calls within seconds. Provisioning auto-routes the number to your agent — the **Inbound** select on the number's row shows who answers, and you can change it anytime. ThunderPhone numbers start as inbound only. To request outbound access, select **Request outbound calling** on the Phone Numbers page and tell us how many outbound calls per day you need, your use case, how you obtain consent, and your website. Once approved, you can request a higher daily limit from the same page. Numbers from your own VoIP provider have no daily limit. We review each request by hand and show the result on the page. You can still connect your own carrier for outbound calling without this permission. An organization can hold 25 ThunderPhone numbers by default; request a higher limit from the same page if you need more. ## The VoIP path **Connect VoIP provider** takes you to **Connections → VoIP** — the full flow is covered in [Connect a VoIP provider](/guides/voip-providers). In short, two routes: - **Connect a provider you already use**: paste an API key for **Twilio** or **Telnyx**, or pick **SignalWire**, **Vonage**, **Plivo** or **Other provider** to fill in SIP trunk details, with provider-specific steps for the named ones and a **Generate AI Setup Guide** button for any trunk. Both end with a connection test. - **Set up a new Telnyx account**: a step-by-step flow that walks you through creating a Telnyx account, generating an API key, and connecting, with a **Connect & Test** check at the end. Once connected, import the numbers you want; imported numbers support inbound **and outbound**. The programmatic version of this flow is [Bring your own numbers (API)](/guides/bring-your-own-numbers). ## Manage your numbers Each row in the numbers table gives you: - **Copy** the number, and a **label** field to name it. - **Routing mode** and the **Inbound** agent select. - An **actions** menu (⋯) for everything else, including release. ### Label phone numbers In **Phone Numbers**, select **Add label** beside a number, enter 1–100 characters, and save. Use **Edit label** or **Remove label** to change it later. Labels are shared across your organization and can also identify external caller or destination numbers from call history. They appear in call details and under **Exclude numbers from analysis** in AI reports. Developers can manage the same E.164-based labels with the [Phone Number Labels API](/api-reference/phone-number-labels). ### Caller name and spam registration Your ThunderPhone numbers show your organization's caller name automatically. Numbers in your own Twilio or Telnyx account can be registered for a caller name, verified caller ID and the carriers' spam filters from the **Caller ID and spam registration** section. See [Caller ID and spam registration](/guides/caller-id-and-spam-registration). ## Hear it for real Dial the number from your own phone and talk to your agent. Or run a [simulation](/guides/simulate-a-call) and let an AI caller do it. --- ## Next steps An AI caller rings your new number. The VoIP connect-import-verify flow over REST. Provision and assign programmatically. Use an outbound-enabled customer VoIP or ThunderPhone number at scale. Show your business name and register numbers so more people answer. --- title: "Caller ID and spam registration" description: "Show your business name on calls and register your numbers with the carriers' spam filters, so more people pick up." --- Phone carriers decide whether your call shows as your business, as a plain number, or as "Spam Likely". Registering your numbers with the carriers' spam filters and showing your business name makes people more likely to answer. Everything on this page is free. Find it on **Phone Numbers** (`/dashboard/phone-numbers`), in the **Caller ID and spam registration** section under your numbers. Each number shows three things: | Column | What it means | |---|---| | **Caller name** | The business name people see when you call (CNAM). | | **Verified caller ID** | Carriers check that the call really comes from your number (SHAKEN/STIR). Unverified calls are more often labeled spam. | | **Spam-filter registration** | The number is registered with the spam filters the major US carriers use, so they know who is calling. | ## ThunderPhone numbers: done for you - **Caller name.** A new ThunderPhone number gets your organization's caller name when you buy it. Saving the caller name applies it to every ThunderPhone number you have. - **Verified caller ID.** Calls from ThunderPhone numbers are verified by our carrier automatically. - **Spam-filter registration** is not offered for ThunderPhone numbers yet; the page shows **Not offered yet**. ### Set your caller name The caller name starts as your organization's name, cut to fit. If your organization still has the name it was created with ("Jane's Team"), there is no caller name until you set one. Only organization admins can change it, on **Phone Numbers** or in **Organization Settings**. On **Phone Numbers**, click **Edit caller name**, or use **Edit** in the **Caller name** card in **Organization Settings**. Up to 15 characters: letters, numbers and spaces, starting with a letter. Leave it blank to use your organization's name. The dialog shows what calls will display. ThunderPhone sends the name to the carriers for every ThunderPhone number. Turn off **Show a caller name** to stop showing one. Carriers take 1 to 3 days to show a new name. Landlines show caller names reliably; many mobile phones show them only sometimes, or show their own label instead. ## Numbers in your own Twilio or Telnyx account Registering a number in your own carrier account changes that account, so ThunderPhone only does it when an organization admin asks, one number or a few at a time. Click **Register** in the column you want, choose the numbers, and confirm. ThunderPhone uses the credentials saved on your [VoIP connection](/guides/voip-providers). | | Telnyx | Twilio | |---|---|---| | Caller name | Yes, set with your API key. **Turn off** removes it again. | Yes, a CNAM registration in Twilio's Trust Hub. | | Verified caller ID | Managed in your Telnyx account. | Yes: the number joins a SHAKEN/STIR registration, so Twilio verifies your calls at the highest level (A instead of B). | | Spam-filter registration | Managed in your Telnyx account. | Yes, through Twilio Voice Integrity. You tell us what the calls are for, your employee count and your daily call volume. | Caller names are for US local numbers only: carriers don't list them on toll-free or non-US numbers, so those numbers show no **Register** button for a caller name. For SignalWire, Vonage and other SIP carriers, register in your carrier's own console. ### Before you register Twilio numbers Twilio needs an approved **Primary Business Profile** in its Trust Hub first. If your account has none, ThunderPhone says so and links to the [Twilio Trust Hub](https://console.twilio.com/us1/account/trust-hub/overview). Create the profile there; Twilio usually reviews it within 1 to 3 days. Then click **Register** again. When you register, ThunderPhone adds each number to your Business Profile, reuses your account's existing registration of that kind or creates one, adds the numbers to it and submits it for review. Registering again never creates a duplicate. When your account already has a spam-filter registration, new numbers join it, and the answers you give now (what the calls are for, employee count, daily calls) are not used; change them in Twilio's Trust Hub. ### Statuses | Status | Meaning | |---|---| | **Sending** | ThunderPhone is sending it to your carrier. | | **Submitted** | The carrier has it and is about to review it. | | **In review** | The carrier is reviewing it. Reviews usually take 1 to 3 days. | | **Approved** | Done. A caller name can take up to 3 more days to reach every carrier. | | **Rejected** | The carrier said no; the reason shows under the status. Fix it in Twilio's Trust Hub, then click **Try again**. | | **Needs attention** | Something stopped the registration, such as missing details or credentials that no longer work. The reason shows under the status. | | **Off** | No caller name is set: it was turned off, or the number left your account. | Telnyx caller names have no review step: they stay **Submitted** for about 3 days, then turn **Approved** once ThunderPhone reads the listing back from Telnyx. They never show **In review**. If the name is changed or removed in your Telnyx account, the status becomes **Needs attention**. ThunderPhone checks registrations under review about once an hour. Click **Refresh status** to check now. ## API The same controls are available over REST at [`/v1/caller-registration`](/api-reference/caller-registration). --- ## Next steps ThunderPhone numbers and your own carrier's numbers. Call your contact list at scale. --- title: "Simulate a call before you ship" description: "An AI caller has a real conversation with your agent — same telephony path, real transcript, real grading." --- A **simulation** is an AI caller talking to your agent for real: it rings your number (or connects directly to the agent), improvises within a scenario you set, and produces a normal call log — transcript, recording, grading — in Call History. It's how you test a prompt before real callers hear it, and how you catch regressions after every change. ## Run one from the agent builder In the agent builder's top bar, click **Simulation**. Generate a caller scenario with AI or write your own — who the caller is, what they want, how difficult they're being. Then approve the charge and place the call. A simulation bills both sides — your configured agent plus the simulated caller — and the dialog shows an estimate capped at 10 minutes before you commit. Shorter calls cost less. A floating activity pill appears as soon as the run is placed — your agent is on a call right now. Open the call to follow along; you can even [listen in live](/guides/review-calls). ## Build a scenario library The **Simulations** page (`/dashboard/simulations`) manages scenarios per agent. Pick an agent and click **Generate scenarios with AI** — it drafts scenarios from the agent's own prompt, with an optional **Include edge case scenarios** pass for the hard stuff (angry callers, wrong numbers, half-answers). Edit and keep the ones you like. Scenarios group into **suites** with a minimum pass rate, which can gate releases in CI — see [Test an agent end-to-end (API)](/guides/test-agents) for the suite runner and the programmatic version of everything on this page. ## What to do with the result Every simulation lands in **Call History** like any other call. Read the transcript to spot where the prompt needs tightening, check the grade, and fix-then-rerun. When a real call exposes a gap, promote it: the call detail page's **Create test case** button turns a real conversation into a reusable scenario. Simulation pricing is itemized from both bots and any telephony leg; it is not a blanket 2× multiplier. See [Pricing](/guides/pricing#simulations-and-testing). --- ## Next steps Transcript, recording, listen-in, whisper. Run simulations and batches programmatically. Release gates with pass rates and regression tracking. Compare variants on live traffic once simulations pass. --- title: "Validation sets: replay real call turns before deployment" description: "Turn real ThunderPhone call moments into reusable validation examples, review expected behavior, replay them against an agent draft, and catch regressions before deploy." --- A **validation example** freezes one agent turn from a real call: the conversation leading up to it, the relevant caller audio, what the agent actually did, and the ground truth for what it should do. Replaying a set against the current draft catches regressions without placing another call. Use the organization-wide **Validation sets** page (`/dashboard/validation`) to build and review the dataset. Use an individual agent's **Validation** tab to run the set and inspect run history. ## Add examples from call history Pick the agent whose behavior the example should validate. The workspace shows up to 25 recent non-simulation calls for that agent. Read the transcript, hover the agent turn you want to preserve, and select the checklist action. The conversation through that turn and its caller audio spans are copied into the validation dataset. ThunderPhone drafts a description of correct behavior. Edit it so it is specific and testable, and optionally edit the accepted example response and expected tool calls. **Save draft** leaves the example in **Needs review**. **Save & activate** includes it in future validation runs. Promoting the same turn twice reuses the existing example rather than creating two copies. An example is a copy of its call, so [deleting the call](/guides/review-calls#delete-a-call) deletes the example too. ## Example kinds and statuses Kinds describe why an example exists: | Kind | Use | | --- | --- | | **Failure** | A real response that exposed behavior you want to prevent. | | **Golden** | A known-good response worth preserving. | | **Manual** | A manually curated case that is neither category. | Statuses control the lifecycle: | Status | Meaning | | --- | --- | | **Needs review** | Ground truth is still being reviewed; excluded from runs. | | **Active** | Included in validation runs. | | **Stale** | The prompt changed since the example was frozen; review its expectation. | | **Incompatible** | The example cannot be replayed with the current configuration. | | **Archived** | Preserved for history but excluded from runs. | ## Browse and refine the dataset Filter the organization dataset by **agent**, **status**, or **kind**. Selecting an example shows: - Frozen conversation context and playable caller audio spans. - The source call and original response. - Ground-truth expectation, accepted spoken response, and tool calls. - The latest replay beside the original and expected behavior. - The latest verdict and judge explanations, when available. Choose **Edit ground truth** to revise the expectation or accepted response. Choose **Replay against draft** to test only that example without starting a full run. Replays use the agent's current draft, not necessarily its deployed configuration. ## Run a validation set From the agent's **Validation** tab, start a run over every active example. Each example receives one of these verdicts: - **Pass** — the response satisfies the expectation and caller audio. - **Warn** — the response is usable but one check found uncertainty. - **Fail** — it contradicts the audio or misses required behavior. - **Skipped** — required caller audio could not be loaded; it is excluded from the pass-rate denominator. - **Error** — replay or grading could not complete. The run summary reports pass, warning, failure, and skipped counts, the pass rate, estimated cost, and any regression from pass to fail since the preceding completed run. Only one validation run per agent can be active at a time. ## Deploy with validation evidence When an agent has validation examples, the deploy dialog reports whether the latest completed run matches the current draft. You can: - Deploy with the displayed pass rate and regression count. - Run validation first if no matching run exists. - Deliberately deploy anyway when the result has been reviewed. Validation is evidence and a deployment check, not an automatic production traffic split. For live comparisons, use [Experiments (A/B testing)](/guides/experiments-ab-testing). ## API automation The [Validation Sets API](/api-reference/validation-sets) covers promoting turns, managing examples, streaming pinned audio, individual replays, batch runs, run results, deploy-time status, and the organization overview. --- title: "Review, listen to, and coach your calls" description: "Call History end-to-end: transcripts, recordings, live listen-in, and whispering instructions to your agent mid-call." --- Every call — inbound, outbound, simulation, or widget session — lands in **Call History** (`/dashboard/call-history`), newest first. This is where you find out what your agent actually said, and where you can step into a live call and steer it. ## Find the call The list filters by **Date**, **Type**, **From**, **To**, **Agent**, **Outcome**, **Needs attention** (calls flagged for a follow-up) and **Business hours**, and the **Columns** button controls what each row shows. The **Outcome** column carries the label the call report chose (the agent's own list, or Success / Failure), **Summary** the one-line summary, and **Needs attention** the follow-up reason. Phone numbers take labels (e.g. "Acme Corp") so recurring callers are recognizable at a glance. Click any row to open the call. ## Set up call reports Open the agent builder and expand **Reporting**. Next to **Outcome labels**, choose **Edit**. Use **Suggest from prompt** or write your own labels, and give each one a description under **When to use it**. Keep 2–12 labels, or choose **Replace all → Success / Failure** to restore the defaults. Under **Summary rules**, choose **Edit** to add instructions for the report's wording, then **Done**. Test the draft with a browser call that includes a conversation. After grading finishes, open the call in Call History and check its outcome, summary and follow-up flag. Choose **Deploy** to apply the changes to production calls. For API setup, see [agent reporting fields](/api-reference/agents#deployed-fields). ## Label a phone number Any number in the **From** or **To** column takes a label ("Aetna", "Main line", "Jack Morrison"): hover the number and choose the pencil. Labels are shared by everyone in the organization. They appear in Call History, on Home, in the Client Portal Calls table and in portal CSV exports. The **From** and **To** filters search labels too: type a label to pick every call from that party. ## Read the conversation The transcript shows every turn, word for word — including the agent's tool calls and their results. It's the fastest way to spot where the prompt needs tightening. Alongside it: - **View** the exact prompt snapshot the call ran with (and download it) — what the agent was told, not what the agent is told today. - **Create test case** — turn this conversation into a reusable [simulation scenario](/guides/simulate-a-call). ## Hear it back Ended calls have a recording — scrub the waveform to replay any moment, or download the audio. ## Listen in — live While a call is **in progress**, the recording spot offers **Listen in** instead. Click it and you join the call silently: you hear everything, and nobody on the call hears you. Use it to watch a new prompt take its first real traffic, or to supervise a [campaign](/guides/outbound-campaigns) as it runs. The [**Live** page](/guides/monitor-live-calls) shows every in-progress call across the org, one click from the same experience. ## Whisper — coach your agent mid-call Once you're listening, a whisper bar appears. Whatever you type goes **straight to your agent**: the caller never hears it, and the agent follows the instruction live, mid-conversation. Try one on your next simulation: `wrap up the call politely`. Or in production: `the caller is a VIP — offer the expedited option`, `stop offering refunds`, `ask for their order number`. Whispers are first-class steering — private operator guidance of up to 500 characters per message. They show up in the call's internal history but never in the customer-facing transcript, and the agent treats them as instructions, not conversation. Listen-in and whisper are also available over the API — see the [calls reference](/api-reference/calls) — so you can build your own supervision console. ## Close the loop Spotted a problem? File an issue on the call — issues carry severity and status and queue up for triage on the [Issues](/guides/issues) page. Recurring themes across many calls are what [Reports](/guides/reports) and [Observability](/guides/observability) are for. ## Delete a call Owners and admins can permanently delete calls. Open a call and choose **Delete call**, or tick several calls in the list and choose **Delete** in the selection bar. Deleting removes the recording, transcript, summary, extracted data, grades and issue reports, and cannot be undone. It also deletes any [validation examples](/guides/validation-sets) made from the call; the confirmation says how many. A call that is still in progress, or whose billing is still being settled right after it ends, can't be deleted yet; try again in a few minutes. Your billing history keeps the charge for each deleted call, without any of its content. To delete calls from your own systems, use the [calls API](/api-reference/calls#delete-a-call). --- ## Next steps Practice listening in and whispering on an AI caller first. Transcripts, history, audio, grading, and export over REST. File and query issues programmatically. Every deploy is versioned — iterate fearlessly. --- title: "Knowledge base" description: "Upload your docs once — FAQs, policies, product sheets — and let agents search them mid-call." --- The **Knowledge** page (`/dashboard/knowledge`) is your organization's document store. Upload files once, then select the exact documents each agent can search; during calls the agent looks up answers in its attached documents instead of guessing. It is also the right home for large reference content such as menus, catalogs, and policies. The agent searches for the part it needs, instead of carrying a tool result that returns everything on every turn. ## Supported files and limits | Type | Extensions | Size limit | | --- | --- | --- | | Text | `.txt`, `.md`, `.csv` | 5 MB per file (UTF-8) | | Binary | `.pdf`, `.docx` | 50 MB per file | ## Upload documents Click **Upload files** or drag files anywhere onto the page — you can drop several at once. Each document is processed for search after upload; its status badge tracks the progress, and a **Retry** action appears on anything that fails (plus a **Retry all** banner when several fail). If a file's content is identical to a document already in the organization, it still uploads but you'll see a duplicate warning so you can tidy up. Adding, editing, replacing, and deleting documents requires an **admin** or **owner** role. Any member can browse and search. ## Find and maintain documents - **Search** — the search box matches document names *and* contents. - **Open a document** — click its row to view the extracted content, download the original file, **replace** it with a new upload (re-processed automatically), retry processing, or delete it. - **Bulk actions** — select multiple rows to retry or delete together. Deleting a document removes it from every agent that uses it. ## Give an agent access Documents don't apply to agents automatically. In the agent builder's knowledge section ([Build an agent](/guides/build-an-agent)), pick the exact documents that agent may search. Scoping per agent keeps a support agent out of your sales playbook and vice versa. ## Next steps Attach documents in the builder's knowledge section. Verify the agent actually uses its knowledge before you ship. --- title: "Outbound campaigns" description: "Upload a contact list and let an agent work through it — with calling windows, retries, and live progress." --- Campaigns dial a list of contacts on your behalf: pick an agent, upload a CSV, set when calling is allowed, and ThunderPhone works through the list — retrying no-answers and voicemails on the schedule you choose. Find them under **Engagement → Campaigns** (`/dashboard/campaigns`). For one-off programmatic calls, use the [outbound calls API](/guides/place-outbound-calls) instead. ## Prerequisites - A deployed voice agent. - An outbound-enabled phone number. Customer VoIP numbers continue to support campaigns. ThunderPhone numbers start inbound-only; an organization admin can request outbound calling from the Phone Numbers page. ThunderPhone reviews each request manually, and approved numbers can run campaigns. See [Connect a VoIP provider](/guides/voip-providers). - Contacts as a CSV file (or pasted JSON) with a `phone` or `phone_number` column. **Up to 5,000 rows per upload**, UTF-8 encoded. New JSON uploads must be flat: extra values may be strings, numbers, booleans, or `null`. Rows containing arrays or nested objects are rejected individually with a validation message; valid rows still import. CSV cells are strings. Accepted extra columns are kept as per-contact variables on the campaign's contact records and automatically supplied to each dial. ThunderPhone also classifies the saved agent prompt before each outbound call to decide whether the agent should navigate a phone menu. Headers such as `name` and `account_id` match `{{name}}` and `{{account_id}}` in the saved agent's prompt. Name mapping can also combine columns into `name`. See [per-call variables](/guides/call-variables) for defaults, naming rules, the 32 KB object limit, and values returned in completion webhooks. Older contact records may contain nested metadata from earlier JSON uploads. That metadata stays on the stored contact. Each dial omits non-scalar values from its variables payload and logs the ignored key names for that attempt, without logging values. Retries use the same filtering. ## Create a campaign Name it, pick the **voice agent**, and choose the **from numbers**: one or more of your outbound-enabled numbers. Spreading calls across several local numbers keeps each number under carrier spam thresholds and shows people a local area code. See [number pools](#number-pools-and-daily-limits). Rows with an invalid phone number or invalid variables are rejected individually and reported back with their row numbers — the rest import fine. Choose a **timezone** and a **daily start/end** window; the campaign only dials inside it. The timezone starts on your first from number's zone (its area code in the US and Canada, its country elsewhere) or, for numbers that span several zones such as toll-free ones, your browser's. The hint under the field says which. The window defaults to 9am–5pm. A window that starts before 8am or ends after 9pm shows a warning: calls outside those hours may break calling-hours rules such as the TCPA's. An end earlier than the start crosses midnight: 22:00 → 02:00 dials from 10pm until 2am the next day, with a warning. **Call in each contact's local time** (on by default) runs the window where each contact is, by their area code: 9am means 9am in New York for a New York contact and 9am in Los Angeles for a Los Angeles one. Contacts without a US or Canadian area code use the timezone above, and an area code that spans two zones is called only while both are inside the window. Turn it off to run the window in the campaign's timezone for everyone. Optionally set a **start at** to schedule the launch and an **end at** after which the campaign completes automatically. - **Concurrent calls** — how many calls this campaign runs at once (1–10, default 3). Campaigns start no new call while your organization has 10 outbound calls in progress, so parallel campaigns share that budget. Every call also counts toward your organization's [concurrent-call limit](/api-reference/introduction#call-limits) (20 by default, across inbound, outbound, web and test calls). When that limit is reached, or the same number was called in the last 10 seconds, the contact simply waits for a later pass; its attempts are not used up. - **Calls per number per day** — the most outbound calls each from number places in any 24 hours (1–500, default 75). Clear it for no limit. - **Maximum attempts** — how many times each contact may be dialed (1–10, default 2). - **Retry backoff (minutes)** — the shortest wait before a retry (at least 30, default 60). - **Retry timing** — **Spread across days and times of day** (the default) moves each retry to a later day and a different part of the contact's day: morning, then midday, then late afternoon. **After the backoff** retries exactly the backoff later. - **Call twice when there's no answer** — if a call rings out, call once more from the same number 30–60 seconds later. It doesn't use an attempt. On iPhone, a second call within three minutes can ring through Do Not Disturb when Allow Repeated Calls is on. Redials take dialing time, so a campaign where most calls ring out reaches new contacts more slowly with this on. - **Retry on** — which outcomes trigger a retry: **no answer**, **voicemail**, **failed** (all on by default). Need more attempts or shorter spacing? Contact support: staff can raise both limits for your organization. Campaigns are created as drafts — contacts can only be added while a campaign is a draft. Starting a campaign requires an explicit consent to the call charges. With a future **start at** it becomes *scheduled*; otherwise it starts *running* immediately. Campaigns saved before these limits keep running exactly as configured. Their attempts and spacing stay as saved through other edits. ## Number pools and daily limits Phone carriers label numbers as spam when they place many calls a day or call in bursts, and people pick up more often when they see a local area code. A campaign with several from numbers (its pool) spreads its calls across them: - Each contact is called from the number it was last called from, so retries come from the same caller ID. A new contact gets a pool number with its area code, else one in its timezone, else the number used least in the last 24 hours. The contacts table shows which number called each contact. With **Text before calling** on, a number keeps room for the contacts it texted, so when that room is all spoken for a retry may come from another pool number. - **Calls per number per day** counts every outbound call from that number in your organization over the last 24 hours, including other campaigns and API calls. - A ThunderPhone number with less than a week of outbound calls warms up: it places at most 20 calls on its first day, rising evenly to the full limit by day 7. Numbers on your own carrier skip the warm-up. - When every number has reached its limit, contacts wait. Nothing fails and no attempts are used; the campaign page says why it is waiting, and calling resumes on its own as the last day's calls age out. To go faster, pause the campaign and add numbers or raise the limit. - If a number stops working mid-campaign, the campaign keeps calling from the others, and pauses only when none is left. As a rule of thumb, size the pool so the list's daily volume divided by the number of numbers stays under the limit. A pool is for pacing and local presence: retire a number that gets labeled as spam rather than adding fresh ones to hide it. ## Text before you call People are more likely to pick up a number they were told to expect. A campaign can text each contact a few minutes before their first call, saying who is calling and from which number. Turn it on in the campaign's create or edit form, under **Text before calling**: switch it on, pick a number under **Send texts from**, edit the **Message**, and set **Minutes before the call** (1–60, default 2). Through the API, set the `pre_call_sms_*` fields on the [campaign](/api-reference/campaigns#text-before-calling). - Texts go out through your own Twilio or Telnyx number. ThunderPhone numbers cannot text. - The message can use the contact's variables plus `{{org_name}}`, `{{agent_name}}` and `{{call_from_number}}` (the pool number that will call this contact; the call comes from it). A missing variable is left out, so a contact with no first name gets "Hi, this is …". The default message: ```text Hi {{first_name}}, this is {{org_name}}. We'll call you in a few minutes from {{call_from_number}}. Reply STOP to opt out of texts. ``` - Only the first call gets a text; retries never do. Campaigns place calls in passes every 5 minutes, so with the default 2 minutes the call comes about 5 minutes after the text. - Keep the minutes before the call short. A texted contact holds its call slot until it is called, so a long lead slows the campaign: with 3 calls at a time and 30 minutes, it texts 3 people and then waits half an hour to call them. A texted contact is called ahead of due retries. - A text goes out only when the calling window is open and will still be open for the call, with 5 minutes to spare. With **Call in each contact's local time** on, that is the contact's own window. Otherwise the contact waits for the next window. The window must be at least the minutes before the call plus 12 minutes. - A text that fails or is skipped never holds up the call: the contact is dialed right away. Landlines are skipped. Failed texts are not resent. Texting needs the same consent as calling; texts fall under the TCPA and carrier rules too. A US local number must be registered for A2P 10DLC on your Twilio account (until it is approved, contacts are called without a text and the campaign shows a warning), a toll-free number needs toll-free verification, and a Telnyx number needs a messaging profile. Carrier fees are billed by your carrier. ThunderPhone does not receive replies yet, so STOP is handled by your carrier's opt-out handling. See [SMS](/api-reference/sms). Open the campaign to see each contact's text result in the **Text** column: delivered or sent, not sent (with the reason), skipped or unknown. Through the API it is the contact's [`pre_call_sms`](/api-reference/campaigns#contact-object), and each text also appears in `GET /v1/sms` with `source: "pre_call"`. ## How outcomes and retries work After each call, the contact gets an outcome — successful calls are marked completed; a call graded as no-answer, detected as voicemail, or failed is retried if that outcome is in your **retry on** list and the contact has attempts left. Contacts that run out of attempts are marked *exhausted*; non-retryable failures are marked *failed*. Dialing pauses automatically while your account balance isn't in good standing, and resumes once it is. Keep [auto top-up](/guides/billing-and-topups) on for long campaigns. ## Monitor and control Each campaign card shows its status and progress (e.g. "173 of 248 finished"). Open a campaign for the status breakdown, per-contact results, and recent calls — each links to the full transcript and recording in call history. - **Pause / resume** — pausing stops new dials; calls already in progress finish naturally. Paused campaigns can be edited and resumed. - **Cancel** — permanently stops the campaign. - A campaign completes on its own when every contact reaches a final state or the **end at** time passes. Campaign statuses: `draft`, `scheduled`, `running`, `paused`, `completed`, `cancelled`. Contact statuses: `pending`, `scheduled`, `calling`, `completed`, `failed`, `exhausted`. ## Next steps Listen in on campaign calls as they happen. Place individual calls programmatically. --- title: "Get outbound calls answered" description: "Keep your numbers from being labeled spam and get more people to pick up, for calls placed through the API and through campaigns." --- Carriers decide whether your call shows your business name, a plain number or "Spam Likely". People decide whether to pick up. The label depends mostly on how each number calls, so most of what helps is in your hands. ## How carriers judge a number Three analytics companies score US calls for the big carriers: Hiya for AT&T, TNS for Verizon and First Orion for T-Mobile. They don't publish their scores, but they weigh the same signals: - How many calls a number places a day, and whether they come in bursts. - How many calls end within seconds, and how often a person answers. - Complaints from the people you call. - The STIR/SHAKEN attestation on each call, and whether the number is registered with them. - Whether the number takes calls back. Whether someone picks up also depends on a local area code, a name they recognize, knowing you'll call, the time of day, and getting past iPhone Call Screening and Pixel Call Screen. ## Which levers apply to your calls Some protections apply to every outbound call, whether you place it with [`POST /v1/call`](/guides/place-outbound-calls) or run a [campaign](/guides/outbound-campaigns). Others are campaign settings, and with the API you do the same thing in your own code. | Lever | Calls through `POST /v1/call` | Campaigns | |---|---|---| | 14-second minimum ring | Automatic | Automatic | | Who answered (`answered_by`) and number health | Yes | Yes | | Callbacks reach an agent with context | Yes | Yes | | Caller name and carrier registration | Yes, per number | Yes, per number | | Voicemail handling | Agent setting | Agent setting | | Call screener handling | Automatic | Automatic | | Local area code | You choose the `from_number` | The pool picks one per contact | | Calls per number per day | You pace your own volume | **Calls per number per day**, with warm-up | | Contact's local calling hours | You schedule | **Call in each contact's local time** | | Retries on other days and times | You schedule | **Spread across days and times of day** | | One quick redial after a ring-out | You redial | **Call twice when there's no answer** | | A heads-up text | Send with [`POST /v1/sms`](/api-reference/sms) | **Text before calling** | ## Keep each number's daily volume low Volume per number is the signal you control most directly. Spread your calls across several numbers instead of pushing them all through one, and make each of them a number people can trust: caller name set, registered, and taking callbacks. A large pool of bare numbers reads as evasion. - [Number health](#watch-each-numbers-health) marks a number **Watch** at over 50 calls on its busiest day and **At risk** at over 100, so a number used near the campaign default of 75 shows **Watch**. - Campaigns cap each number at 75 calls in any 24 hours by default (**Calls per number per day**, 1–500). The count includes every outbound call from that number in your organization, API calls too. When every number is at its limit, contacts wait; nothing fails. - A ThunderPhone number new to outbound calling warms up in campaigns: 20 calls on its first day, rising to the full limit by day 7. - No per-number cap applies to API calls. Count calls per `from_number` over the last 24 hours and stop well before 100. Don't send a whole list from one number in a few minutes. Size the pool to the list so each number stays under the limit. See [number pools](/guides/outbound-campaigns#number-pools-and-daily-limits). ## Call from a local number People answer a familiar area code more often than a distant one. - **Campaigns** call a new contact from a pool number with the contact's area code, else one in the same time zone, else the least used number. Retries come from the number that called before. - **With the API**, keep numbers in the area codes you call most, choose the `from_number` that matches the contact, and use the same number for every call to that person. ## Show who's calling Find these on **Phone Numbers**, in the **Caller ID and spam registration** section. They are free. See [Caller ID and spam registration](/guides/caller-id-and-spam-registration) for the steps. - **Caller name (CNAM).** ThunderPhone numbers show your organization's caller name automatically, up to 15 letters, numbers and spaces, once your organization has a real name (not the generated "…'s Team"). Numbers in your own Twilio or Telnyx account get one when an admin registers them. Carriers take 1 to 3 days, and landlines show it more reliably than mobile phones. Caller names work on US local numbers only, not toll-free. - **Verified caller ID (STIR/SHAKEN).** Carriers sign each call **A** when they know you own the number. Lower attestation gets labeled spam more often. Calls from ThunderPhone numbers are signed A. On Twilio, registering a number for verified caller ID gets its calls signed A. On a manual SIP trunk, ask your carrier for A attestation. Each number's health panel shows the attestation to expect. - **Spam-filter registration.** On Twilio, an admin can have ThunderPhone register numbers with the carriers' spam analytics through Twilio Voice Integrity; it needs an approved Primary Business Profile. For any other number, register it yourself at [freecallerregistry.com](https://www.freecallerregistry.com), the free registry Hiya, TNS and First Orion share. ## Tell people you're going to call A person who expects your call is more likely to pick up. A short text a few minutes before the call, from a number they can see is yours, does that. Only text people who agreed to it: texts fall under the same consent rules as calls. - **Campaigns** can text each contact before the first call. Turn on **Text before calling**, pick one of your Twilio or Telnyx numbers, and set the minutes before the call (1–60, default 2). Retries are never texted, and a text that fails never holds up the call. See [Text before you call](/guides/outbound-campaigns#text-before-you-call). - **With the API**, send the text with [`POST /v1/sms`](/api-reference/sms) from your Twilio or Telnyx number, then place the call. ThunderPhone numbers cannot send texts. A US local number needs A2P 10DLC registration before it can text. Ask people to save your number. iPhone Call Screening and Silence Unknown Callers skip numbers in the contact list. ## Call when people pick up - **Call in the contact's local hours.** Campaigns do it by default: **Call in each contact's local time** runs the window by each contact's area code. With the API, work out the contact's time zone and schedule the call yourself. US marketing calls must fall between 8am and 9pm local time; see [outbound calling compliance](/guides/outbound-calling-laws). - **Retry on another day at another time.** Someone who missed a 10am call may be free mid-afternoon. Campaigns spread retries this way by default (**Retry timing** → **Spread across days and times of day**). With the API, don't redial an unanswered number on a fixed short loop. - **Redial once, quickly, after a ring-out.** On iPhone, a second call from the same number within three minutes rings through Do Not Disturb when Allow Repeated Calls is on. Campaigns do this with **Call twice when there's no answer**. Never redial more than once, and never after a declined call or voicemail. ## Make every call look like a real call Carriers read short rings and very short calls as robocalls. - **Let it ring.** Outbound calls ring for at least 14 seconds, whatever the agent's **Ring duration**. A call that rings once and drops is what one-ring scams do. A shorter setting applies to transfers only, and the builder says so. - **Avoid very short calls.** Number health counts answered calls under 10 seconds. Have the agent speak first (the default) and open with who is calling and why, so the person stays on the line. - **Leave a voicemail.** People call back numbers that leave a message, and callbacks help the number's standing. Under **When voicemail answers**, **Let the agent decide** (the default) leaves a short message written from your prompt unless the prompt says not to, and **Leave a message, then hang up** plays the message you set. **Hang up silently** leaves nothing, and the builder warns about it. See [voicemail handling](/guides/agent-settings#voicemail-handling). - **Get past call screeners.** When iPhone Call Screening or Pixel Call Screen answers, the agent states its name and the reason for the call in a sentence, then waits for the person. Put your business name and the purpose of the call near the top of the prompt so the agent has both. ## Take calls back A number that takes calls back looks like a real business, and a returning caller is your warmest lead. When someone calls back a number that called them in the last 30 days, the answering agent gets a short note on the earlier call: when, which agent and what happened. If the number has no inbound agent and no webhook answers, the call goes to the agent that placed the earlier call instead of ending. This works for API and campaign calls alike. Give every outbound number an inbound agent or a route you choose. Caller ID can be faked, so the earlier call's summary and variables are shared only if an admin turns on **Share the earlier call's details** under **Organization → General → Callbacks**. See [callbacks to your outbound calls](/guides/handle-inbound-calls#callbacks-to-your-outbound-calls). ## Watch each number's health On **Phone Numbers**, each number that placed outbound calls in the last 30 days shows **Healthy**, **Watch** or **At risk**. Open **Outbound health** for calls per day, how often a person answered, voicemail rate, short calls and short rings over the last day, week and month, with the reasons in plain English. The status covers the last 7 days, and needs at least 20 calls before it judges the answer rate and 10 before it judges short calls or short rings. The same data is at [`GET /v1/phone-numbers/health`](/api-reference/phone-numbers#outbound-number-health). | Reason | Watch | At risk | |---|---|---| | Calls a day from one number | Over 50 | Over 100 | | Calls that reach a person | Fewer than 1 in 5 | Fewer than 1 in 10 | | Answered calls under 10 seconds | Over 30% | Over half | | Unanswered calls that ring under 12 seconds | Over half | — | Each outbound call also records who answered as `answered_by`: `human`, `voicemail`, `screener`, `ivr` or `unknown`. It is on the [call](/api-reference/calls#call-object), in the completion webhook and under **Answered by** in call history. Health reads the same signals carriers use from your own calls. It is a guide, not any carrier's actual label. ## When a number gets labeled spam You'll usually see it first as a falling answer rate, a number marked **At risk**, or people telling you their phone showed "Spam Likely". 1. **Slow down or stop calling from it.** Lower its daily volume, or take it out of the campaign pool. 2. **Fix the cause.** Check its health reasons: too many calls a day, too many short calls, or too few people answering. 3. **Register it.** Set a caller name and register the number with the spam filters as above, or at [freecallerregistry.com](https://www.freecallerregistry.com). If the label stays, ask the analytics company behind that carrier (Hiya, TNS or First Orion) to review the number. 4. **Retire it if the label stays.** Once you've fixed the cause, release the number and replace it, and call from the new one at a pace that keeps it healthy. Don't rotate fresh numbers in to hide a labeled one. A pool is for pacing and local area codes, not for dodging labels. New numbers that call the same way get labeled the same way. ## Stay within the law Consent, calling hours and do-not-call rules apply to every outbound call and text. Read [outbound calling compliance](/guides/outbound-calling-laws) and talk to your counsel about your calling program. --- ## Next steps Number pools, local calling hours, spread retries and texts. Call one person at a time from your own code. Show your business name and register your numbers. Consent, calling hours and do-not-call rules. --- title: "Monitor live calls" description: "See every call in progress, listen in silently, and whisper guidance the caller never hears." --- The **Live** page (`/dashboard/live`) under **Engagement** lists every call currently in progress — who's talking to which agent, in which direction, and for how long. From there you can join any call as a silent listener and coach the agent mid-call. ## The live board Each row shows the **agent**, **direction** (inbound/outbound), **caller**, **callee**, a ticking **live duration**, and when the call started. The page refreshes itself every few seconds, and a "live" counter in the header tracks the total. Calls placed directly through the API without an agent show as *API / Webhook*. Click **Listen** on a row to open that call's detail page, where the live controls appear. Live supervision works on real calls only — in demo mode the board shows simulated calls and the listen/whisper controls are disabled. ## Listen in **Listen in** joins the call as a hidden, listen-only participant: - Neither the caller nor the agent is affected — nothing on the call changes and nobody is notified. - The recording is unaffected; your listening never appears in it. - A **LIVE** indicator shows while connected. Click **Leave** to drop out; if your connection blips, the session reconnects automatically. - When the call ends, the session ends with it. ## Whisper to the agent While listening, a **Whisper to agent…** box appears next to the live controls. Type a short instruction (up to 500 characters) and press **Send**: - The agent receives your text as **private mid-call guidance** and incorporates it into the conversation. - **The caller never hears or sees the whisper** — it changes what the agent does next, not the audio on the line. - You'll see *Whisper sent* on delivery, or *Call ended* if the call finished before the whisper arrived. Whispers are ideal for live corrections — "the discount code is SPRING24", "offer to escalate", "wrap up, this is a spam call" — without taking the call over. Listen and whisper are also available programmatically via `POST /v1/calls/{id}/listen` and `POST /v1/calls/{id}/whisper` — see the [calls API reference](/api-reference/calls). ## Next steps Transcripts, recordings, and grading in call history. Get notified when success rates dip — before you need to listen in. --- title: "Create a web widget in the dashboard" description: "Set up a click-to-talk widget for your site from the Web Widgets page — no API calls required." --- The **Web Widgets** page (`/dashboard/web-widgets`) creates and manages the publishable keys that power ThunderPhone's embeddable voice widget. Everything the [API version of this guide](/guides/embed-web-widget) does with cURL, this page does with a form — plus it generates the embed code for you. ## Create a widget - **Agent mode** — the widget always runs one agent you pick from the dropdown. The simplest setup. - **Webhook mode** — on every widget session, ThunderPhone calls your webhook URL and your server returns the agent configuration for that visitor (per-page routing, logged-in users, rollouts). Leave the URL blank to use your organization's default webhook; when you enter a new URL you can also set it as the org default. At least one domain is required, and browsers can only start sessions from the hostnames you list. Subdomain wildcards like `*.example.com` are allowed (they match subdomains, not the bare domain); bare wildcards like `*` are rejected. The list stops casual reuse of your key on other sites but is not authentication: a script outside a browser can send any `Origin` header. `localhost` is not allowed automatically in production; add it to the list to test locally against the production API. Tick each session setting your page sends: **Page context**, **Variables**, **Voice**, **Language**. New widgets accept none, and a session request carrying a setting that is off fails with `400`. Your key is public, so allow only what you use. See [Browser overrides](/guides/embed-web-widget#browser-overrides). The **Enable widget** toggle controls whether the key accepts sessions — you can create a widget disabled and flip it on at launch. ## Grab the embed code Right after creation (or via each row's **Embed** button), an embed dialog generates ready-to-paste code with your publishable key filled in: - **React** — a `` snippet for bundler apps. - **Script tag** — a CDN ` ``` ```html Pinned version ``` The `latest` URLs are what the dashboard's embed snippet generator produces. They are cached for 5 minutes and pick up new stable releases automatically. Pinned `/widget/vX.Y.Z/` paths are immutable and cached long-term -- if you pin, match the version to the `@thunderphone/widget` npm release you develop against. --- ## Basic Usage ```html
``` The widget mounts into the target element and renders as a **fixed-position bar in a corner of the viewport** (default `bottom-right`, controlled by the `position` option). The mount element is only the React root -- its location in the page does not affect where the widget appears. --- ## Mount Options `ThunderPhone.mount()` accepts the same options as the React component, plus the `element` property: | Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | `element` | `string \| HTMLElement` | Yes | -- | CSS selector (e.g., `'#thunderphone'`) or a DOM element reference. | | `publishableKey` | `string` | Yes | -- | Publishable API key (`pk_live_...`). The agent is resolved automatically from the key's widget configuration. | | `theme` | `'light' \| 'dark'` | No | `'light'` | Color scheme. | | `primaryColor` | `string` | No | `'#000000'` (light) / `'#ffffff'` (dark) | CSS color string used as the accent color. | | `title` | `string` | No | `'Voice assistant'` | Text displayed in the widget bar. | | `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left'` | No | `'bottom-right'` | Fixed viewport position. | | `apiBase` | `string` | No | `'https://api.thunderphone.com/v1'` | API base URL override. | | `language` | `string` | No | -- | Per-session language override -- a language code or locale such as `en`, `es`, or `fr-FR`. When unset, the agent's configured language applies. Requires `language` under the key's [browser overrides](/guides/embed-web-widget#browser-overrides). | | `voice` | `string` | No | -- | Per-session voice override -- a voice name such as `maria`. When unset, the agent's configured voice applies. Requires `voice` under the key's [browser overrides](/guides/embed-web-widget#browser-overrides). | | `context` | `string` | No | -- | Per-session factual page or site context passed to the agent. Truncated server-side to 12,000 characters. Requires `context` under the key's [browser overrides](/guides/embed-web-widget#browser-overrides). | | `onConnect` | `() => void` | No | -- | Called when the voice session connects. | | `onDisconnect` | `() => void` | No | -- | Called when the session ends. | | `onError` | `(error) => void` | No | -- | Called on errors. Error has `error` (code) and `message` fields. | | `ringtone` | `boolean \| string` | No | `false` | Play a ringtone while connecting. `true` for the default ringtone, or a URL string for custom audio. | --- ## Cleanup `ThunderPhone.mount()` returns a widget instance with cleanup methods: ```html ``` Both `unmount()` and `destroy()` do the same thing -- they disconnect any active voice session and remove the widget from the DOM. Use whichever reads better in your code. Always clean up the widget when navigating away in single-page applications or when the containing element is removed. This ensures active voice sessions are properly disconnected. --- ## Examples ### Dark Theme with Custom Color ```html
``` ### Custom Position ```html
``` ### With Event Callbacks ```html
``` ### With Ringtone ```html
``` ### Using a DOM Element Reference ```html
``` ### WordPress / CMS Integration Add this to a Custom HTML block or your theme's footer: ```html
``` The `
` can go anywhere -- it is only the mount point. The widget itself renders as a fixed overlay in a viewport corner (set by the `position` option) and does not flow with surrounding content. --- ## Troubleshooting Make sure both the CSS and JS files are loaded. Check the browser console for network errors. Verify that the target element exists in the DOM before calling `ThunderPhone.mount()`. The script has not loaded yet. Ensure the `