# Daily call analytics Source: https://docs.topcalls.ai/api-reference/account/daily-call-analytics /api-reference/openapi.json get /v1/analytics/calls Returns pre-aggregated daily call counters for the given date range, ordered by day ascending. `start_date` and `end_date` are both required and the window must not exceed 90 days. # Get account balance Source: https://docs.topcalls.ai/api-reference/account/get-account-balance /api-reference/openapi.json get /v1/balance Get your account's remaining call minutes and usage information # List usage ledger movements Source: https://docs.topcalls.ai/api-reference/account/list-usage-ledger-movements /api-reference/openapi.json get /v1/usage/movements Cursor-paginated raw ledger of minute-consumption events. Each row records the minutes consumed by a single source (e.g. a call) at the time it happened. This is a raw-column surface: field names mirror the ledger schema rather than the derived balance shown by `GET /v1/balance`. # Bulk delete leads (hybrid sync/async) Source: https://docs.topcalls.ai/api-reference/bulk-delete-leads-hybrid-syncasync /api-reference/openapi.json post /v1/leads/bulk-delete Delete leads in bulk. Polymorphic body: pass `ids: uuid[]` (max 500, sync only) OR `filter: {list_id?, status?, tag?}` (any size; gateway recounts then auto-routes sync if <=500 / async via job table if >500). Sync mode (200) returns `{kind:'sync', deleted, skipped, skipped_payload}`. Async mode (202) returns `{kind:'async', job_id, status:'pending', total_leads, chunks_total}`. A background worker drains the job in 1000-row chunks; client polls or watches notifications for terminal status. Hard delete: `lead_list_assignments` cascades automatically; `calls.lead_id` becomes NULL via existing FK. There is no undo. Requires scope: `leads:write`. # Bulk import leads Source: https://docs.topcalls.ai/api-reference/bulk-import-leads /api-reference/openapi.json post /v1/leads/bulk Imports up to 2000 leads in a single transaction. `mode='merge'` (default) timestamp-appends notes and DISTINCT-unions tags on existing rows; `mode='skip'` leaves existing rows untouched. Requires scope: `leads:write`. # Cancel a call Source: https://docs.topcalls.ai/api-reference/calls/cancel-a-call /api-reference/openapi.json post /v1/calls/{call_id}/cancel Cancel a call that is currently queued or in progress. This mirrors the `/stop` gate: a call in a terminal state (completed, failed, cancelled) cannot be cancelled and returns 400. # Create a new call Source: https://docs.topcalls.ai/api-reference/calls/create-a-new-call /api-reference/openapi.json post /v1/calls Create and dispatch an AI-powered phone call. The call will be queued and executed immediately. **Phone Number Format**: Must be in E.164 format (e.g., `+14155551234`) - Must start with `+` - Country code must be 1-9 (not 0) - Total length: 1-15 digits after the `+` **Simple Mode**: Provide `task` (simple prompt) **Advanced Mode**: Provide `instructions` (full system prompt) # Get call details Source: https://docs.topcalls.ai/api-reference/calls/get-call-details /api-reference/openapi.json get /v1/calls/{call_id} Retrieve detailed information about a specific call # List calls Source: https://docs.topcalls.ai/api-reference/calls/list-calls /api-reference/openapi.json get /v1/calls Retrieve a paginated list of calls for your account. Returns call details including transcript, recording URL, summary, billing information, customer-supplied metadata, and analysis. For account and internal-tracking fields (e.g. `account_id`, `external_call_id`, `completed`), use `GET /v1/calls/{call_id}`. # Stop an in-progress call Source: https://docs.topcalls.ai/api-reference/calls/stop-an-in-progress-call /api-reference/openapi.json post /v1/calls/{call_id}/stop Cancel or stop a call that is currently queued or in progress # Create a campaign Source: https://docs.topcalls.ai/api-reference/campaigns/create-a-campaign /api-reference/openapi.json post /v1/campaigns Create a campaign. Status is always `draft` at create time. Outbound (`direction` omitted or `outbound`): attach lead lists via `lead_list_ids`, then launch with `POST /v1/campaigns/{id}/start`. Inbound (`direction: inbound`): attach a phone number with `PATCH /v1/phone-numbers/{id}` `{ inbound: { enabled: true, campaign_id } }`, then `POST /v1/campaigns/{id}/start`. Same start/pause/resume/stop as outbound. Inbound needs no lead list and creates no campaign run. # Get campaign details Source: https://docs.topcalls.ai/api-reference/campaigns/get-campaign-details /api-reference/openapi.json get /v1/campaigns/{campaign_id} Retrieve a single campaign by id, scoped to the authenticated account. Includes `direction` and `inbound_phone_numbers` (attached DIDs). Inbound campaigns answer only while `status` is `running`. # Get campaign statistics Source: https://docs.topcalls.ai/api-reference/campaigns/get-campaign-statistics /api-reference/openapi.json get /v1/campaigns/{campaign_id}/stats Returns call counters for the campaign, computed from call records at request time, plus the current (most recently created) run, if one exists. `stats` is all zero when the campaign has no calls yet. `current_run` is `null` when the campaign has never been started. Inbound campaigns have no campaign run (`current_run` stays `null`); their counters still include inbound calls on attached numbers. # List campaigns Source: https://docs.topcalls.ai/api-reference/campaigns/list-campaigns /api-reference/openapi.json get /v1/campaigns List campaigns for your account, ordered by creation time (newest first). # Pause a campaign Source: https://docs.topcalls.ai/api-reference/campaigns/pause-a-campaign /api-reference/openapi.json post /v1/campaigns/{campaign_id}/pause Marks the campaign run `paused` and the campaign `paused` so the producer stops queuing new calls; in-flight calls are unaffected. The response returns as soon as the status flips. Cleanup of already-queued calls (so a later resume starts from fresh lead data) runs asynchronously afterward and is not awaited. Inbound campaigns have no run: pause sets `status` to `paused` so the attached number stops answering. Resume sets `running` again. # Place a test call for a campaign Source: https://docs.topcalls.ai/api-reference/campaigns/place-a-test-call-for-a-campaign /api-reference/openapi.json post /v1/campaigns/{campaign_id}/test Dials the given phone number using the campaign's current configuration, without linking the call to any lead or campaign run. This is a real outbound call: it reserves account minutes the same way any other call does and is billed on completion like any other call. Use it to verify a campaign's script, voice, and knowledge base before starting a real run. `test_lead_data` is optional context injected into the call as if it were a lead record (name, email, notes, status, and any extra metadata keys), useful for testing personalized scripts without creating a real lead. # Resume a paused campaign Source: https://docs.topcalls.ai/api-reference/campaigns/resume-a-paused-campaign /api-reference/openapi.json post /v1/campaigns/{campaign_id}/resume Resumes the campaign's paused run (account quota is re-checked, the same way as start) and marks the campaign `running` again. Queued calls were removed on pause, so the producer creates new calls with the campaign's current configuration and lead data. Inbound campaigns have no run: resume sets `status` to `running`. The attached number answers again. # Start a campaign Source: https://docs.topcalls.ai/api-reference/campaigns/start-a-campaign /api-reference/openapi.json post /v1/campaigns/{campaign_id}/start Validates the campaign (lead lists attached, leads present, account quota covers a call's dial-time reserve), creates a campaign run, and marks the campaign `running` so workers begin queuing calls. If the campaign has a future `schedule_time`, validation runs the same way but the campaign is moved to `scheduled` instead, with no run created yet. It starts automatically at the scheduled time. Send an `Idempotency-Key` header to make retries safe: a request with a key that already produced a run for this campaign returns the existing run with `200` instead of creating a second one. The key represents one logical start operation, not one HTTP request. Inbound campaigns set `status` to `running` and create no run. Callers are answered on the attached number only while `running`. # Stop a campaign Source: https://docs.topcalls.ai/api-reference/campaigns/stop-a-campaign /api-reference/openapi.json post /v1/campaigns/{campaign_id}/stop Permanently stops the campaign: marks its run `stopped` and the campaign `completed`, then cancels queued calls in place (they stay in call history, marked cancelled, rather than being deleted). A stopped campaign cannot be resumed. Start a new one instead. Calling stop on a campaign with no active run still marks it `completed`; in that case `run_id` is returned as `null`. Inbound campaigns have no run: stop sets `status` to `completed` so the attached number no longer answers. # Update a campaign Source: https://docs.topcalls.ai/api-reference/campaigns/update-a-campaign /api-reference/openapi.json patch /v1/campaigns/{campaign_id} Partially update a campaign. Every field is optional; only the fields you send are changed. `status` can never be set here. Use the lifecycle endpoints (start/pause/resume/stop) to change it, for both outbound and inbound. Inbound answers only while `status` is `running`. Pause stops answering; resume starts again; stop completes the campaign. `direction` can only change while the campaign is `draft` (`409` `campaign_not_draft` otherwise). Switching a draft inbound campaign to `outbound` while a number still routes to it returns `409` (`campaign_has_inbound_routes`). `config` is merged shallowly with the campaign's existing configuration: keys you send overwrite the existing value, keys you omit are left untouched, and a key sent with value `null` deletes it from the stored configuration. Nested objects (e.g. `analysis_schema`) are replaced whole rather than merged. Values you provide (other than `null`) go through the same validation as `POST /v1/campaigns`. `lead_list_ids`, when included, replaces the campaign's full set of attached lead lists (an empty array detaches all of them). Omit the field to leave the current lists unchanged. A configuration change takes effect on the campaign's next dispatch buffer fill (typically within seconds), not on calls already queued. # List available models Source: https://docs.topcalls.ai/api-reference/configuration/list-available-models /api-reference/openapi.json get /v1/models Returns all available AI models grouped by mode. **Realtime mode**: Speech-to-speech models for ultra-low latency conversations. **Legacy mode**: Separate STT, LLM, and TTS models that can be mixed and matched. Use this endpoint to discover which models are available and their capabilities. # List built-in voices Source: https://docs.topcalls.ai/api-reference/configuration/list-built-in-voices /api-reference/openapi.json get /v1/voices/builtin Returns built-in voices grouped by mode and provider. These voices are available without any additional setup. Use query parameters to filter by mode, provider, language, or gender. # List voices Source: https://docs.topcalls.ai/api-reference/configuration/list-voices /api-reference/openapi.json get /v1/voices Returns available voices for your account, including system voices and any custom/cloned voices. For built-in voices without account context, use `GET /v1/voices/builtin` instead. # Create knowledge base entry Source: https://docs.topcalls.ai/api-reference/create-knowledge-base-entry /api-reference/openapi.json post /v1/knowledge-bases Creates one knowledge base text entry. `description_category` defaults to `company_information` when omitted, so a minimal `{ name, content }` body succeeds; a `custom` category requires a non-empty `custom_description`. Entry names are unique within the account. Requires scope: `knowledge_base:write`. # Create lead list Source: https://docs.topcalls.ai/api-reference/create-lead-list /api-reference/openapi.json post /v1/lead-lists Creates a lead list, or returns the existing row matching `(account_id, lower(trim(name)))`. Returns 201 on create, 200 on idempotent re-call. Requires scope: `leads:write`. # Create or attach lead Source: https://docs.topcalls.ai/api-reference/create-or-attach-lead /api-reference/openapi.json post /v1/leads Creates a single lead and assigns it to the given list. Idempotent on `(account_id, phone_number)`: if a lead with this phone number already exists in the account, the existing lead is attached to the requested list (no error) and the response carries `existed: true` with HTTP 200. A genuinely new lead returns HTTP 201 with `existed: false`. `notes` and `notes_append` are mutually exclusive. Requires scope: `leads:write`. # Delete knowledge base entry Source: https://docs.topcalls.ai/api-reference/delete-knowledge-base-entry /api-reference/openapi.json delete /v1/knowledge-bases/{id} Hard-deletes an owned entry; any campaign attachments are removed automatically. Requires scope: `knowledge_base:write`. # Delete lead Source: https://docs.topcalls.ai/api-reference/delete-lead /api-reference/openapi.json delete /v1/leads/{id} Without `list_id`, hard-deletes the lead row (assignments cascade). With `list_id`, removes only that assignment; if no assignments remain, the lead row is also deleted. Requires scope: `leads:write`. # Get knowledge base entry Source: https://docs.topcalls.ai/api-reference/get-knowledge-base-entry /api-reference/openapi.json get /v1/knowledge-bases/{id} Returns a single knowledge base entry owned by the account. Requires scope: `knowledge_base:read`. # Get lead by id Source: https://docs.topcalls.ai/api-reference/get-lead-by-id /api-reference/openapi.json get /v1/leads/{id} Requires scope: `leads:read`. # Get lead list details Source: https://docs.topcalls.ai/api-reference/get-lead-list-details /api-reference/openapi.json get /v1/lead-lists/{lead_list_id} Returns a single lead list with its cached counters plus `fresh_counts` computed live from lead status. Requires scope: `leads:read`. # API Reference Source: https://docs.topcalls.ai/api-reference/introduction Complete REST API documentation for TopCalls. Build AI phone agents with our API. ## Welcome to the TopCalls API The TopCalls API is a RESTful API that lets you build AI-powered phone agents. Make calls, manage phone numbers, check available models and voices, and integrate with your systems via webhooks. Standard HTTP methods and JSON responses. Works with any programming language. Try API endpoints directly in this documentation. See responses in real-time. Real-time notifications for call events, tool calls, and campaign updates. Detailed schemas, examples, and error responses for every endpoint. ## Base URL All API requests should be made to: ``` https://api.topcalls.ai ``` ## Authentication All API requests require authentication. Include your API key in the `Authorization` header: ```bash theme={null} Authorization: Bearer YOUR_API_KEY ``` Get your API key from [topcalls.ai](https://www.topcalls.ai/auth/sign-in). Sign in and create a new key in your account settings. API keys carry **scopes** — each scope grants access to one group of endpoints. When you create a key in the dashboard, grant only the scopes it needs. A request to an endpoint outside the key's scopes returns `403 Forbidden`, and the response names the scope that was missing. | Scope | Grants access to | | ---------------------- | ------------------------------------------------------------------------------------------------------- | | `calls:read` | List and read calls (details, transcripts, recordings), plus account balance, usage, and call analytics | | `calls:write` | Create, stop, and cancel calls | | `campaigns:read` | List campaigns and read campaign details and stats | | `campaigns:write` | Create, update, start, pause, resume, stop, and test campaigns | | `phone_numbers:read` | List phone numbers and carriers, and read phone number details | | `phone_numbers:write` | Add and remove phone numbers, manage custom carriers (BYOC), and attach inbound routing | | `leads:read` | List and read leads and lead lists | | `leads:write` | Create, update, delete, and bulk-import leads, and create lead lists | | `knowledge_base:read` | List and read knowledge base entries | | `knowledge_base:write` | Create, update, and delete knowledge base entries | | `webhooks:read` | List and read webhook subscriptions | | `webhooks:write` | Create, update, and delete webhook subscriptions | A read scope never grants writes: a `calls:read` key can list calls but cannot place one. The model and voice catalog endpoints (`/v1/models`, `/v1/voices`) require no scope — any valid key can read them. ## API Versioning The current API version is **v1**. All endpoints are prefixed with `/v1`: ``` https://api.topcalls.ai/v1/calls ``` ## Response Format All responses are JSON. Success responses include the requested data: ```json theme={null} { "call_id": "564d4fd4-03bc-400a-abe0-05540fbeff88", "status": "queued" } ``` Error responses follow RFC 7807 Problem+JSON format: ```json theme={null} { "status": 400, "title": "Invalid request body", "detail": "Phone number must be in E.164 format", "errors": [ { "path": "phone_number", "message": "Phone number must be in E.164 format (e.g., +14155551234)" } ] } ``` ## Quick Start ### 1. Make Your First Call ```bash theme={null} curl -X POST https://api.topcalls.ai/v1/calls \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number": "+14155551234", "task": "Call to confirm appointment...", "voice": "alloy" }' ``` ### 2. Check Call Status ```bash theme={null} curl https://api.topcalls.ai/v1/calls/CALL_ID \ -H "Authorization: Bearer YOUR_API_KEY" ``` ### 3. List Calls ```bash theme={null} curl https://api.topcalls.ai/v1/calls \ -H "Authorization: Bearer YOUR_API_KEY" ``` ## API Endpoints ### Calls | Method | Endpoint | Description | | ------ | ---------------------------- | ----------------------------------- | | `GET` | `/v1/calls` | List calls | | `GET` | `/v1/calls/{call_id}` | Get call details | | `POST` | `/v1/calls` | Create a new call | | `POST` | `/v1/calls/{call_id}/stop` | Stop an active call | | `POST` | `/v1/calls/{call_id}/cancel` | Cancel a queued or in-progress call | ### Campaigns Create, configure, and run campaigns from the API or the TopCalls dashboard: | Method | Endpoint | Description | | ------- | ------------------------------------ | -------------------------------- | | `POST` | `/v1/campaigns` | Create a campaign | | `GET` | `/v1/campaigns` | List campaigns | | `GET` | `/v1/campaigns/{campaign_id}` | Get campaign details | | `GET` | `/v1/campaigns/{campaign_id}/stats` | Get campaign stats | | `PATCH` | `/v1/campaigns/{campaign_id}` | Update a campaign | | `POST` | `/v1/campaigns/{campaign_id}/start` | Start a campaign | | `POST` | `/v1/campaigns/{campaign_id}/pause` | Pause a running campaign | | `POST` | `/v1/campaigns/{campaign_id}/resume` | Resume a paused campaign | | `POST` | `/v1/campaigns/{campaign_id}/stop` | Stop a campaign | | `POST` | `/v1/campaigns/{campaign_id}/test` | Place a test call for a campaign | ### Phone Numbers | Method | Endpoint | Description | | -------- | ----------------------------------------- | -------------------------------- | | `GET` | `/v1/phone-numbers/carriers` | List carriers | | `GET` | `/v1/phone-numbers` | List phone numbers | | `GET` | `/v1/phone-numbers/{phone_number_id}` | Get phone number details | | `POST` | `/v1/phone-numbers/carriers` | Add custom carrier (BYOC) | | `POST` | `/v1/phone-numbers` | Add phone number | | `PATCH` | `/v1/phone-numbers/{phone_number_id}` | Update phone number label/status | | `DELETE` | `/v1/phone-numbers/carriers/{carrier_id}` | Delete custom carrier | | `DELETE` | `/v1/phone-numbers/{phone_number_id}` | Delete phone number | ### Leads | Method | Endpoint | Description | | -------- | -------------------------------- | --------------------------------------------- | | `POST` | `/v1/leads` | Create a lead (idempotent per phone number) | | `GET` | `/v1/leads` | List leads with cursor pagination and filters | | `GET` | `/v1/leads/{id}` | Get lead details | | `PATCH` | `/v1/leads/{id}` | Update a lead | | `DELETE` | `/v1/leads/{id}` | Delete a lead | | `POST` | `/v1/leads/bulk` | Bulk import leads | | `POST` | `/v1/leads/bulk-delete` | Bulk delete leads | | `GET` | `/v1/leads/bulk-delete/{job_id}` | Poll an async bulk-delete job | ### Lead Lists | Method | Endpoint | Description | | ------- | ------------------------------- | --------------------- | | `GET` | `/v1/lead-lists` | List lead lists | | `GET` | `/v1/lead-lists/{lead_list_id}` | Get lead list details | | `POST` | `/v1/lead-lists` | Create a lead list | | `PATCH` | `/v1/lead-lists/{lead_list_id}` | Update a lead list | ### Knowledge Bases | Method | Endpoint | Description | | -------- | -------------------------- | ----------------------------- | | `GET` | `/v1/knowledge-bases` | List knowledge base entries | | `GET` | `/v1/knowledge-bases/{id}` | Get a knowledge base entry | | `POST` | `/v1/knowledge-bases` | Create a knowledge base entry | | `PATCH` | `/v1/knowledge-bases/{id}` | Update a knowledge base entry | | `DELETE` | `/v1/knowledge-bases/{id}` | Delete a knowledge base entry | ### Webhooks | Method | Endpoint | Description | | -------- | --------------------------- | ----------------------------- | | `POST` | `/v1/webhooks` | Create a webhook subscription | | `GET` | `/v1/webhooks` | List webhook subscriptions | | `GET` | `/v1/webhooks/{webhook_id}` | Get a webhook subscription | | `PATCH` | `/v1/webhooks/{webhook_id}` | Update a webhook subscription | | `DELETE` | `/v1/webhooks/{webhook_id}` | Delete a webhook subscription | ### Configuration | Method | Endpoint | Description | | ------ | -------------------- | ------------------------------------------------------ | | `GET` | `/v1/models` | List available AI models | | `GET` | `/v1/voices/builtin` | List built-in voices (with filters) | | `GET` | `/v1/voices` | List voices for your account (including custom/cloned) | ### Account | Method | Endpoint | Description | | ------ | --------------------- | ----------------------------- | | `GET` | `/v1/balance` | Get account balance and usage | | `GET` | `/v1/usage/movements` | List usage ledger movements | | `GET` | `/v1/analytics/calls` | Daily call analytics | ## Webhooks TopCalls sends webhooks to your server when calls finish. Set `webhook_url` on a call for per-call delivery: ```json theme={null} { "webhook_url": "https://your-app.com/webhooks/call-complete" } ``` Or create an account-level subscription with `POST /v1/webhooks`, including disposition-suffixed events like `call.completed.booked_callback`. See the [Webhooks Guide](/guides/webhooks) for complete documentation. ## Idempotency Mutating endpoints accept an optional `Idempotency-Key` header (8-255 characters, `[A-Za-z0-9_-]`). The gateway caches the response for 24 hours and returns the same response on retries with the same key. A key reused while the first request is still running returns `409`. ## Rate Limits Two limits apply, both counted per account. Every API key on the same account shares them. * **All endpoints:** 120 requests per minute. * **`POST /v1/calls`:** also limited to your account's `max_calls_per_minute` setting (default 20) per minute. When you go over a limit, the API returns `429 Too Many Requests` with a `Retry-After` header and a Problem+JSON body: ```http theme={null} HTTP/1.1 429 Too Many Requests Retry-After: 60 Content-Type: application/problem+json { "status": 429, "title": "Too Many Requests", "detail": "Your account has reached its per-minute call rate limit (max_calls_per_minute). Wait for the number of seconds in the Retry-After header, then retry.", "code": "RATE_LIMITED" } ``` **What to do when you get a 429** 1. Read the `Retry-After` header. Its value is the number of seconds to wait before trying again. 2. Wait that long, then send the same request again. 3. If you keep getting `429`, you are sending faster than your limit allows. Lower your request rate, or spread the work out over time. 4. In automated clients, check for status `429` (or the `code` field equal to `RATE_LIMITED`) and back off: wait the `Retry-After` seconds on the first `429`, then double the wait on each repeat until the request succeeds. The limits are per account, so adding more API keys or servers against the same account does not raise the ceiling. For steady high call volume, run a campaign instead of sending individual `POST /v1/calls` requests, or contact us to raise your `max_calls_per_minute`. ## Error Codes | Status Code | Description | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `200` | Success | | `201` | Created | | `400` | Bad Request - Invalid input | | `401` | Unauthorized - Invalid or missing API key | | `402` | Payment Required - Insufficient quota | | `403` | Forbidden - Insufficient permissions | | `404` | Not Found - Resource doesn't exist | | `409` | Conflict - Idempotency key reused while the original request is in flight | | `413` | Payload Too Large - Too many items in a bulk request | | `422` | Unprocessable - Prompt failed the automated compliance review (see the `code` field, e.g. `PROMPT_SAFETY_FRAUD`), or the request could not be processed | | `429` | Too Many Requests - rate limit exceeded. Wait for the `Retry-After` header, then retry. See [Rate Limits](#rate-limits) | | `500` | Internal Server Error | ## SDKs & Libraries Official SDKs coming soon. For now, use any HTTP client library: * **JavaScript/TypeScript**: `fetch`, `axios` * **Python**: `requests` * **Ruby**: `httparty` * **Go**: `net/http` * **PHP**: `guzzle` ## Support * **Documentation**: Browse our [guides](/guides/making-calls) and [concepts](/concepts/overview) * **Contact**: Email us at [hello@topcalls.ai](mailto:hello@topcalls.ai) * **Get Your Deployment Plan**: [Book a discovery call](https://cal.com/topcalls.ai/30min) for a free consultation ## Next Steps Make your first call in 5 minutes. Learn how to make calls with the API. # List knowledge base entries Source: https://docs.topcalls.ai/api-reference/list-knowledge-base-entries /api-reference/openapi.json get /v1/knowledge-bases Returns the account's knowledge base text entries, newest first. Requires scope: `knowledge_base:read`. # List lead lists Source: https://docs.topcalls.ai/api-reference/list-lead-lists /api-reference/openapi.json get /v1/lead-lists Returns all lead lists for the account, sorted by name. Requires scope: `leads:read`. # List leads Source: https://docs.topcalls.ai/api-reference/list-leads /api-reference/openapi.json get /v1/leads Cursor-paginated list of leads. Requires scope: `leads:read`. # Add custom carrier Source: https://docs.topcalls.ai/api-reference/phone-numbers/add-custom-carrier /api-reference/openapi.json post /v1/phone-numbers/carriers Add a custom SIP carrier (Bring Your Own Carrier). **Trunk Types:** - `static_ip` - IP-based authentication (default). Your carrier whitelists TopCalls SBC IP. - `auth` - Credential-based inbound authentication. Requires `inbound_auth` object. - `reg` - SIP registration. Requires `registration` object. # Add phone number Source: https://docs.topcalls.ai/api-reference/phone-numbers/add-phone-number /api-reference/openapi.json post /v1/phone-numbers Add a phone number to your account. The number is provisioned in the telephony system. **Note:** The phone number must be in E.164 format (e.g., `+14155551234`). # Delete custom carrier Source: https://docs.topcalls.ai/api-reference/phone-numbers/delete-custom-carrier /api-reference/openapi.json delete /v1/phone-numbers/carriers/{carrier_id} Delete a custom carrier. Cannot delete the TopCalls default carrier. **Note:** Deleting a carrier does not automatically remove phone numbers using that carrier. # Delete phone number Source: https://docs.topcalls.ai/api-reference/phone-numbers/delete-phone-number /api-reference/openapi.json delete /v1/phone-numbers/{phone_number_id} Remove a phone number from your account. The number is deprovisioned from the telephony system. # Get phone number details Source: https://docs.topcalls.ai/api-reference/phone-numbers/get-phone-number-details /api-reference/openapi.json get /v1/phone-numbers/{phone_number_id} Retrieve detailed information about a specific phone number. # List carriers Source: https://docs.topcalls.ai/api-reference/phone-numbers/list-carriers /api-reference/openapi.json get /v1/phone-numbers/carriers Get the carriers available to your account. **Note:** The TopCalls platform default carrier is the first entry, marked `is_system: true`; the custom carriers (BYOC) you've added follow. You can use the default carrier by omitting `voip_carrier_sid` when provisioning phone numbers. # List phone numbers Source: https://docs.topcalls.ai/api-reference/phone-numbers/list-phone-numbers /api-reference/openapi.json get /v1/phone-numbers Get all phone numbers on your account. # Update phone number Source: https://docs.topcalls.ai/api-reference/phone-numbers/update-phone-number /api-reference/openapi.json patch /v1/phone-numbers/{phone_number_id} Partially update a phone number's `label`, `status` and/or `inbound` routing. At least one field is required. `status` only accepts `active` or `inactive`; a number that is currently `failed` or `pending` cannot have its status changed manually. Setting `inbound.enabled` to `true` attaches the number to the inbound voice application so that calls to it are answered by the given campaign. Enabling requires an `active` number and a `campaign_id` that belongs to your account, has `direction: inbound`, is not `cancelled` or `completed`, does not use the realtime mode, and has a `first_sentence` with no unresolved `{{...}}` placeholders. Greeting, who speaks first, hours, max duration and webhook live on the campaign. The campaign prompt is checked by the safety review before routing starts. Setting `inbound.enabled` to `false` detaches the number and always succeeds. # Poll a bulk-delete job Source: https://docs.topcalls.ai/api-reference/poll-a-bulk-delete-job /api-reference/openapi.json get /v1/leads/bulk-delete/{job_id} Returns the current status of an async bulk-delete job created by the 202 branch of `POST /v1/leads/bulk-delete`. Poll until `status` is a terminal value (`completed`, `completed_with_skipped`, `failed`, or `canceled`). Requires scope: `leads:read`. # Update knowledge base entry Source: https://docs.topcalls.ai/api-reference/update-knowledge-base-entry /api-reference/openapi.json patch /v1/knowledge-bases/{id} Partially update an owned entry. At least one field is required. When the category is set to `custom`, `custom_description` must be provided. Requires scope: `knowledge_base:write`. # Update lead Source: https://docs.topcalls.ai/api-reference/update-lead /api-reference/openapi.json patch /v1/leads/{id} Requires scope: `leads:write`. Body must contain at least one field; `notes` and `notes_append` are mutually exclusive. # Update lead list Source: https://docs.topcalls.ai/api-reference/update-lead-list /api-reference/openapi.json patch /v1/lead-lists/{lead_list_id} Partially update `name` and/or `description`. At least one field is required. Requires scope: `leads:write`. # Create a webhook subscription Source: https://docs.topcalls.ai/api-reference/webhooks/create-a-webhook-subscription /api-reference/openapi.json post /v1/webhooks Subscribe a URL to receive events from your account. v1 supports `call.completed` and disposition-suffixed variants like `call.completed.booked_callback`. The `lead.status_changed` and `campaign.activated` / `campaign.paused` event names are reserved for future use. # Delete a webhook subscription Source: https://docs.topcalls.ai/api-reference/webhooks/delete-a-webhook-subscription /api-reference/openapi.json delete /v1/webhooks/{webhook_id} Soft-delete the subscription (sets `deleted_at`). Idempotent - deleting a row that's already soft-deleted also returns 204. Returns 404 for unknown ids or rows belonging to a different account. # Get a webhook subscription Source: https://docs.topcalls.ai/api-reference/webhooks/get-a-webhook-subscription /api-reference/openapi.json get /v1/webhooks/{webhook_id} Retrieve a single webhook subscription by id, scoped to the authenticated account. Returns 404 for unknown ids, rows belonging to a different account, or soft-deleted rows. # List webhook subscriptions Source: https://docs.topcalls.ai/api-reference/webhooks/list-webhook-subscriptions /api-reference/openapi.json get /v1/webhooks List active webhook subscriptions for the authenticated account. Soft-deleted rows are excluded. # Update a webhook subscription Source: https://docs.topcalls.ai/api-reference/webhooks/update-a-webhook-subscription /api-reference/openapi.json patch /v1/webhooks/{webhook_id} Partially update a webhook subscription's `event`, `url`, and/or `filters`. At least one field is required. Values go through the same validation as `POST /v1/webhooks`. Returns 404 for unknown ids, rows belonging to a different account, or soft-deleted rows. # Account Lifecycle Source: https://docs.topcalls.ai/concepts/account-lifecycle What happens when you close your TopCalls account: immediate effects, the review period, reactivation, and permanent deletion. ## Closing Your Account You can close your account anytime from your account settings, under **Danger Zone > Close Account**. Closing takes effect immediately: * Your access ends. Signing in shows an account-closed notice instead of the dashboard. * Your API keys stop authenticating. Requests to the API return `401`. * Running campaigns stop and queued calls are cancelled. * Webhooks and integration flows stop firing. Your data is not deleted at this point. It enters a review period. ## The Review Period After you close your account, TopCalls keeps your data for a review period before permanent erasure. During this window you can change your mind: contact [hello@topcalls.ai](mailto:hello@topcalls.ai) and ask for reactivation. A reactivated account gets its data, campaigns, and settings back as they were. Closed the account by mistake, or was it closed and you don't know why? Contact support. Reactivation is only possible during the review period. ## Permanent Deletion Once the review period ends, account data is permanently erased. Personal data in call records (transcripts, recordings, lead details) is removed as part of this step and cannot be recovered. One exception: billing records are retained where legal and financial compliance requires it. If you need your data erased on a specific legal basis, such as a GDPR erasure request, contact [hello@topcalls.ai](mailto:hello@topcalls.ai) and state that in your request. ## Before You Close A short checklist, since erasure is final: * **Export what you need.** Download recordings and transcripts you want to keep, from the dashboard or via `GET /v1/calls`. * **Stop campaigns yourself first.** You'll see exactly what was in flight rather than having it cut off. * **Turn off external triggers.** If other systems create calls through the API or integrations, disable them so they don't fail against a closed account. ## Next Steps Export call data before closing your account. Reactivation requests and data questions. # AI & Voice Customization Source: https://docs.topcalls.ai/concepts/ai-voice Control how your agent sounds, behaves, and responds. Create the perfect AI persona for your use case. ## Creating Your AI Persona The most important part of your AI agent is the **instructions** (system prompt). This defines who the agent is, what they do, and how they behave. ## Writing Effective Instructions Your instructions should include: ### 1. Identity & Role ```text theme={null} You are Rachel, a senior appointment coordinator at Bright Dental. You have 5 years of experience and are known for being warm and professional. ``` ### 2. Goal & Context ```text theme={null} Your goal is to confirm {{patient_name}}'s appointment scheduled for {{appointment_date}} at {{appointment_time}}. The patient has been a client for 3 years and prefers afternoon appointments. ``` ### 3. Tone & Style ```text theme={null} Be warm, professional, and concise. Use the patient's first name naturally. If they seem rushed, keep it brief. If they want to chat, be friendly but redirect to the appointment confirmation. ``` ### 4. Handling Scenarios ```text theme={null} - If they want to reschedule: Ask for their preferred date/time and use the 'check_availability' tool to find slots. - If they cancel: Express understanding, ask if everything is okay, but do not pressure them. Offer to call back if they change their mind. - If they have questions: Answer directly or use the 'lookup_patient_info' tool if you need specific details. ``` ### 5. Boundaries ```text theme={null} - Never discuss pricing or billing (transfer to billing department) - Never make medical diagnoses - Always confirm the appointment details before ending the call ``` ## Complete Example ```text theme={null} You are Rachel, a friendly appointment coordinator at Bright Dental. Your goal is to confirm {{patient_name}}'s dental appointment scheduled for {{appointment_date}} at {{appointment_time}}. Be warm, professional, and concise. Use the patient's first name naturally. If they want to reschedule: - Ask for their preferred date and time - Use the 'check_availability' tool to find available slots - Confirm the new appointment immediately If they cancel: - Express understanding - Ask if everything is okay (but don't pressure) - Offer to call back if they change their mind If they have questions: - Answer directly if you know the answer - Use the 'lookup_patient_info' tool for specific patient details - Transfer to the front desk for complex questions Never discuss pricing or billing. Always confirm appointment details before ending the call. ``` ## Voice Selection ### Realtime Mode Voices In Realtime Mode, choose from preset voices optimized for low-latency conversations. The default voice is `alloy`. Check available voices via `GET /v1/voices/builtin`. Realtime Mode voices are optimized for ultra-low latency and natural conversation. They work best for English but can handle other languages with proper instructions. ### Legacy Mode: Full Voice Library Legacy Mode gives you access to hundreds of built-in voices plus voice cloning: * **Large voice library** with voices across different styles and tones * **Voice cloning**: Upload audio samples to clone any voice * **Multiple languages** supported * **Custom voice IDs**: Use any voice from our library Check available voices via `GET /v1/voices/builtin` and `GET /v1/voices`. ```json theme={null} { "voice": "your-voice-id", "mode": "legacy" } ``` ## Language & Dialect Control ### Realtime Mode * **Auto-detection**: Works automatically but best with explicit instructions * **Instruction-based**: Tell the AI "Speak only in Spanish" or "Respond in French" ### Legacy Mode * **Full control**: Set `stt_language` for speech recognition * **32 languages**: en-US, en-GB, en-AU, es-ES, es-MX, fr-FR, de-DE, and more * **Dialect-specific**: Choose British English (`en-GB`) vs American (`en-US`) ```json theme={null} { "mode": "legacy", "stt_language": "en-GB", "voice": "your-preferred-voice-id" } ``` ## Temperature & Creativity Control how creative or focused your AI is: ```json theme={null} { "temperature": 0.7 } ``` * **0.0-0.3**: Very focused, consistent responses (good for confirmations) * **0.4-0.7**: Balanced (default, good for most use cases) * **0.8-1.0**: Creative, varied responses (good for sales, engaging conversations) Not every model takes a custom temperature. `GET /v1/models` reports `supports_temperature` for each one; a model that reports `false` runs at its own default and ignores the value you send. ## First Sentence Set the opening line to control how the call starts: ```json theme={null} { "first_sentence": "Hi {{name}}, this is Rachel from Bright Dental. I'm calling to confirm your appointment tomorrow at 3 PM." } ``` The first sentence is critical. It sets the tone and immediately establishes context. Make it clear, friendly, and specific. ## Best Practices ### Do This * **Be specific**: Include exact scenarios and how to handle them * **Set boundaries**: Define what the AI should and shouldn't do * **Use variables**: Use `{{variable}}` syntax for personalization * **Test thoroughly**: Try different scenarios before going live * **Iterate**: Refine instructions based on real call transcripts ### Avoid This * **Vague instructions**: "Be helpful" is too generic * **Conflicting goals**: Don't ask the AI to both sell and not be pushy without clear boundaries * **Missing context**: Provide relevant information about the customer or situation * **Too long**: Keep instructions focused, around 200-500 words ## Next Steps Browse available voices and learn about voice cloning. Give your AI tools to interact with your systems mid-call. # Campaign Management Source: https://docs.topcalls.ai/concepts/campaigns Outbound dialling queues and inbound answering campaigns. Outbound processes contact lists with timezone awareness and retries; inbound answers calls on an attached phone number. ## What Are Campaigns? A campaign is either **outbound** (we dial leads from a list) or **inbound** (a phone number routes its calls here). Set `direction` when you create it; omit it and the campaign is outbound. Outbound campaigns process contact lists, handle retries, respect timezones, and provide real-time monitoring. Use cases include appointment reminders, post-purchase follow-ups, renewal reminders, and lead qualification for consented contacts. Inbound campaigns answer calls on an attached number. Attach the number, then start the campaign with the same start/pause/resume/stop endpoints as outbound. Callers are answered only while `status` is `running`. See [Inbound Calls](/guides/inbound-calls). Process thousands of contacts efficiently with intelligent queue management and fair-share rate limiting. Automatically respect local business hours. Never call contacts outside their timezone's acceptable hours. Automatically retry busy signals, no-answers, and failed calls with configurable retry logic. Monitor connection rates, completion rates, and more in real-time. ## How Campaigns Work Create a campaign through the TopCalls dashboard. Define the AI configuration (instructions, voice), schedule, and add your contact list. Campaign starts in `draft` status. Upload a CSV or add contacts through the dashboard. Each contact can have custom variables (e.g., `{{name}}`, `{{appointment_date}}`, `{{order_number}}`). Ensure all contacts have proper consent where required. Start via the dashboard or API (`POST /v1/campaigns/{id}/start`). Outbound picks up contacts from the queue and dispatches calls. Inbound starts answering on the attached number and creates no campaign run. Attaching a number without starting leaves the campaign silent. Calls are made respecting: * Account rate limits * Campaign parallel call limits * Timezone restrictions (business hours) * Retry logic * Compliance requirements (opt-out handling, frequency limits) Track real-time stats: calls completed, connection rates, and more. Use `GET /v1/campaigns/{id}` to check progress. Campaign automatically completes when all contacts are processed. You can also pause or stop via API. ## Campaign Statuses | Status | Description | Actions Available | | ----------- | --------------------------------------------------------------------------------------- | ---------------------------- | | `draft` | Not live. Outbound: not dialling. Inbound: not answering, even if a number is attached. | Edit, attach a number, Start | | `scheduled` | Waiting for start time | Edit, Start, Cancel | | `running` | Outbound: dispatching calls. Inbound: answering on the attached number. | Pause, Stop | | `paused` | Temporarily stopped. Inbound: new callers are declined unanswered. | Resume. Outbound also: Stop | | `completed` | All contacts processed | View stats | | `failed` | Stopped by an unrecoverable error | View stats | | `cancelled` | Permanently stopped | View stats | ## API Endpoints Campaigns are created and configured through the TopCalls dashboard. The API provides control and monitoring: | Endpoint | Description | | -------------------------------- | ------------------------------------------------------------------------------------------- | | `GET /v1/campaigns` | List campaigns. Filter with `?direction=inbound` or `?direction=outbound` | | `GET /v1/campaigns/{id}` | Get campaign details (`direction`, `inbound_phone_numbers`) | | `POST /v1/campaigns` | Create. Pass `direction: inbound` for an answering campaign | | `POST /v1/campaigns/{id}/start` | Outbound: start dialling. Inbound: start answering on the attached number | | `POST /v1/campaigns/{id}/pause` | Outbound: stop dialling. Inbound: stop answering | | `POST /v1/campaigns/{id}/resume` | Outbound: start dialling again. Inbound: start answering again | | `POST /v1/campaigns/{id}/stop` | Permanently stop the campaign | | `POST /v1/campaigns/{id}/test` | Place a real, billed test call using the campaign's configuration | | `PATCH /v1/phone-numbers/{id}` | Attach or detach the inbound number. Wiring only; the campaign answers only while `running` | ## Rate Limiting & Fair Share ### Account-Level Rate Limit Every account has a `max_calls_per_minute` setting (default: 20). This controls both API calls and campaign calls. ### Campaign Fair Share If multiple campaigns are running, they share the account limit fairly. Campaigns that want less don't waste capacity for others. **Example**: * Account limit: 20 calls/minute * 2 campaigns running * Each campaign gets: 10 calls/minute (fair share) ## Timezone & Business Hours Contacts are only called during their local business hours: ```json theme={null} { "timezone": "America/New_York", "dispatch_hours": { "start": "09:00", "end": "17:00" } } ``` Timezone awareness helps prevent compliance issues and improves connection rates. Customers are responsible for compliance with local calling laws (TCPA/TSR/DNC, GDPR). ## Retry Logic Each campaign has a retry schedule that decides when a lead is called again after an unsuccessful attempt. The schedule is a list of slots, one per retry, and each slot is one of: * **A relative delay**: "wait 30 minutes", counted from the previous attempt * **A fixed time**: "at 6:00 PM", on the lead's local clock Retries follow the slots in order until the lead answers or the campaign's maximum attempts per lead is reached. If a fixed-time slot is already in the past when the retry comes due, it is skipped and the next slot applies. Whether a given outcome retries at all depends on its disposition; a caller who asked not to be called again is not retried. Configure the schedule and the attempt cap in the campaign editor. Presets (Gentle, Standard, Aggressive) cover common cases, and every slot stays editable. ## Contact Variables Personalize each call with contact-specific data: ```json theme={null} { "phone_number": "+14155551234", "name": "John Smith", "appointment_date": "2025-12-24", "appointment_time": "3:00 PM" } ``` Use in instructions: ```text theme={null} You are calling {{name}} about their appointment on {{appointment_date}} at {{appointment_time}}. ``` ## Bot Protection Campaign calls include bot protection by default. When the person on the line behaves like an automated system rather than a human (scripted lines, the same non-answer to different questions), the call ends automatically so it doesn't burn minutes. Turn it off per campaign in the editor, or per call with `bot_protection_enabled: false`. ## Voicemail Detection Campaign calls hang up automatically when they reach an answering machine or voicemail in any language, so a mailbox greeting doesn't run up minutes. The assistant may also end the call on its own if it recognizes an automated system. ## Campaign Analytics Track key metrics in real-time: | Metric | Description | | ------------------- | ------------------------------ | | **Total Contacts** | Number of contacts in campaign | | **Calls Completed** | Successfully finished calls | | **Calls Pending** | Still in queue | | **Calls Failed** | Technical failures | | **Connection Rate** | % of calls that connected | | **Completion Rate** | % that completed successfully | | **Avg Duration** | Average call length | ## Best Practices ### Do This * **Verify consent**: Ensure all contacts have proper consent where required * **Start small**: Test with 10-20 contacts before scaling * **Monitor closely**: Watch connection rates and adjust * **Respect timezones**: Always configure timezone and business hours * **Set retry limits**: Don't retry indefinitely * **Use variables**: Personalize calls with contact data * **Honor opt-outs**: Immediately remove contacts who request to be removed ### Avoid This * **Call without consent**: Never call contacts who haven't opted in where required * **Ignore rate limits**: Respect account and campaign limits * **Call outside hours**: Always use timezone awareness * **Skip testing**: Test your instructions on a few calls first * **Over-retry**: Set reasonable max attempts ## Next Steps Learn how to make individual calls via API. Set up webhooks to receive real-time campaign and call events. # Crew (Multi-Agent Handoffs) Source: https://docs.topcalls.ai/concepts/crew Run a call with multiple specialised agents that hand off to each other under controlled conditions. Crew is rolling out gradually. Availability may vary by account while this feature is being enabled. ## What Is a Crew? A crew lets a single call be handled by more than one AI agent. Instead of one set of instructions trying to cover every part of a conversation, you define several members, each with their own instructions, and the call moves between them when specific conditions are met. One member starts the call. When the conversation reaches a point you defined, that member hands off to another member, who takes over with their own instructions and tone. A handoff can only happen along a path you set up, and a few built-in guards stop a crew from looping or bouncing a call back and forth. Use cases include a sales agent that hands off to a scheduling specialist, or a first-line agent that hands off to a member with access to the knowledge base for detailed questions. ## Crew Members Each crew has a `members` array. Every member is an object with these fields: | Field | Type | Required | Description | | -------------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | string | Yes | Short identifier for the member, unique within the crew. | | `name` | string | Yes | Display name for the member (for example, "Alex"). | | `instructions` | string | Yes | This member's own instructions, up to 8000 characters. | | `use_knowledge_base` | boolean | No | When true, your knowledge base is added to this member's instructions. When false or absent, this member does not use the knowledge base. | | `knowledge_base_ids` | array | No | Optional subset of this campaign's attached knowledge base entry ids visible to this member. When absent, `use_knowledge_base` controls all-or-none access. An empty array means this member gets no knowledge base entries. | | `mcp_tool_allowlist` | array | No | Optional subset of the campaign-level `mcp_tool_allowlist` available to this member. When absent, the member inherits the campaign-level tool list. An empty array means this member gets no remote tools. | | `handoffs` | array | No | The list of members this member can hand off to, and when. Empty or absent means this member cannot hand off (a terminal member). | The first member in the `members` array is the one who answers the call. ## How Handoffs Work Each entry in a member's `handoffs` array is a condition pointing at another member: | Field | Type | Required | Description | | ------ | ------ | -------- | ------------------------------------------------------------------------------- | | `to` | string | Yes | The `id` of the member to hand off to. Must be another member in the same crew. | | `when` | string | Yes | Plain text describing the condition for this handoff, up to 300 characters. | The `when` text is descriptive, not code. Write it the way you would explain the condition to a person, for example "the caller wants to book a specific appointment time" or "the caller is asking a detailed product question." ### Handoff Guards Three checks apply to every handoff, so a crew cannot loop or run away with a call: 1. **Only listed destinations.** A member can only hand off to a member listed in its own `handoffs`. A handoff to any other member, or to an id that does not exist in the crew, does not happen. 2. **Handoff limit.** Each crew has a maximum number of handoffs allowed in a single call. This is set with `max_handoffs`, an integer from 1 to 10. If you don't set it, the default is 3. Once a call reaches the limit, no further handoffs happen for the rest of that call. 3. **No immediate bounce-back.** A member cannot immediately hand a call back to the member who just handed it to them. This prevents two members from passing the call back and forth on the same turn. ### Invalid Configuration Falls Back to a Single Agent If a crew configuration does not meet the requirements described here (for example, too few or too many members, a missing field, or a handoff pointing at an id that doesn't exist), the call does not fail. It runs as a normal single-agent call using the first member's instructions, and the invalid crew configuration is skipped for that call. ## Limits | Limit | Value | | ----------------------- | --------------------- | | Members per crew | 2 to 5 | | Instructions per member | up to 8000 characters | | `when` condition length | up to 300 characters | | `max_handoffs` | 1 to 10 (default 3) | ## Example A crew is set inside the `config` object of a campaign, under the `crew` key: ```json theme={null} { "config": { "mode": "legacy", "mcp_tool_allowlist": ["book_appointment", "send_pricing_link"], "crew": { "max_handoffs": 3, "members": [ { "id": "intro", "name": "Alex", "instructions": "You are Alex, the first point of contact. Greet the caller, confirm who you're speaking with, and find out whether they want to book an appointment or ask a question about pricing.", "handoffs": [ { "to": "scheduler", "when": "the caller wants to book, reschedule, or confirm a specific appointment time" }, { "to": "pricing", "when": "the caller is asking about pricing or plan details" } ] }, { "id": "scheduler", "name": "Jordan", "instructions": "You are Jordan, the scheduling specialist. Confirm the caller's preferred date and time and lock in the appointment.", "use_knowledge_base": false, "mcp_tool_allowlist": ["book_appointment"] }, { "id": "pricing", "name": "Sam", "instructions": "You are Sam, the pricing specialist. Answer pricing questions using the knowledge base and offer to transfer back to booking once the caller is ready.", "use_knowledge_base": true, "mcp_tool_allowlist": ["send_pricing_link"], "handoffs": [ { "to": "scheduler", "when": "the caller is ready to book after getting pricing information" } ] } ] } } } ``` ## Setting Crew Per Call A crew can also be set directly on an individual call at call creation, using the same `crew` object shape, instead of at the campaign level. ## Next Steps Learn how crew fits into campaign configuration. Learn how to write effective instructions for a single agent. # How TopCalls Works Source: https://docs.topcalls.ai/concepts/overview Understanding the architecture and capabilities of the TopCalls platform. ## Platform Overview TopCalls bridges traditional telephony (SIP/PSTN) and modern AI to automate phone interactions at scale. We handle the hard parts: telephony infrastructure, AI orchestration, audio processing, and compliance tooling. You focus on what your agents say and do. ## What TopCalls Does SIP trunking, carrier registration, number provisioning, and audio streaming. No telecom expertise needed. Real-time speech recognition, intelligent conversation handling, and natural voice synthesis. All optimized for phone conversations. Queue management for automated outbound calls with retry logic, timezone awareness, and compliance tooling. Automatic call summaries, sentiment analysis, structured data extraction, and reporting. ## System Architecture ``` ┌──────────────────────────────────────┐ │ Your Application / SaaS App │ │ (User Management, Campaigns, │ │ Analytics Dashboard) │ └──────────────┬───────────────────────┘ │ │ REST API ▼ ┌──────────────────────────────────────┐ │ TopCalls Voice Gateway │ │ │ │ ✅ Call Execution & Control │ │ ✅ AI Conversation Handling │ │ ✅ Campaign Queue & Dispatch │ │ ✅ Quota Management │ │ ✅ Transcript & Recording │ │ ✅ Post-Call Analysis & Webhooks │ │ ✅ Telephony Infrastructure │ │ ✅ Audio Processing & Streaming │ └──────────────────────────────────────┘ ``` ## Conversation Modes TopCalls supports two modes, each optimized for different use cases: ### Realtime Mode (Recommended) Speech-to-speech processing for the most natural conversations. | Feature | Details | | ------------- | --------------------------------------------------- | | **Latency** | Ultra-low (\~200-500ms) | | **Voices** | Preset voices (default: `alloy`) | | **Languages** | Auto-detects, works best with explicit instructions | | **Best For** | Customer support, appointment management | Realtime Mode provides the most natural conversations with the lowest latency. Use `mode: "realtime"` in your API calls. ### Legacy Mode (Maximum Customization) Separate speech recognition, language model, and voice synthesis for full control over each stage. | Feature | Details | | ------------- | ------------------------------------------------------ | | **Latency** | Standard (\~300-600ms) | | **Voices** | Hundreds of built-in voices + voice cloning | | **Languages** | 32 languages with explicit dialect control | | **Best For** | Brand-specific personas, voice cloning, multi-language | Check available models and voices via `GET /v1/models` and `GET /v1/voices/builtin`. Legacy Mode gives you full control over voice, language, and model selection. Use `mode: "legacy"` in your API calls. ## The Call Lifecycle Every call goes through these stages: You trigger a call via API (`POST /v1/calls`) or it's dispatched from a campaign. The system validates your request and reserves quota. The call is dispatched to our telephony infrastructure. Status changes to `queued` then `in_progress`. The recipient picks up. If you set `first_sentence`, that line is spoken first. If you omit it, the callee hears silence until the agent times out and then improvises an opening. Set `first_sentence` when you want a controlled start. Audio streams in real-time. The AI: * Transcribes speech * Processes with the language model (with knowledge base context if configured) * Responds naturally with voice synthesis * Executes tools/functions as needed * Can end the call gracefully when the conversation is complete Call ends (either by user or AI). The system: * Captures final transcript * Fetches recording URL (available \~15s after call ends) * Generates call summary (if configured) * Extracts structured data from transcript (if `analysis_schema` provided) * Maps analysis fields to outcomes using `outcome_mapping` rules Your server receives a webhook with complete call details including transcript, recording URL, call summary, structured analysis data, and all custom metadata. ## Key Features ### Intelligent Routing Automatically detect voicemail, IVR systems, or human answers. Route accordingly or handle each scenario with custom logic. ### Function Calling Give your AI agents tools to interact with your systems during calls: book appointments, look up orders, update CRMs, process payments, and end calls gracefully. ### Knowledge Base Injection Add text entries with the product, pricing, and policy details your agents need. The AI automatically accesses the attached context during conversations. ### Structured Data Extraction Define schemas to extract specific information from calls: "Did the customer agree to a demo?", "What objections were raised?", "What's the next step?" ### Multi-Language Support 32 languages with proper dialect control. Use for operations across multiple regions. ## What You Control | Aspect | Your Control | | --------------------- | ------------------------------------------------------- | | **AI Instructions** | Full control over persona, goals, and behavior | | **Voice Selection** | Choose from built-in voices or use custom/cloned voices | | **Call Flow** | Define first sentence, handle objections, set goals | | **Tools & Functions** | Integrate with your systems in real-time | | **Knowledge** | Provide context via knowledge bases | | **Analytics** | Define what data to extract from calls | ## What We Handle | Aspect | TopCalls Responsibility | | ---------------------- | ---------------------------------------------------------------- | | **Telephony** | SIP trunking, carrier management, number provisioning | | **Audio Processing** | Real-time streaming, VAD, echo cancellation | | **AI Orchestration** | Speech recognition, language model, and voice synthesis pipeline | | **Infrastructure** | Scaling, reliability, monitoring | | **Compliance Tooling** | Features to help honor local calling laws (TCPA/TSR/DNC, GDPR) | **Compliance Responsibility**: TopCalls provides production-ready compliance tooling, but customers remain responsible for ensuring lawful use of the platform. ## Next Steps Control your agent's personality, voice, and behavior. Scale your outbound calling with campaign features. # Webhooks & Events Source: https://docs.topcalls.ai/concepts/webhooks Receive notifications when calls finish. Understand the event model, delivery timing, and retry behavior. ## What Are Webhooks? Webhooks are HTTP callbacks that TopCalls sends to your server when events occur. They're the best way to integrate TopCalls with your existing systems. Get notified when calls complete or fail. No polling required. Receive full call details, transcripts, recordings, and analysis in a single webhook payload. Built-in retry logic ensures webhooks are delivered even if your server is temporarily unavailable. Subscribe to specific dispositions or conversions instead of receiving every event. ## How Events Are Delivered There are two delivery channels: | Channel | How to set up | What it receives | | -------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------- | | Per-call `webhook_url` | Set on `POST /v1/calls` (or in the campaign editor) | One payload when that call completes, fails, or is cancelled | | Account-level subscription | `POST /v1/webhooks` | Every matching event on the account, regardless of how the call was created | ## Available Events Subscriptions use these event names: | Event | When It Fires | | ------------------------------ | ---------------------------------------------------------------------------------- | | `call.completed` | Any call finishes and post-call processing is done | | `call.completed.` | A call finishes with a specific disposition, e.g. `call.completed.booked_callback` | The disposition suffix is a slug of the label assigned by post-call analysis (`Booked Callback` becomes `booked_callback`). Combine events with `filters` on the subscription, including the `is_conversion` flag, to narrow delivery further. The event names `lead.status_changed`, `campaign.activated`, and `campaign.paused` are reserved for future use. The API accepts subscriptions for them, but they do not fire yet. Per-call webhooks carry a `status` field (`completed`, `failed`, or `cancelled`), so a single endpoint can handle successful and failed calls. ## Webhook Timing ### Successful Calls Webhooks are sent **\~15 seconds after call ends** to ensure: * Recording URL is available * Transcript is complete * Call summary is generated (if configured) ### Failed Calls Webhooks are sent **immediately** when the call fails (no recording to wait for). ## Webhook Retries TopCalls automatically retries failed webhook deliveries: * **Initial attempt**: Immediate * **Retry 1**: After 1 minute * **Retry 2**: After 5 minutes * **Retry 3**: After 15 minutes * **Retry 4**: After 30 minutes * **Retry 5**: After 1 hour After 5 retries, the webhook is marked as failed. You can still retrieve call data via the API. ## Next Steps Learn how to set up and handle webhooks in your application. See the complete API documentation. # Crews Source: https://docs.topcalls.ai/guides/crews Split one campaign across a small team of focused AI agents that pass the live call to each other. ## What Is a Crew? A Crew is a small team of AI agents working the same campaign. Instead of one agent carrying every script, policy, and edge case, each member handles one part of the conversation and passes the live call to the teammate best placed to continue. The caller hears one voice and one continuous conversation. The handoff happens mid-call, and the next member picks up with everything said so far. Crews are opt-in. Every campaign starts as a single agent, and most campaigns work best that way. Reach for a Crew when one agent's instructions have grown so long that answers get muddy. Each member follows a short, focused set of instructions instead of one giant script, so replies stay on point. On knowledge-heavy campaigns, only the members that need the knowledge base carry it. The rest stay lean and fast. A qualifier that only qualifies and a closer that only closes are each harder to confuse than one agent doing both. Switch the Crew off and the campaign returns to a single agent immediately. Nothing else changes. ## The Member Model A Crew has 2 to 5 members. The first member in the list answers the call. Every member has: * **A name.** Used inside the team so members know who they can hand the call to. The caller never hears it unless a member introduces itself. * **Its own instructions.** Written the same way you write campaign instructions today, but scoped to that member's job. Up to 8,000 characters per member. * **Handoff rules.** A list of teammates this member can pass the call to, each with a plain-language condition describing when. * **Knowledge base access.** Controls what this member reads from the campaign's attached knowledge base. With one knowledge base attached, this is a simple on or off. With two or more attached, you pick exactly which ones this member carries. Members share the campaign's voice, language, and call settings. A handoff changes who is thinking, not how the call sounds. ## Handoffs Each handoff rule names a destination member and a condition. Write the condition as a sentence about the conversation, describing the moment the call should move: * "When the caller agrees to book a time" * "When the caller asks a detailed pricing or product question" * "When the caller raises a complaint about a past order" The active agent reads its own rules and decides during the call whether the moment has arrived. Base conditions on what the caller says and wants, never on how long the call has run. "After a few exchanges" or "once three questions are answered" gives the agent nothing concrete to recognize, and calls rarely follow the length you predict. A member with no handoff rules keeps the call until it ends. That is normal for a closer or a wrap-up member. Handoffs are silent by default. The next member picks up the conversation directly, and since every member speaks with the same voice, nothing marks the moment the call changes hands. Members do not announce a transfer or mention a colleague. If you want a member introduced by name instead, say so in the instructions of the member handing the call over, and it will make that introduction before passing the call. Choose one way per Crew: leave the instructions silent for a seamless single-person call, or write the introduction in for a warm-transfer feel. Design the flow to move forward. In a stage-style Crew (open, then present, then close), give each member only the handoff rules that move the call to the next stage, and let each member answer the ordinary questions in its own stage rather than passing them back. Members that hand the call backward as well as forward tend to pass the caller around; a clean Crew reads as one steady progression. Each call also has a handoff budget. By default a call can hand off 3 times; you can set the cap anywhere from 1 to 10. Once the budget is spent, the active member finishes the call on its own. The platform also blocks a member from bouncing the call straight back to whoever just passed it, so two members cannot trade the caller back and forth. ## The Knowledge Base The campaign's knowledge base attaches per member, not per campaign. Each member reads only the part its job needs. How you choose depends on how many knowledge bases the campaign has attached: * **One knowledge base attached.** Each member gets a simple on or off switch. Turn it on for the members that answer factual questions and off for everyone else. * **Two or more attached.** Each member gets a checklist of the attached knowledge bases by name. Tick the ones that member should carry. Leave every box unticked and the member carries none. This is where knowledge-heavy campaigns save the most. A greeter that only opens the call and routes it has no reason to carry your full product catalog on every reply. Give the price sheet to the member that quotes prices and the policy guide to the member that handles complaints, and every member stays lean, answers faster, and is less likely to reach for the wrong document. ## Where to Find It Crews live in the campaign editor, on the prompt step. Enable the Crew, add members, write each member's instructions, and set the handoff rules between them. Saving the campaign saves the Crew with it. Get the single-agent version producing decent calls first. A Crew multiplies the quality of the instructions you give it, good or bad. Two or three members with clear jobs (open and qualify, answer product questions, book the appointment) beat five members with overlapping duties. One sentence per rule, describing the conversational moment. Read each one and ask: could a colleague listening in recognize this moment? If not, rewrite it. Give each member only the knowledge it needs. With one knowledge base attached, that is an on or off switch per member. With two or more, tick the specific ones each member should carry. Place a test call and steer the conversation toward each handoff moment. Confirm the call moves to the right member and the caller notices nothing. ## Limits | Limit | Value | | ------------------------ | ------------------- | | Members per Crew | 2 to 5 | | Instructions per member | 8,000 characters | | Handoff condition length | 300 characters | | Handoffs per call | 1 to 10 (default 3) | If a Crew configuration is incomplete, the campaign runs as a single agent using the campaign's normal instructions. Calls never fail because of a Crew setting. ## Solo by Default Crew is a switch, not a migration. Campaigns without a Crew behave exactly as they always have, and turning a Crew off returns the campaign to a single agent on the next call. Your original campaign instructions stay untouched either way. ## Next Steps Build the knowledge your specialist members will answer from. See how campaign instructions, voices, and schedules fit together. # Voice Library & Cloning Source: https://docs.topcalls.ai/guides/custom-voices Choose from hundreds of built-in voices or clone your own. Create brand-consistent AI agents that sound exactly how you want. ## Voice Options Overview TopCalls offers different voice options depending on the conversation mode you choose: | Mode | Voice Options | Voice Cloning | Languages | Latency | | ------------ | --------------------------- | ------------- | ----------- | ----------- | | **Realtime** | Preset voices | No | Auto-detect | \~200-500ms | | **Legacy** | Hundreds of built-in voices | Yes | 32 | \~300-600ms | Check available voices via the API: * All built-in voices: `GET /v1/voices/builtin` * Your account's voices (including cloned): `GET /v1/voices` Voice availability may change over time. Use the API endpoints for the most current list. ## Realtime Mode Voices In Realtime Mode, choose from preset voices optimized for low-latency conversations. The default is `alloy`. ```json theme={null} { "mode": "realtime", "voice": "alloy" } ``` Realtime Mode voices are optimized for ultra-low latency and natural conversation. Check available options via `GET /v1/voices/builtin`. ## Legacy Mode: Full Voice Library Legacy Mode gives you access to hundreds of voices across different styles, tones, and languages. ### Using Built-in Voices Browse available voices via `GET /v1/voices/builtin` and use the voice ID in your calls: ```json theme={null} { "mode": "legacy", "voice": "voice-id-from-api" } ``` ### Voice Cloning Clone any voice with a short audio sample through the TopCalls dashboard: **Step 1: Prepare Audio Samples** * 1-5 audio files (MP3, WAV) * At least 1 minute total duration * Clear, high-quality recordings * Single speaker, minimal background noise **Step 2: Clone via Dashboard** Upload your audio samples through the TopCalls dashboard. The platform processes them and creates a custom voice for your account. **Step 3: Use Cloned Voice** Once cloned, use the voice ID in your API calls: ```json theme={null} { "mode": "legacy", "voice": "your_cloned_voice_id" } ``` Your cloned voices appear in the `GET /v1/voices` endpoint alongside built-in voices. Voice cloning requires high-quality audio samples. Poor quality samples produce poor voice quality. Use professional recordings when possible. ## Choosing the Right Voice ### For Customer Support Choose warm, professional voices that sound patient and helpful. Check available voices via the API and test a few to find the best fit. ### For Sales & Outreach Choose confident, energetic voices that sound engaging. Match the voice to your brand personality. ### For Brand Consistency Clone your brand spokesperson's voice and use the same voice across all channels. ## Multi-Language Voices ### Legacy Mode with Language Control ```json theme={null} { "mode": "legacy", "stt_language": "es-ES", "voice": "spanish-voice-id" } ``` Legacy Mode supports 32 languages with proper dialect control. Set `stt_language` for accurate speech recognition and choose a voice that matches the target language. Supported languages include English, Spanish, German, French, Dutch, Italian, Japanese, and many more. ## Best Practices ### Do This * **Test voices**: Try different voices to find the best fit for your use case * **Match tone**: Choose voices that match your brand personality * **Consider the use case**: Support calls need different voices than sales calls * **Use cloning for brands**: Clone spokesperson voices for consistency * **Test quality**: Always test cloned voices before production ### Avoid This * **Ignore latency**: Realtime Mode is faster but has fewer voice options * **Poor audio samples**: Use high-quality recordings for cloning * **Mismatched languages**: Make sure the voice language matches your instructions * **Too many voices**: Stick to 1-2 voices for consistency ## Next Steps Control your AI's personality and behavior. Use custom voices when making calls. # Function Calling & Tools Source: https://docs.topcalls.ai/guides/function-calling Give your AI agents tools to act during calls. Book appointments, update your CRM, send confirmations, and end calls gracefully. ## What Is Function Calling? Function calling lets your AI agent execute actions during a call. Instead of just talking, your agent can: * Book appointments in your calendar * Look up orders in your systems * Update CRM records * Send confirmations * End the call gracefully when the conversation is complete The agent invokes tools mid-conversation and uses the results in its next reply. Callers don't notice the tool call. It feels like talking to an agent who has your systems open in front of them. Build tools visually in the Integrations platform, or connect your own MCP server through the API. Only tools you allowlist are available to the agent. Nothing attaches by default. ## Built-in Tools TopCalls provides built-in tools that are automatically available during calls. ### end\_call The `end_call` tool allows your AI agent to gracefully end the call when the conversation is complete. The AI uses `end_call` automatically when appropriate. The agent decides when to end based on context: user says goodbye, all questions answered, or clear conversation conclusion. **When the AI uses end\_call:** * User says "goodbye", "thanks, that's all", etc. * All questions have been answered * User explicitly asks to end the call * Conversation naturally concludes **What happens:** 1. AI decides to end the call 2. AI speaks a contextual farewell message 3. Call is terminated You can influence end-call behavior in your instructions: "Always confirm the next steps before ending the call" or "Offer to help with anything else before saying goodbye." ## Custom Tools There are two ways to give the agent custom tools. ### Through the Integrations Platform (No Code) Build a flow in the Integrations platform (for example "book a callback in Google Calendar" or "send an SMS confirmation"), then select it as a tool in the campaign editor. During calls, the agent can invoke the flow and use its result in the conversation. See [Integrations](/integrations) for the full catalog of connectable tools. ### Through Your Own MCP Server (API) If you run your own tool server that speaks the [Model Context Protocol](https://modelcontextprotocol.io), connect it per call: ```json theme={null} { "phone_number": "+14155551234", "instructions": "You are Rachel, an appointment coordinator...", "mcp_url": "https://tools.your-app.com/mcp/abc123", "mcp_token": "YOUR_MCP_BEARER_TOKEN", "mcp_tool_allowlist": ["check_availability", "book_appointment"], "tool_call_timeout_ms": 3000 } ``` | Field | What it does | | ---------------------- | --------------------------------------------------------------------------------------------------------- | | `mcp_url` | Your MCP server URL. The gateway connects at call start and merges your tools into the agent's tool list. | | `mcp_token` | Bearer token the gateway sends to your server. Redacted from logs. | | `mcp_tool_allowlist` | Names of the tools the agent may invoke. When absent or empty, no remote tools are attached. | | `tool_call_timeout_ms` | Per-invocation timeout (500 to 10000 ms, default 3000). | The allowlist is an explicit opt-in. Listing your server with `mcp_url` alone attaches zero tools; name each tool the agent should have. This keeps the prompt small and the agent focused. ## How a Tool Call Works During the conversation, the AI decides a tool is needed based on what the caller asked. The gateway invokes the tool with the arguments the AI chose. The tool's result is fed back to the AI. The AI works the result into its next reply and the conversation continues. ``` User: "Can you check if 3 PM tomorrow is available?" AI: [Invokes check_availability with date and time] → Tool returns: {"available": true} AI: "Yes, 3 PM tomorrow is available! Would you like to book it?" ``` ## Error Handling If a tool call fails or times out, the AI is told and recovers conversationally: > "I'm having trouble reaching the calendar right now. Can I take your preferred time and have someone confirm it shortly?" Design your tools to return clear error messages; the AI relays the situation naturally instead of exposing raw errors to the caller. ## Best Practices ### Do This * **Clear names and descriptions**: The AI picks tools based on their descriptions, so write them precisely * **Validate inputs**: Always validate arguments on your side before acting on them * **Return structured data**: Use consistent response formats * **Fast responses**: Return results quickly (under 2 seconds) so the conversation stays natural * **Allowlist narrowly**: Give the agent only the tools this call needs ### Avoid This * **Vague descriptions**: "Helper function" doesn't help the AI decide when to use it * **Slow tools**: Long tool calls create awkward silence on the line * **Too many tools**: Limit to 5-10 tools per call for best results * **Secrets in prompts**: Keep credentials on your server, never in instructions ## Next Steps Build no-code tools and flows for your agent. Learn how to include tools when making calls. # Inbound Calls Source: https://docs.topcalls.ai/guides/inbound-calls Answer calls to your phone numbers with an inbound campaign. Greeting, who speaks first, hours, duration and webhook live on the campaign. Read the results through the same call object and webhook you already use. ## How It Works An inbound call is a call someone places to one of your TopCalls phone numbers. You decide which campaign answers it. 1. You attach a campaign to a phone number with `PATCH /v1/phone-numbers/{phone_number_id}`. 2. You start the campaign with `POST /v1/campaigns/{id}/start`. Until then the number stays silent, even if it is attached. 3. When the number rings, TopCalls checks that the campaign is `running`, then the campaign's business hours and your account's concurrency limit. 4. The call is answered and the caller hears the campaign's `first_sentence`. 5. The campaign takes over the conversation, exactly as it does on an outbound call: same prompt, same knowledge base, same tools. 6. When the call ends you receive the usual completion webhook, and the call appears in `GET /v1/calls` with `direction: "inbound"`. Greeting, who speaks first, max duration and webhook live on the campaign, not on the number. Enabling fails with `422` when the campaign has no `first_sentence`. Inbound calls have no lead, so a `first_sentence` that still contains a `{{...}}` placeholder is rejected with `422` (`code: "greeting_placeholder"`). Write the opening line as an answer: who is speaking, and what the caller can ask for. ## Campaign direction An inbound campaign has `direction: "inbound"`. Create it, attach a number with `PATCH /v1/phone-numbers/{id}`, then `POST /v1/campaigns/{id}/start` — the same start/pause/resume/stop as outbound. It is never dialled and needs no lead list. Callers are answered only while `status` is `running`. Pause stops answering; resume starts again; stop completes it. `POST /v1/campaigns/{id}/test` still places a billed outbound test call to a number you pass, using this campaign's prompt. A number can only be routed to an inbound campaign. Switching a draft campaign back to `outbound` while a number still routes to it returns `409` (`code: "campaign_has_inbound_routes"`); disable the routing first. Bring-your-own-trunk (BYOT) numbers must originate from static source ranges, or use registration or credential authentication. A carrier that sends inbound calls from more than one address declares each range as its own inbound gateway: the primary gateway on `sip_gateway`, and any additional ranges in `inbound_gateways`. Each inbound range is an IPv4 address or a CIDR (`ipv4` plus an optional `netmask`, defaulting to `32`); hostnames are rejected for inbound because inbound calls are matched by source IP. A BYOT number whose carrier has no active inbound gateway is refused at enable time with `422` (`code: "carrier_no_inbound_gateway"`). For a provider that originates from several ranges, add the carrier with every range declared: ```json theme={null} { "carrier_name": "My SIP trunk", "trunk_type": "static_ip", "sip_gateway": { "ipv4": "203.0.113.0", "netmask": 30, "inbound": true, "outbound": true }, "inbound_gateways": [ { "ipv4": "203.0.113.4", "netmask": 30 }, { "ipv4": "198.51.100.0", "netmask": 30 } ] } ``` The primary `sip_gateway` still carries outbound dialling; each `inbound_gateways` entry is inbound-only. Omitting `inbound_gateways` keeps the original single-gateway behaviour. ## Business Hours By default a running inbound campaign answers around the clock. Restrict the window on the campaign (`config.inbound_business_hours`): timezone, weekdays (`0` is Sunday), and `HH:MM` start/end. An overnight window (`start` after `end`) stays open past midnight on the weekday the night started on. Outside that window the call is declined without being answered. Sending `business_hours` on the number is rejected with `400`. ## Answering Style Who speaks first is a campaign setting (`first_sentence_wait_for_caller`): * Off (default) speaks the campaign `first_sentence` as soon as the call is answered. * On answers the line silently and lets the caller speak first, then speaks the greeting after the caller's first sentence (waiting at most 5 seconds, or the campaign's `first_sentence_delay_seconds`). ## Concurrency and Duration * Each account can have a limited number of inbound calls in progress at the same time. When the limit is reached, additional callers are declined until a call ends. * The campaign's `max_duration` caps an inbound call in minutes (1 to 60). When it is reached the agent wraps up and the call ends. * Inbound calls consume plan minutes the same way outbound calls do. When the balance is exhausted, the caller hears a short unavailable message and the call ends. ## What You Receive ### The call object Inbound calls appear in `GET /v1/calls` and `GET /v1/calls/{call_id}` alongside outbound calls. These fields tell them apart: | Field | Outbound | Inbound | | ------------------- | ---------------------- | --------------------------- | | `direction` | `"outbound"` | `"inbound"` | | `phone_number` | The number you dialled | The caller's number | | `from_phone_number` | Your caller ID | Your number that was called | `phone_number` is always the other party, so `GET /v1/calls?phone_number=+40...` finds every conversation with that person in either direction. Filter by direction with `GET /v1/calls?direction=inbound`. A caller who withholds their number is recorded with `phone_number` set to `"+10000000000"`. ### The webhook The completion webhook carries the same payload as for outbound calls, plus: * `direction`: `"inbound"` or `"outbound"`. * `caller_name`: the name presented by the caller's network, when available. Present only on inbound calls; `null` when the network did not provide one. Inbound calls use the campaign's webhook. ## Enabling Inbound on a Number The number must be `active` and the campaign must belong to your account, have `direction: "inbound"`, not be `cancelled` or `completed`, and use the standard (non-realtime) mode. `campaign_id` is required when enabling. The campaign must already have a `first_sentence`. Greeting, who speaks first, hours, max duration and webhook live on the campaign. Sending those fields on `inbound` is rejected with `400`. Enabling wires the number. The campaign answers only after `POST /v1/campaigns/{id}/start` (and while `status` stays `running`). ```bash theme={null} curl -X PATCH https://api.topcalls.ai/v1/phone-numbers/27bc24c9-0d16-47fe-bc1b-6b22924e9996 \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "inbound": { "enabled": true, "campaign_id": "8f1c1a2e-3b4d-4c5e-9f6a-7b8c9d0e1f2a" } }' ``` The response is the phone number object with its stored inbound routing: ```json theme={null} { "id": "27bc24c9-0d16-47fe-bc1b-6b22924e9996", "number": "+40742600785", "status": "active", "inbound": { "enabled": true, "campaign_id": "8f1c1a2e-3b4d-4c5e-9f6a-7b8c9d0e1f2a" } } ``` Enabling runs the campaign prompt through the same safety review as an outbound campaign. A refused prompt returns `422` with a `code` naming the reason, and the number stays unchanged. Each enabling request is the complete routing for the number: the stored settings are replaced by what you send, and any optional field you leave out returns to its default. To change one setting, resend the whole `inbound` object with the change applied. ### Errors | Status | `code` | Meaning | | ------ | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `403` | — | The `campaign_id` belongs to another account. | | `404` | — | The phone number or the campaign was not found (a deleted campaign counts as not found). | | `409` | `inbound_number_conflict` | Another number with the same digits already answers inbound calls. | | `409` | `inbound_already_routed` | This number already answers for another inbound campaign. Disable it there first. | | `400` | — | `inbound` included an unknown field such as `greeting`, `answer_mode`, `max_duration`, `webhook_url` or `business_hours`. Those live on the campaign. | | `422` | — | The number is not `active`, `campaign_id` is missing, or the campaign has no `first_sentence`. | | `422` | `campaign_direction` | The campaign is not an inbound campaign. Set its `direction` to `inbound` first. | | `422` | `campaign_status` | The campaign is `cancelled` or `completed`. | | `422` | `greeting_placeholder` | The campaign `first_sentence` still contains an unresolved `{{...}}` placeholder. | | `422` | `carrier_no_inbound_gateway` | The number is on a customer trunk (BYOT) whose carrier has no active inbound gateway. | | `422` | — | The campaign uses the realtime mode, or the prompt was refused by the safety review. | | `503` | — | Inbound calling is not available in this environment. | ## Disabling Inbound ```bash theme={null} curl -X PATCH https://api.topcalls.ai/v1/phone-numbers/27bc24c9-0d16-47fe-bc1b-6b22924e9996 \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "inbound": { "enabled": false } }' ``` Disabling always succeeds, whatever the state of the number or the campaign. Callers are declined from the next call onwards; a call already in progress runs to its end. The previous settings remain visible on the phone number object, so you can copy them into a new enabling request. Re-enabling goes through the safety review again. Deleting the campaign detaches it from the number: the number stays enabled but declines callers until a new campaign is attached. ## Current Limits * The campaign can connect a caller to a person mid-call when `config.transfer` is set; see [Transfer to a person](/guides/transfer-to-a-person). * Campaigns in the realtime mode cannot answer inbound calls. Use the standard mode. * One number answers with one campaign. To route by menu choice, build the routing into the campaign prompt. ## Related Outbound calls, campaigns and call configuration. The completion payload, retries and filtering. Phone number and call endpoints. # Knowledge Bases Source: https://docs.topcalls.ai/guides/knowledge-bases Give your AI agents the product, pricing, and policy knowledge they need to answer questions accurately during calls. ## What Are Knowledge Bases? Knowledge bases are collections of information your AI agents can draw on during conversations. Instead of packing everything into instructions, you add text entries once and attach them to campaigns. Attached knowledge is provided to the agent as context for the call. No extra API plumbing needed. Add text entries directly — FAQs, product details, pricing, policies — organized by category. Update entries anytime. The next call uses the current content. Maintain one knowledge base and attach it to as many campaigns as you need. ## How Knowledge Bases Work In the TopCalls dashboard, open **Knowledge Base** and add text entries (FAQs, product info, policies), each with a name and category so entries stay organized. In the campaign editor, attach the knowledge base entries the campaign's agent should know. Every call the campaign makes includes the attached knowledge as context for the agent. API calls get the same knowledge when you pass the campaign's `campaign_id`. The agent answers from the attached content instead of guessing. ## Using Knowledge Bases via the API You define and attach knowledge in the dashboard, then reference the campaign when placing a call. Add `campaign_id` to a normal call and the agent picks up that campaign's attached knowledge base — no other campaign-execution fields are needed: ```json theme={null} { "phone_number": "+14155551234", "task": "Answer customer questions about our products and services.", "campaign_id": "0c4f4b9e-8b0a-4f57-9d3e-2f1a7c9d1234" } ``` The gateway loads the campaign's attached knowledge base and includes it in the call's runtime context. You can also create and manage entries programmatically instead of in the dashboard. See the **Knowledge Bases** section of the [API reference](/api-reference/introduction) for the list, get, create, update, and delete endpoints. ## Example: Customer Support Agent ### Step 1: Create Knowledge Entries Through the dashboard, add your support documentation: ``` Product Features: - AI Voice Agents - Campaign Management - Custom Voices - Function Calling Common Issues: - Call not connecting: Check phone number format (E.164) - No audio: Verify microphone permissions - Poor quality: Check internet connection ``` ### Step 2: Attach and Call Attach the entries to your support campaign, then reference it on calls: ```json theme={null} { "phone_number": "+14155551234", "instructions": "You are a customer support agent. Answer questions about our products and help troubleshoot issues. Use the knowledge base to provide accurate information.", "campaign_id": "0c4f4b9e-8b0a-4f57-9d3e-2f1a7c9d1234" } ``` ## Best Practices ### Do This * **Organize by topic**: Create separate entries for different topics (products, pricing, support) * **Keep it updated**: Regularly update entries with new information * **Use clear structure**: Organize content with clear headers and sections * **Test thoroughly**: Run test calls and check the agent answers from your content * **Keep it focused**: Everything attached to the campaign is given to the agent, so lean content keeps calls fast ### Avoid This * **Too much information**: Oversized knowledge bases slow the agent down and dilute answers * **Outdated content**: Regularly review and update * **Vague content**: Use clear, specific information * **Duplicate content**: Avoid overlapping information across entries ## Next Steps Learn how calls pick up campaign knowledge. See the complete API documentation. # Making Calls Source: https://docs.topcalls.ai/guides/making-calls Learn how to make AI phone calls via the TopCalls API. From simple reminders to complex conversations with function calling. ## Making Your First Call The simplest way to make a call is with a `task`: ```bash theme={null} curl -X POST https://api.topcalls.ai/v1/calls \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number": "+14155551234", "from_number": "+18005551234", "task": "You are calling to confirm John's dental appointment tomorrow at 3 PM. Be friendly and professional.", "voice": "alloy" }' ``` ## Simple vs Advanced Configuration ### Simple Mode (Task) For straightforward calls: ```json theme={null} { "phone_number": "+14155551234", "task": "Call to confirm appointment...", "voice": "alloy" } ``` ### Advanced Mode (Instructions) For complex conversations with full control: ```json theme={null} { "phone_number": "+14155551234", "instructions": "You are Rachel, a senior scheduler...", "first_sentence": "Hi, this is Rachel from Bright Dental...", "voice": "alloy", "temperature": 0.7, "max_duration": 10 } ``` ## Required Parameters | Parameter | Type | Description | | ------------------------ | ------ | ------------------------------------------------- | | `phone_number` | string | Destination number (E.164 format: `+14155551234`) | | `task` OR `instructions` | string | What the AI should do (one is required) | ## Optional Parameters ### Voice & Mode ```json theme={null} { "voice": "alloy", "mode": "realtime" } ``` Check available voices via `GET /v1/voices/builtin` and available models via `GET /v1/models`. **Realtime Mode** provides the lowest latency (\~200-500ms) and most natural conversations. **Legacy Mode** gives you full control over voice selection and language/dialect settings. Check model and voice availability via the API. ### Call Control ```json theme={null} { "from_number": "+18005551234", "max_duration": 10, "first_sentence": "Hi, this is Rachel...", "background_audio": "office", "background_audio_gain": "medium" } ``` If you omit `first_sentence`, the callee hears silence until the agent times out and then improvises an opening. Set it when you want a controlled start. ### Context & Personalization ```json theme={null} { "lead_context": { "patient_name": "John Smith", "appointment_date": "2025-12-24" }, "campaign_id": "0c4f4b9e-8b0a-4f57-9d3e-2f1a7c9d1234" } ``` `lead_context` values fill `{{variable}}` placeholders in your instructions and first sentence. Pass `lead_id` instead and the gateway builds the context from the stored lead record. `campaign_id` gives the agent the campaign's attached knowledge base. ### Webhooks ```json theme={null} { "webhook_url": "https://your-app.com/webhooks/call-complete" } ``` The URL receives one payload when the call completes, fails, or is cancelled. To limit which outcomes fire it, pass `webhook_call_status_filter` with the statuses you care about (for example `["completed", "failed"]`); leave it out to receive every outcome. For account-wide or disposition-filtered delivery, use [webhook subscriptions](/guides/webhooks). ### Custom Call Summaries Every finished call includes an AI-written summary in the webhook payload and call record. Pass `summary_prompt` to control what it covers: ```json theme={null} { "summary_prompt": "List the objections raised and the agreed callback time." } ``` When omitted, the default summary style applies. ### More Options `POST /v1/calls` also accepts `max_duration` (minutes, up to 60), `background_audio` and `background_audio_gain`, `bot_protection_enabled`, `analysis_schema` for structured extraction, STT and TTS tuning knobs for legacy mode, and an `Idempotency-Key` header for safe retries. See the [API reference](/api-reference/introduction) for every field. ## Complete Example: Appointment Reminder ```json theme={null} { "phone_number": "+14155551234", "from_number": "+18005551234", "instructions": "You are Rachel, a friendly appointment coordinator at Bright Dental. Your goal is to confirm {{patient_name}}'s appointment on {{appointment_date}} at {{appointment_time}}. Be warm, professional, and concise.", "first_sentence": "Hi {{patient_name}}, this is Rachel from Bright Dental. I'm calling to confirm your appointment tomorrow at {{appointment_time}}.", "voice": "alloy", "mode": "realtime", "temperature": 0.7, "max_duration": 5, "lead_context": { "patient_name": "John Smith", "appointment_date": "2025-12-24", "appointment_time": "3:00 PM" }, "webhook_url": "https://your-app.com/webhooks/call-complete" } ``` ## Response ```json theme={null} { "call_id": "564d4fd4-03bc-400a-abe0-05540fbeff88", "status": "queued" } ``` ## Checking Call Status Use the `call_id` to check status: ```bash theme={null} curl https://api.topcalls.ai/v1/calls/564d4fd4-03bc-400a-abe0-05540fbeff88 \ -H "Authorization: Bearer YOUR_API_KEY" ``` ## Call Statuses | Status | Description | | ------------- | ------------------------------------ | | `pending` | Call created, not yet queued | | `queued` | Call waiting to be dispatched | | `in_progress` | Call is active | | `completed` | Call finished successfully | | `failed` | Call failed (busy, no answer, error) | | `cancelled` | Call was cancelled | ## Call Ending Behavior The AI agent can end calls gracefully when the conversation is complete. This happens automatically when: * The user says goodbye or thanks you * All questions have been answered * The user explicitly asks to end the call * The conversation naturally concludes The AI will speak a contextual farewell message before hanging up. You can influence this behavior in your instructions: "Always confirm the next steps before ending the call" or "Ask if there's anything else before saying goodbye." ## Common Patterns ### Pattern 1: Simple Reminder ```json theme={null} { "phone_number": "+14155551234", "task": "Remind the user about their appointment tomorrow at 3 PM. Be brief and friendly.", "voice": "alloy" } ``` ### Pattern 2: Multi-Language Call ```json theme={null} { "phone_number": "+14155551234", "instructions": "Habla solo en español. Eres un asistente de servicio al cliente...", "mode": "legacy", "stt_language": "es-ES", "voice": "your-spanish-voice-id" } ``` ## Error Handling ### Invalid Phone Number ```json theme={null} { "status": 400, "title": "Invalid request body", "errors": [ { "path": "phone_number", "message": "Phone number must be in E.164 format (e.g., +14155551234)" } ] } ``` ### Insufficient Quota ```json theme={null} { "status": 402, "title": "Insufficient Quota", "detail": "You need 5 minutes but have 3.5 remaining" } ``` ## Best Practices ### Do This * **Validate phone numbers**: Use E.164 format (`+14155551234`) * **Set first\_sentence**: Control how the call starts * **Use lead\_context**: Personalize with variables * **Configure webhooks**: Get notified when calls complete * **Test first**: Try with your own number before production ### Avoid This * **Invalid formats**: Always use E.164 format * **Missing instructions**: Provide clear task or instructions * **Too long**: Keep instructions focused (200-500 words) * **Ignore errors**: Handle API errors gracefully ## Next Steps Give your AI tools to interact with your systems. Receive real-time notifications when calls complete. # Test Your Campaign Source: https://docs.topcalls.ai/guides/test-your-campaign Hear each voice before you pick it, and try your campaign in a chat panel on the review step before you launch any calls. ## Why Test First A campaign only pays back when the agent sounds right and says the right things. Two checks in the wizard help you catch problems before you spend a call: a voice preview on the voice step, and a chat panel on the review step. ## Voice Preview On the voice step of the campaign wizard, each built-in voice shows a play button. Click it to hear a short sample of that voice reading a fixed line, then decide whether to pick it. Click again to stop. Selecting a voice is a separate action, so previewing does not change your choice. Voice previews are available for built-in voices. Cloned voices show no preview button since the sample comes from your own recording. ### What To Listen For * **Tone match.** Does the voice fit the persona your campaign describes? * **Pace.** Fast voices sound urgent, slower voices sound patient. Match it to the call type. * **Language fit.** A voice tagged for one language may still speak another, but the accent can carry over. Prefer a voice tagged for the language your leads actually use. ## Campaign Chat Test On the review step, a chat panel lets you talk to your campaign in text. The first message is the campaign's opening line, exactly as it would be spoken at the start of a real call. Type a reply and the agent responds using your campaign's real instructions, first sentence, knowledge base, and tools. The chat runs against the same production configuration your calls will use. If you change an instruction or attach a knowledge base, the next chat message reflects that change. ### What Chat Covers * The agent's opening line, with any `{{lead_name}}` or similar placeholders filled in from the test lead. * The agent's replies to your typed messages, driven by your instructions. * Tool behavior, including when the agent decides to end the call. * The same content checks the call pipeline runs after each reply, surfaced in the chat panel so you can see whether the reply repeats itself or drifts off-language. ### What Chat Does Not Cover Chat is a text simulation. It cannot reproduce everything a real call includes: * Audio behavior. Voice tone, pauses, and interruptions do not exist in text. * Speech recognition. Real calls transcribe what the lead says, so noise and accents can change what the agent hears. * Silence and echo detection. Signals that watch for a stalled call only fire during real audio. Use chat to catch the obvious problems (wrong opening line, weak objection handling, missing knowledge) then place a real test call to confirm audio behavior. The chat is stateless. Each chat session starts fresh. Nothing is billed and no call record is created. ## Suggested Test Flow On the voice step, play a few voices and pick the one that fits your campaign's persona and language. On the review step, read the opening line. If it sounds off, edit the first sentence and chat again. Type the objections your leads actually raise. Check the agent's answers against your knowledge base and instructions. Ask the agent something that should end the call. Confirm the agent ends cleanly. Once chat looks right, run a real test call to confirm audio behavior before starting the campaign. ## Next Steps Pick a built-in voice or clone your own. Give your agent the facts it needs to answer questions. # Transfer to a Person Source: https://docs.topcalls.ai/guides/transfer-to-a-person Let the agent connect a caller to one of your people mid-call. Configure the destinations on the campaign; the agent confirms with the caller, says one handoff sentence and bridges the call. If nobody answers, the agent carries on. ## How It Works A transfer is part of the conversation, not a menu. When a caller asks for a person, or reaches a point you described as a transfer condition, the agent: 1. Confirms the destination with the caller in one sentence ("I can connect you to our support desk, is that alright?"). 2. Says the handoff sentence ("One moment, I am connecting you now."). 3. Rings the destination number. The caller hears the ringing. 4. Bridges the caller and the person once they answer. The agent goes quiet; the call stays one call on your account, with one recording and one webhook. If the person does not answer within `timeout_seconds`, is busy or declines, the agent resumes the conversation and offers what you configured as the fallback (continuing now, or a callback). Once `max_attempts` is reached the agent no longer offers a transfer on that call. The caller's own number is never handed to the agent as a destination and the destination numbers are never spoken: the agent only ever chooses between the destinations you configured, by their labels. Transfers work on standard campaigns, outbound and inbound alike. A campaign in the realtime mode does not offer them. The person receiving the call sees the campaign's own number as the caller ID. ## Configure the destinations Set `config.transfer` on the campaign. Each destination has a stable `id` (what the agent chooses between), a `label` (what the caller hears and what you get back), the `number` in E.164, and a `when` condition written in the third person, which is the rule the agent follows to pick it. ```json theme={null} { "config": { "transfer": { "targets": [ { "id": "support_desk", "label": "our support desk", "number": "+40722000001", "when": "the caller asks to speak with a person or has a billing dispute the agent cannot settle" }, { "id": "sales", "label": "a sales colleague", "number": "+40722000002", "when": "the caller wants a quote or to negotiate a price" } ], "handoff_message": "One moment, I am connecting you to {{target}}.", "timeout_seconds": 25, "time_limit_seconds": 1800, "max_attempts": 1, "allowed_prefixes": ["+40"], "business_hours": { "timezone": "Europe/Bucharest", "weekdays": [1, 2, 3, 4, 5], "start": "09:00", "end": "18:00" } } } } ``` | Field | Meaning | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `targets` | One to five destinations. Ids are lowercase letters, digits and underscores. | | `handoff_message` | Fallback sentence when the agent gives none. `{{target}}` becomes the chosen label. | | `timeout_seconds` | How long the destination rings before the agent gives up (10 to 60, default 25). | | `time_limit_seconds` | Longest the caller and the person stay connected (60 to 3600, default 1800). Also capped by the call's remaining `max_duration`. | | `max_attempts` | Transfer attempts per call (1 or 2, default 1). | | `allowed_prefixes` | Every destination must start with one of these. Default `["+40"]`. A number outside the list is rejected with `422`. | | `business_hours` | Optional window in which transfers are offered. Same shape as `inbound_business_hours`. Outside it the agent does not offer a transfer. | | `enabled` | Set `false` to switch transfers off without deleting the block. | The block is validated when you save the campaign and again on every call. Use `PATCH /v1/campaigns/{id}` to change it; the next call picks it up. ## Write the prompt for it The agent already knows how to operate the transfer. Your campaign instructions should say when a person is the right outcome and what to offer when the person cannot be reached. Two lines are usually enough: ```text theme={null} If the caller asks for a person, or raises a billing dispute you cannot settle, offer to connect them to our support desk. If the support desk does not answer, offer to arrange a callback within one business day. ``` Do not put phone numbers in the instructions. Do not ask the caller to press a key. ## Read the outcome The call object (`GET /v1/calls/{id}`) and the completion webhook carry two extra fields when a transfer was attempted: | Field | Value | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `transferred_to` | The destination's label when the caller and the person were connected; `null` otherwise. | | `transfer_status` | `completed`, `failed` (no answer, busy or declined; the agent resumed), `abandoned_by_caller` (the caller hung up while it was ringing) or `not_allowed`. | A completed transfer sets `disposition` to `Transferred`. Webhook subscriptions receive `call.completed.transferred`. The call's `duration` covers the whole call, including the time the caller spent with the person, and is billed as one call. The recording covers the whole call as well. The transcript stops at the handoff sentence: the conversation with the person is not transcribed. ## Limits * One destination is rung at a time; there is no simultaneous ring. * The person is not briefed before the bridge and cannot accept or decline with a key press. * The caller's own number cannot be shown to the person; the campaign number is used. * Destinations must be ordinary phone numbers in the allowed prefixes. SIP addresses are not accepted. ## Related * [Inbound Calls](/guides/inbound-calls) * [Webhooks](/guides/webhooks) * [API reference](/api-reference/introduction) # Webhooks Source: https://docs.topcalls.ai/guides/webhooks Receive notifications when calls finish. Deliver transcripts, recordings, and analysis straight to your systems. ## What Are Webhooks? Webhooks are HTTP callbacks that TopCalls sends to your server when a call finishes. They're the best way to integrate TopCalls with your existing systems. Get notified when calls complete or fail. No polling required. Receive full call details, transcripts, recordings, and analysis in a single webhook payload. Built-in retry logic ensures webhooks are delivered even if your server is temporarily unavailable. Subscribe to specific dispositions or conversions so your endpoint only receives the events it cares about. ## Setting Up Webhooks There are two ways to receive webhooks. ### Per-Call Webhooks Set `webhook_url` when creating a call. That URL receives one payload when the call completes, fails, or is cancelled: ```json theme={null} { "phone_number": "+14155551234", "task": "Confirm appointment...", "webhook_url": "https://your-app.com/webhooks/call-complete", "webhook_call_status_filter": ["completed", "failed"] } ``` `webhook_call_status_filter` is optional. When set, only calls whose final status matches a listed value fire the webhook; leave it out (or send an empty array) to receive every outcome. It applies only to the per-call `webhook_url`, not to account-level subscriptions. ### Account-Level Subscriptions Subscribe a URL once and receive events for every matching call on your account, no matter how the call was created. Manage subscriptions through the API: ```bash theme={null} curl -X POST https://api.topcalls.ai/v1/webhooks \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "event": "call.completed", "url": "https://hooks.your-app.com/topcalls" }' ``` Subscribe to a specific disposition by adding its slug as a suffix. For example, if your campaign's post-call analysis labels a call `Booked Callback`, the event name is `call.completed.booked_callback`: ```json theme={null} { "event": "call.completed.booked_callback", "url": "https://hooks.your-app.com/callbacks" } ``` Narrow further with `filters`. Values match by equality, arrays match any element, strings with `%` match SQL-LIKE, and the special key `is_conversion` matches calls whose disposition is in the campaign's conversion outcomes: ```json theme={null} { "event": "call.completed", "url": "https://hooks.your-app.com/conversions", "filters": { "is_conversion": true, "phone_number": "+1555%" } } ``` Filter on any payload field. For example, `direction` routes only calls received on your numbers to a dedicated endpoint: ```json theme={null} { "event": "call.completed", "url": "https://hooks.your-app.com/inbound", "filters": { "direction": "inbound" } } ``` Use `GET /v1/webhooks` to list subscriptions and `DELETE /v1/webhooks/{webhook_id}` to remove one. See the [API reference](/api-reference/introduction) for full schemas. ### Campaign Webhooks Campaigns configure their webhook URL in the TopCalls dashboard. The campaign editor also lets you pick which call statuses fire the webhook (for example only answered calls, or only voicemail), which keeps noise out of your downstream tools. ## Webhook Payload Here's a complete `call.completed` webhook payload: ```json theme={null} { "call_id": "564d4fd4-03bc-400a-abe0-05540fbeff88", "phone_number": "+14155551234", "from_phone_number": "+18005551234", "direction": "outbound", "caller_name": null, "status": "completed", "call_status": "completed", "duration": 2.5, "transcript": [ { "id": 1, "role": "assistant", "text": "Hi, this is Rachel from TopView Dental...", "created_at": "2025-12-22T10:30:05.120Z" }, { "id": 2, "role": "user", "text": "Yes, hi...", "created_at": "2025-12-22T10:30:08.450Z" }, { "id": 3, "role": "assistant", "text": "", "created_at": "2025-12-22T10:32:35.010Z", "kind": "end", "end_via": "caller" } ], "recording_url": "https://api.topcalls.ai/recordings/.../2025/12/22/mp3", "call_summary": "Successfully confirmed appointment...", "error_message": null, "answered_by": "human", "campaign_id": "camp_abc123", "lead_id": "lead_xyz789", "created_at": "2025-12-22T10:30:00Z", "started_at": "2025-12-22T10:30:05Z", "end_at": "2025-12-22T10:32:35Z", "disposition": "Appointment Booked", "is_conversion": true, "analysis": { "call_outcome": "Appointment Booked", "main_objection": null }, "metadata": { "patient_id": "pat_123", "source": "reminder_system" } } ``` Each transcript item has a speaker and a `created_at` timestamp with milliseconds. On this webhook the speaker field is `role` (`"assistant"` or `"user"`). `GET /v1/calls` uses `user` for the same value. Spoken turns omit `kind`. Other items set `kind` when something besides speech happened: * `tool_call` — a tool the agent used (`tool_name`, `tool_ok`) * `handoff` — a crew pass (`handoff_from`, `handoff_to`) * `signal` — the line went quiet (`caller_silent`) or the call is near its time limit (`caller_time_limit`) * `barge_in` — the caller spoke over the agent * `end` — how the call finished (`end_via`: `agent`, `caller`, `time_limit`, `voicemail`, or `connection`) If you only want spoken text, skip items that have `kind`. `disposition` is the label assigned by post-call analysis, and `is_conversion` is true when that disposition is in the campaign's conversion outcomes list. `direction` is always present: `"outbound"` when TopCalls placed the call, `"inbound"` when the call arrived on one of your numbers. `phone_number` is the other party in both cases. `caller_name` is present only on inbound calls (the network-supplied caller name, or `null` when none was provided); it is absent on outbound payloads. ## Webhook Timing ### Successful Calls The completion webhook fires shortly after the call ends, once the transcript and (if configured) the call summary are ready. The `recording_url` may be missing from this payload. Recordings take a few seconds longer to finish encoding, so a background job resolves the recording and writes it to the call record just after the webhook is sent. If you need the recording and it was not in the webhook, read the call back with `GET /v1/calls/{call_id}` and use the `recording_url` from that response. ### Failed Calls Webhooks are sent **immediately** when the call fails (no recording to wait for). ## Handling Webhooks ### Express.js Example ```javascript theme={null} app.post('/webhooks/call-complete', async (req, res) => { // Always return 200 quickly res.status(200).json({ received: true }); // Process asynchronously const call = req.body; // Update CRM await updateCRM(call); // Send notification await sendNotification(call); // Update analytics await updateAnalytics(call); }); ``` ### Securing Your Endpoint Webhook payloads are not signed today, so treat the URL itself as the credential: * Serve the endpoint over HTTPS only. * Put an unguessable token in the path or query string (for example `https://your-app.com/webhooks/tc_8f3k2m9x`) and reject requests without it. * Before acting on a payload, you can confirm it against the API: fetch `GET /v1/calls/{call_id}` and compare. ## Webhook Retries TopCalls automatically retries failed webhook deliveries: * **Initial**: Immediate * **Retry 1**: After 1 minute * **Retry 2**: After 5 minutes * **Retry 3**: After 15 minutes * **Retry 4**: After 30 minutes * **Retry 5**: After 1 hour After 5 retries, the webhook is marked as failed. You can still retrieve call data via the API. ## Best Practices ### ✅ Do This * **Return 200 quickly**: Acknowledge receipt within 1 second * **Process asynchronously**: Don't block the webhook response * **Idempotency**: Handle duplicate webhooks gracefully * **Log everything**: Keep logs for debugging * **Keep the URL secret**: Use an unguessable path token and HTTPS * **Handle errors**: Don't let webhook processing crash your server ### ❌ Don't Do This * **Long processing**: Don't process synchronously in the webhook handler * **Ignore duplicates**: Webhooks may be delivered multiple times * **Expose the URL**: Don't publish or log your webhook URL where others can see it * **Block on external APIs**: Don't wait for slow external services ## Testing Webhooks Use [ngrok](https://ngrok.com) or [webhook.site](https://webhook.site) to test locally: ```bash theme={null} # Start ngrok tunnel ngrok http 3000 # Use the ngrok URL { "webhook_url": "https://abc123.ngrok.io/webhooks/call-complete" } ``` ## Next Steps Learn about available webhook events and when they fire. See complete webhook payload schemas in the API reference. # TopCalls Voice API Source: https://docs.topcalls.ai/index AI-powered calling, fully managed. Replace manual dialing. Scale without hiring. First calls live in 2 weeks. ## AI-Powered Calling, Fully Managed TopCalls runs your calls so you can close the deals. We provide production-ready telephony infrastructure, AI orchestration, and platform tooling for automating customer phone interactions at scale. Every module is production-ready, compliance-built, and scales with your volume. Natural, human-like conversations with sub-500ms response times. Your callers won't know it's AI. Queue management for automated calls with timezone awareness, retry logic, and real-time monitoring. Choose from dozens of built-in voices or clone your own for brand consistency. Give your AI agents tools to book appointments, look up orders, update CRMs, and integrate with your systems mid-call. Add text entries with product, pricing, and policy details. Your agents access the attached context automatically during conversations. Automatic call summaries, sentiment analysis, structured data extraction, and webhooks for every call. ## What You Can Build Attach a campaign to one of your phone numbers and answer callers with the campaign greeting and optional business hours. Automate confirmations, rescheduling, and reminders. Reduce no-shows and improve scheduling efficiency. Qualify inbound and warm leads, schedule follow-ups, and route qualified prospects to your sales team. Handle common support inquiries around the clock. Provide instant responses and escalate complex issues. Automate NPS surveys, renewal reminders, and post-purchase check-ins. Professional, compliant collection calls with payment plan negotiation and full audit trails. ## Get Started Make your first AI phone call in under 5 minutes. Complete REST API documentation with interactive examples and code samples. ## How It Works 1. **You call our API** with a phone number, instructions for the AI agent, and a voice 2. **We handle everything** from telephony to AI orchestration to audio processing 3. **You get results** via webhooks with transcripts, recordings, summaries, and structured data 32 languages supported. Timezone-aware scheduling. Carrier-grade reliability. **Free consultation, tailored proposal delivered within 48 hours.** Customers remain responsible for lawful use and compliance with local calling regulations. **New to AI voice agents?** Start with our [Quickstart Guide](/quickstart) to make your first call in minutes, or explore [How It Works](/concepts/overview) to understand the platform. # Integrations Source: https://docs.topcalls.ai/integrations Connect Topcalls to your tools to automate the call lifecycle. No code required. ## Overview Connect Topcalls to your tools to automate the call lifecycle. Trigger calls when something happens in your CRM, push call outcomes into spreadsheets or messaging apps, and keep leads in sync between systems. Build flows visually, no code required. 683+ tools, each with its own page listing every trigger and action. Search and filter by category. ## What it does Start a Topcalls call when a row is added to your CRM, a meeting is booked, or a form is submitted. Route completed calls, transcripts, and dispositions into your CRM, sheets, or chat tools. Keep lead data consistent between Topcalls and your source-of-truth systems. ## Available triggers Triggers start a flow when something happens in Topcalls. The names below match what you see in the in-product catalog. | Trigger | Fires when | | ----------------------------- | ------------------------------------------------------------------------------------------- | | When a Call Ends | A call ends, regardless of outcome | | When a Call Fails | A call ends without reaching a person (no-answer, busy, failed, rejected, dispatch timeout) | | When a Call Hits Voicemail | A call is answered by a voicemail system instead of a person | | When a Callback Is Requested | A call ends with a disposition meaning the caller asked for a callback | | When an Appointment Is Booked | A call ends with a disposition meaning an appointment was booked | | When a Follow-up Is Scheduled | A call ends with a disposition meaning a follow-up was scheduled | | When a Conversion Happens | A call ends with a disposition you have marked as a conversion | ## Available actions Actions are steps you add to a flow to make Topcalls do something. Below is a representative set; the full catalog is visible in-product. | Action | Effect | | --------------------- | -------------------------------------------------------- | | Make a Call | Place an outbound call with a script and voice | | Stop In-Progress Call | Hang up a call that is currently in progress | | Cancel Call | Cancel a call that is queued or in progress | | Add Lead | Create a lead and assign it to a list | | Update Lead | Change fields on an existing lead | | Bulk Import Leads | Insert many leads in one call | | Start Campaign | Activate a campaign so it begins dialing leads | | Pause Campaign | Pause an active campaign without cancelling queued calls | | Find Call by ID | Look up a single call and return the full record | | List Recent Calls | Return the last N calls for your account | | Get Account Balance | Return remaining call minutes and balance details | ## Supported destinations Connect Topcalls to 683+ tools across the categories below. The cards show a representative sample; the [full catalog](https://www.topcalls.ai/integrations) has a searchable page for every tool. HubSpot, Salesforce, Pipedrive, Freshdesk, Zendesk Google Calendar, Cal.com Google Sheets, Airtable, Notion Slack, Discord, Gmail, SendGrid, Mailchimp Stripe, Shopify Twilio More destinations are added regularly. Browse the [full catalog](https://www.topcalls.ai/integrations) to see every supported tool; if one you use isn't there yet, [let us know](https://www.topcalls.ai/contact). ## Setup walkthrough Sign in at [topcalls.ai](https://www.topcalls.ai/auth/sign-in) and click **Integrations** in the left sidebar. Browse the [integrations catalog](https://www.topcalls.ai/integrations) (or the **Integrations** tab in-app) and click the tool you want to connect. Each entry shows the triggers and actions available for that destination. Sign in with OAuth or paste an API key, depending on the destination. Topcalls stores credentials encrypted and only uses them for the flows you build. Drag a trigger onto the canvas, add one or more actions, and map the fields between them. The visual editor previews the data at every step. Run the flow against a sample event before turning it on for live traffic. Once you're happy with the result, enable it and the flow runs automatically. ## Webhooks vs Integrations **Webhooks** deliver call events directly to your endpoint with a JSON payload. Best when you have a backend that can receive and process them. **Integrations** route the same events through a no-code builder to other tools. Best when you want to push data into a CRM or messaging app without writing code. You can use both. Webhooks for the systems your engineers own, Integrations for the tools your operations team uses. ## Where to next Set up webhooks and handle payloads. Deliveries are not signed. Read the event model, available events, and retry semantics. # Quickstart Source: https://docs.topcalls.ai/quickstart Make your first AI phone call in under 5 minutes. ## Get Started in 5 Minutes This guide will help you make your first AI phone call. Sign in at [topcalls.ai](https://www.topcalls.ai/auth/sign-in). New to TopCalls? [Book a call](https://cal.com/topcalls.ai/30min) and we set up your account. Once logged in, navigate to **Settings > API Keys** and create a new key. Copy it for the next step. Use the example below to trigger your first AI phone call. Replace `YOUR_API_KEY` with your actual key. ## Example: Simple Appointment Reminder ```bash theme={null} curl -X POST https://api.topcalls.ai/v1/calls \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "phone_number": "+14155551234", "from_number": "+18005551234", "task": "You are Rachel, a friendly appointment coordinator. Call to confirm John's appointment tomorrow at 3 PM. Be warm, professional, and brief. If they need to reschedule, ask for their preferred time.", "voice": "alloy", "mode": "realtime" }' ``` ### Response ```json theme={null} { "call_id": "564d4fd4-03bc-400a-abe0-05540fbeff88", "status": "queued" } ``` The call will be placed within seconds. Make sure the `phone_number` is a number you can answer for testing. `from_number` is the caller ID the recipient sees. It must be a phone number provisioned for your TopCalls account. Provision one in your dashboard before making calls; if you omit the field, TopCalls falls back to a default sender that is not guaranteed to be the right caller ID for your traffic. ## What Happens Next? 1. **Call is Queued**: The system validates your request and queues the call 2. **Call Connects**: Within a few seconds, the recipient receives the call 3. **AI Conversation**: The AI agent speaks naturally, handles responses, and completes the task 4. **Call Completes**: After the call ends, you receive a webhook (if configured) with the transcript and summary ## Check Call Status Use the `call_id` from the response to check the status: ```bash theme={null} curl https://api.topcalls.ai/v1/calls/564d4fd4-03bc-400a-abe0-05540fbeff88 \ -H "Authorization: Bearer YOUR_API_KEY" ``` ## Next Steps Control your agent's personality, voice, and behavior. Receive real-time events when calls start, end, or fail. See real-world examples for appointments, support, sales, and collections. Complete REST API documentation with interactive examples. ## Need Help? * **Documentation**: Browse our [Core Concepts](/concepts/overview) * **Contact**: Email us at [hello@topcalls.ai](mailto:hello@topcalls.ai) * **Get Your Deployment Plan**: [Book a discovery call](https://cal.com/topcalls.ai/30min) for a free consultation # Appointment Management Source: https://docs.topcalls.ai/use-cases/appointment-reminders Automate appointment confirmations, rescheduling, and reminders with AI-powered phone calls. ## Overview Appointment reminders are one of the most common use cases for AI voice agents. TopCalls automates confirmations, rescheduling, and follow-ups across any industry that books appointments. Automated reminders significantly reduce no-show rates. Phone calls get higher engagement than texts or emails. Automate thousands of reminder calls. Your team can focus on in-person work. Personalized phone calls feel more professional than generic SMS blasts. AI agents handle rescheduling requests automatically, updating your calendar in real-time. ## Works For Any Industry * **Healthcare**: Medical, dental, therapy, specialist appointments * **Beauty & Wellness**: Salons, spas, fitness sessions * **Professional Services**: Legal consultations, financial planning, real estate viewings * **Automotive**: Service appointments, test drives, inspections * **Education**: Tutoring sessions, parent-teacher conferences, admissions meetings * **Home Services**: Plumbing, HVAC, cleaning, pest control ## Example Implementation ### Step 1: Create the Call ```json theme={null} { "phone_number": "+14155551234", "from_number": "+18005551234", "instructions": "You are Rachel, a friendly appointment coordinator. Your goal is to confirm {{client_name}}'s appointment scheduled for {{appointment_date}} at {{appointment_time}}. Be warm, professional, and concise. If they want to reschedule, ask for their preferred date/time.", "first_sentence": "Hi {{client_name}}, this is Rachel calling about your appointment tomorrow at {{appointment_time}}.", "voice": "alloy", "mode": "realtime", "lead_context": { "client_name": "John Smith", "appointment_date": "2025-12-24", "appointment_time": "3:00 PM" }, "webhook_url": "https://your-app.com/webhooks/appointment-reminder" } ``` ### Step 2: Handle the Webhook ```javascript theme={null} app.post('/webhooks/appointment-reminder', async (req, res) => { res.status(200).json({ received: true }); const call = req.body; if (call.call_summary.includes('confirmed')) { await updateAppointmentStatus(call.metadata.appointment_id, 'confirmed'); } else if (call.call_summary.includes('rescheduled')) { // call.analysis is structured data extracted from the transcript when you // set analysis_schema on call creation (see /guides/making-calls). const newDateTime = call.analysis?.next_appointment_time; await rescheduleAppointment(call.metadata.appointment_id, newDateTime); } else if (call.call_summary.includes('cancelled')) { await cancelAppointment(call.metadata.appointment_id); } if (call.answered_by === 'voicemail') { await sendSMS(call.phone_number, 'We left you a voicemail about your appointment...'); } }); ``` ## Campaign Setup For high-volume reminders, create a campaign through the TopCalls dashboard with your contact list and schedule. The system processes contacts automatically, respecting timezones and retry logic. ## Best Practices ### Do This * **Call 24-48 hours before**: Best window for reminders * **Respect timezones**: Only call during business hours * **Handle voicemail**: Leave clear messages or send SMS follow-up * **Enable rescheduling**: Let people reschedule easily * **Track outcomes**: Monitor confirmation rates and adjust ### Avoid This * **Call too early**: More than 48 hours in advance is too soon * **Call too late**: Same-day reminders are too late to reschedule * **Ignore timezones**: Always respect local business hours * **No rescheduling option**: Always offer to reschedule ## Next Steps Make your first appointment reminder call. Scale to thousands of reminders with campaigns. # Customer Support Source: https://docs.topcalls.ai/use-cases/customer-support Handle common support inquiries around the clock with AI agents. Escalate complex issues to human agents when needed. ## Overview TopCalls enables customer support with AI agents that handle common inquiries, troubleshoot issues, and escalate complex problems to human agents when needed. Support customers anytime. No more "business hours only" limitations. Customers get immediate answers. No waiting on hold or for email responses. Handle more inquiries simultaneously without growing your support team. AI handles common issues, humans handle complex problems. Clean handoffs every time. ## How It Works Customer calls your support number. AI agent answers immediately. AI listens to the customer's problem and identifies the issue type. AI provides solutions from its knowledge base or troubleshoots step-by-step. If the issue is too complex, the AI connects the caller to one of the people you configured (see [Transfer to a person](/guides/transfer-to-a-person)). After resolution, AI can follow up to confirm the issue is resolved. ## Example Implementation ### Create a Support Agent ```json theme={null} { "phone_number": "+14155551234", "instructions": "You are a customer support agent. Your goal is to help customers with their questions and issues. Be friendly, patient, and solution-oriented. Use the knowledge base to answer questions accurately. If you can't solve the issue, let the customer know you'll escalate to a specialist. Always confirm the issue is resolved before ending the call.", "first_sentence": "Hi, thanks for calling. How can I help you today?", "voice": "alloy", "mode": "realtime", "campaign_id": "0c4f4b9e-8b0a-4f57-9d3e-2f1a7c9d1234", "webhook_url": "https://your-app.com/webhooks/support" } ``` Knowledge bases attach to campaigns in the dashboard. Pass `campaign_id` on the call and the agent gets that campaign's knowledge base as context. See the [Knowledge Bases guide](/guides/knowledge-bases). ### Handle Support Webhooks ```javascript theme={null} app.post('/webhooks/support', async (req, res) => { res.status(200).json({ received: true }); const call = req.body; await supportSystem.logInteraction({ customerPhone: call.phone_number, issue: call.call_summary, resolved: call.call_summary.includes('resolved'), duration: call.duration, transcript: call.transcript, recordingUrl: call.recording_url }); if (!call.call_summary.includes('resolved')) { await sendFollowUpEmail(call.phone_number, call.call_summary); } }); ``` ## Common Support Scenarios ### Account Issues * Password resets * Account access problems * Billing questions * Subscription changes ### Product Questions * Feature usage * Troubleshooting * Setup assistance ### Order Support * Order status * Shipping questions * Returns and refunds ## Best Practices ### Do This * **Build a knowledge base**: Cover all common issues in your knowledge base * **Define escalation criteria**: Describe each transfer destination's `when` condition so the agent knows when to offer a person * **Friendly tone**: Be patient and empathetic * **Confirm resolution**: Always verify the issue is solved before ending the call * **Follow up**: Send email summaries for complex issues * **Track metrics**: Monitor resolution rates and escalation rates ### Avoid This * **Over-promise**: Don't promise things you can't deliver * **Skip escalation**: Configure transfer destinations so complex issues reach a person * **Rush**: Take time to understand the problem fully * **Ignore context**: Use customer account data when available ## Next Steps Create knowledge bases for your support agents. Integrate with your support systems and CRM. # Compliance & Collections Source: https://docs.topcalls.ai/use-cases/debt-collection Professional, compliant collection calls with payment plan negotiation and full audit trails. Built-in compliance tooling. ## Overview TopCalls provides a platform for professional, compliant collection calls. The platform includes tooling to help customers honor local calling laws (TCPA, FDCPA, GDPR) while automating initial outreach, payment negotiations, and follow-ups. Customers remain responsible for lawful use and compliance. Compliance tooling to help maintain regulatory requirements. Consistent, professional interactions every time. AI agents can negotiate payment plans and handle objections with consistent professionalism. Reach debtors at convenient times within allowed hours. Higher contact rates than manual methods. Every call is recorded and transcribed. Complete documentation for compliance. ## Compliance Tooling TopCalls provides platform features to help customers maintain compliance: * **Time restrictions**: Configure allowed calling hours per timezone * **Frequency limits**: Set maximum call frequency rules * **Opt-out handling**: Immediately honor do-not-call requests * **Full transcripts**: Complete records for compliance audits * **Recording storage**: Secure storage of all call recordings * **Audit trails**: Complete logs of all interactions **Compliance Responsibility**: Customers are responsible for ensuring all collection calls comply with applicable laws including TCPA, FDCPA, and local regulations. TopCalls provides tooling to help but does not guarantee legal compliance. Consult legal counsel before implementing collection campaigns. ## Example Implementation ### Create a Collection Call ```json theme={null} { "phone_number": "+14155551234", "instructions": "You are a professional collection agent. Your goal is to collect payment for account {{account_number}} with a balance of {{debt_amount}}. Be firm but respectful. Offer payment plans if they can't pay in full. If they request to be removed from the call list, immediately honor that request. Never threaten or use abusive language.", "first_sentence": "Hi {{name}}, this is a call regarding account {{account_number}}. I'm reaching out about an outstanding balance of {{debt_amount}}.", "voice": "alloy", "mode": "realtime", "temperature": 0.6, "lead_context": { "name": "John Smith", "account_number": "ACC-12345", "debt_amount": "$1,250.00" }, "webhook_url": "https://your-app.com/webhooks/collection" } ``` ### Handle Webhooks ```javascript theme={null} app.post('/webhooks/collection', async (req, res) => { res.status(200).json({ received: true }); const call = req.body; if (call.call_summary.includes('payment received')) { await updateAccountStatus(call.metadata.account_number, 'paid'); } else if (call.call_summary.includes('payment plan')) { await createPaymentPlan(call.metadata.account_number, call.metadata.payment_plan); } else if (call.call_summary.includes('opt out')) { await optOutDebtor(call.phone_number); } // Log for compliance await complianceSystem.logCall({ accountNumber: call.metadata.account_number, phoneNumber: call.phone_number, outcome: call.call_summary, transcript: call.transcript, recordingUrl: call.recording_url, timestamp: call.end_at }); }); ``` ## Compliance Checklist * Time restrictions configured * Frequency limits enforced * Opt-out handling implemented * Full call transcripts stored * Recordings securely stored * Audit trail maintained * Professional scripts reviewed * Legal compliance verified with counsel ## Best Practices ### Do This * **Respect regulations**: Always follow TCPA, FDCPA, and local laws * **Offer payment plans**: Be flexible with payment options * **Honor opt-outs**: Immediately remove from call list if requested * **Document everything**: Keep complete records for compliance * **Professional tone**: Be firm but respectful * **Time restrictions**: Only call during allowed hours * **Consult legal counsel**: Verify compliance before launching ### Avoid This * **Threaten**: Never use threatening or abusive language * **Call outside hours**: Respect time restrictions * **Ignore opt-outs**: Always honor do-not-call requests * **Skip documentation**: Maintain complete audit trails * **Pressure excessively**: Don't be overly aggressive ## Next Steps Scale your collection campaigns. Integrate with your payment and CRM systems. # Lead Qualification & Follow-ups Source: https://docs.topcalls.ai/use-cases/sales-outreach Qualify inbound and warm leads with AI agents that schedule follow-ups and route qualified prospects to your sales team. For consented contacts only. ## Overview TopCalls automates lead qualification and follow-up calls for consented contacts. AI agents handle initial conversations, qualify interest, and route qualified prospects to your sales team. Automatically qualify inbound and warm leads before they reach your sales team. Focus on high-intent prospects. AI agents book meetings directly into your calendar. No back-and-forth emails. Handle more qualification calls without adding headcount. Compliance tooling to help honor local calling laws (TCPA/TSR/DNC). For consented contacts only. ## How It Works Upload your lead list with contact info and consent verification. All contacts must have opted in or have an existing business relationship where required. Your AI agent calls each lead, introduces your product, and qualifies interest while respecting opt-out requests. AI asks qualifying questions and determines fit. Qualified leads: Book a demo or schedule a follow-up. Not interested: Thank them and end gracefully. Opt-out requested: Immediately honor the request. All interactions are logged with call recordings and transcripts via webhooks. ## Example Implementation **Compliance Notice**: This use case is for consented contacts only. Ensure all contacts have opted in or have an existing business relationship. Always honor opt-out requests immediately. Customers are responsible for compliance with local calling laws. ### Create a Qualification Call ```json theme={null} { "phone_number": "+14155551234", "instructions": "You are Alex, a sales development representative. Your goal is to qualify {{company_name}} as a potential customer and book a demo if they're interested. Be friendly, professional, and consultative. Ask about their current solution and pain points. If not interested, thank them and end gracefully. If they request to be removed from the call list, immediately honor that request.", "first_sentence": "Hi {{name}}, this is Alex from our team. I'm reaching out because {{company_name}} might benefit from our platform.", "voice": "alloy", "mode": "realtime", "temperature": 0.8, "lead_context": { "name": "John Smith", "company_name": "Acme Corp", "title": "VP of Operations" }, "webhook_url": "https://your-app.com/webhooks/sales" } ``` ### Handle Webhooks ```javascript theme={null} app.post('/webhooks/sales', async (req, res) => { res.status(200).json({ received: true }); const call = req.body; await crm.updateLead(call.metadata.lead_id, { lastCallDate: call.end_at, callDuration: call.duration, callOutcome: call.call_summary, recordingUrl: call.recording_url, transcript: call.transcript }); const isQualified = call.call_summary.includes('interested') || call.call_summary.includes('demo scheduled'); if (isQualified) { await crm.updateLeadStatus(call.metadata.lead_id, 'qualified'); await notifySalesTeam(call); } }); ``` ## Best Practices ### Do This * **Verify consent**: Only call contacts who have opted in where required * **Honor opt-outs**: Immediately remove contacts who request it * **Personalize**: Use company name, contact name, and relevant context * **Be consultative**: Focus on solving problems, not hard selling * **Book meetings**: Make it easy to schedule next steps * **Document compliance**: Maintain records of consent and opt-out requests ### Avoid This * **Call without consent**: Never call contacts who haven't opted in where required * **Ignore opt-outs**: Always honor do-not-call requests immediately * **Be pushy**: Don't pressure uninterested leads * **Skip qualification**: Always qualify before booking meetings ## Next Steps Scale your outreach with campaigns. Integrate with your CRM and calendar systems.