Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fern/calls/call-concurrency.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,5 @@ Adjust the `timeRange.step` to inspect usage by hour, day, or week. Peaks that a
## Next steps

- **[Call queue management](mdc:docs/calls/call-queue-management):** Build a Twilio queue to buffer calls when you hit concurrency caps.
- **[Outbound campaign planning](mdc:docs/outbound-campaigns/overview):** Design outbound strategies that pair batching with analytics.
- **[Outbound campaign planning](/campaigns/overview):** Design outbound strategies that pair batching with analytics.
- **[Enterprise plans](mdc:docs/enterprise/plans):** Review larger plans that include higher default concurrency.
2 changes: 1 addition & 1 deletion fern/calls/call-outbound.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Use both `customers` and `schedulePlan` together to schedule batched calls.

## Creating Outboud Calls from the Dashboard

Learn more about how to launch [Outbound Calling Campaigns via Dashboard](/outbound-campaigns/quickstart)
Learn more about how to launch [Campaigns via Dashboard](/campaigns/quickstart)

## Trusted Calling and Caller ID

Expand Down
217 changes: 217 additions & 0 deletions fern/campaigns/advanced.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
---
title: Campaigns advanced
subtitle: Personalization, scheduling, the pre-dial webhook, and managing campaigns via the API
slug: campaigns/advanced
description: Personalize each campaign call with variables and overrides, schedule dialing windows, gate contacts with the pre-dial webhook, and manage campaigns via the REST API.
---

## Personalize each call

Every contact can get a tailored conversation. Overrides exist at two levels and are deep-merged at dial time:

- **Campaign-level** overrides apply to every call in the campaign
- **Per-contact** overrides are merged on top for that contact's call

Both `assistantOverrides` and `squadOverrides` are supported — at the campaign level and per contact. `assistantOverrides` applies when the call is handled by an assistant; `squadOverrides` when it's handled by a squad.

The most common use is `variableValues`, which feed [dynamic variables](/assistants/dynamic-variables) in your assistant's prompts:

```bash
curl https://api.vapi.ai/v2/campaign \
-H "Authorization: Bearer $VAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Post-Service Feedback",
"assistantId": "your-assistant-id",
"phoneNumberId": "your-phone-number-id",
"maxConcurrency": 10,
"assistantOverrides": {
"variableValues": { "company": "TechFlow" }
},
"customers": [
{
"number": "+14155551234",
"name": "John",
"assistantOverrides": {
"variableValues": { "customer_issue": "password reset" }
}
},
{
"number": "+14155555678",
"name": "Mary",
"assistantOverrides": {
"variableValues": { "customer_issue": "address update" }
}
}
]
}'
```

Your assistant can then reference `{{company}}` and `{{customer_issue}}` in its prompts.

In the dashboard, extra CSV columns become per-contact `variableValues` automatically — a `customer_issue` column fills `{{customer_issue}}` for each row.

## Schedule a campaign

By default, dialing starts immediately on creation. Use `schedulePlan` to control the window:

- `earliestAt` — dialing starts at this time instead of immediately
- `latestAt` — optional deadline; no further calls are attempted after it

```bash
curl https://api.vapi.ai/v2/campaign \
-H "Authorization: Bearer $VAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Weekend Promo",
"assistantId": "your-assistant-id",
"phoneNumberId": "your-phone-number-id",
"maxConcurrency": 10,
"customers": [ { "number": "+14155551234" } ],
"schedulePlan": {
"earliestAt": "2026-07-04T16:00:00Z",
"latestAt": "2026-07-05T01:00:00Z"
}
}'
```

In the dashboard, this is the **Schedule for later** option when launching.

<Tip>
Times are absolute UTC instants (ISO 8601). "9am in each contact's local timezone" isn't expressible yet — if you need per-timezone windows, split contacts into one campaign per region with region-appropriate windows.
</Tip>

## Pre-dial webhook

Set a `server` on the campaign to receive a `campaign.predial` webhook **before each contact is dialed**. Your server decides, contact by contact, whether the call should happen — useful for do-not-call registries, opt-out lists, or last-minute eligibility checks that can't be known at campaign creation time.

```bash
curl https://api.vapi.ai/v2/campaign \
-H "Authorization: Bearer $VAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Renewals with DNC check",
"assistantId": "your-assistant-id",
"phoneNumberId": "your-phone-number-id",
"maxConcurrency": 10,
"customers": [ { "number": "+14155551234" } ],
"server": { "url": "https://example.com/vapi/campaign-webhook" }
}'
```

Before each dial, your server receives:

```json
{
"message": {
"type": "campaign.predial",
"campaignId": "4a5b6c7d-...",
"contact": {
"id": "contact-id",
"campaignId": "4a5b6c7d-...",
"number": "+14155551234",
"name": "John"
}
}
}
```

Respond with whether the contact may be dialed:

```json
{ "eligible": true }
```

<Warning>
The pre-dial check **fails closed**: if your server responds `"eligible": false`, is unreachable, errors, or times out, the contact is **skipped** and not called. Make sure the endpoint is fast and highly available — an outage on your side pauses dialing decisions against you, not in your favor.
</Warning>

In the dashboard, this is the **Pre-dial webhook** field when creating a campaign.

## Tune concurrency

`maxConcurrency` is required at creation, must be within your org's [concurrency limit](/calls/call-concurrency), and caps how many campaign calls run at once. Both the campaign cap and the org-wide limit apply to every campaign call — see the [overview](/campaigns/overview#concurrency-two-limits-apply).

Practical guidance:

- **Leave headroom.** If your org limit is 10 and inbound or one-off outbound calls happen alongside the campaign, a `maxConcurrency` of 10 will starve them — and campaign dispatches at capacity can fail.
- **Telephony limits are separate.** Your provider (e.g. Twilio) has its own rate limits on outbound call initiation.
- **More isn't always faster.** Answer rates, voicemail, and call duration dominate campaign wall-clock time more than raw concurrency.

## Manage campaigns via the API

Everything the dashboard does, the API does too — the endpoints live under `/v2/campaign` and use your private API key. Full request/response schemas are in the [API reference](/api-reference/campaigns/campaign-controller-create-v2).

### Create

`POST /v2/campaign` creates and starts a campaign in one request. Required: `name`, an `assistantId` or `squadId` (exactly one), `phoneNumberId`, `customers` (at least one), and `maxConcurrency`.

```bash
curl https://api.vapi.ai/v2/campaign \
-H "Authorization: Bearer $VAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Q3 Renewal Reminders",
"assistantId": "your-assistant-id",
"phoneNumberId": "your-phone-number-id",
"maxConcurrency": 5,
"customers": [
{ "number": "+14155551234", "name": "John" },
{ "number": "+14155555678", "name": "Mary" }
]
}'
```

### Monitor

Poll `GET /v2/campaign/{id}`. The response includes the contact list, current `status`, and — as each call ends — an entry in the `calls` map with the customer, timestamps, `endedReason`, and [`analysis`](/assistants/call-analysis):

```json
{
"status": "in-progress",
"calls": {
"call-id-1": {
"id": "call-id-1",
"customer": { "number": "+14155551234", "name": "John" },
"status": "ended",
"endedReason": "customer-ended-call",
"startedAt": "2026-07-02T18:01:12.000Z",
"endedAt": "2026-07-02T18:03:45.000Z",
"analysis": { "summary": "..." }
}
}
}
```

For full call artifacts (recording, transcript), fetch the individual call with [`GET /call/{id}`](/api-reference/calls/get). To list campaigns, use `GET /v2/campaign` (paginated, filterable by `status`).

### Cancel

Campaigns are immutable after creation — cancellation is the only supported update:

```bash
curl -X PATCH https://api.vapi.ai/v2/campaign/CAMPAIGN_ID \
-H "Authorization: Bearer $VAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "cancelled" }'
```

New calls stop being dispatched; calls already in progress finish naturally.

### Delete

`DELETE /v2/campaign/{id}` archives the campaign — cancelling it first if it's still running. Archived campaigns no longer appear in list or get responses.

## Re-running a campaign

Since campaigns are immutable, iteration works by duplication: pass `duplicateFromCampaignId` on create to inherit the source campaign's configuration and contacts. Any fields you provide override the source; if you provide `customers`, they replace the source's contact list.

```bash
curl https://api.vapi.ai/v2/campaign \
-H "Authorization: Bearer $VAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Q3 Renewal Reminders (rerun)",
"duplicateFromCampaignId": "SOURCE_CAMPAIGN_ID"
}'
```
59 changes: 59 additions & 0 deletions fern/campaigns/overview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: Campaigns overview
subtitle: Scalable batch outbound calling with per-campaign concurrency control
slug: campaigns/overview
description: Learn how Vapi Campaigns work — upload contacts, set a concurrency limit, and let Vapi dial through your list from the dashboard or the API.
---

## Overview

Campaigns are Vapi's batch outbound calling system. You provide a list of contacts, pick an assistant (or squad) and a phone number, set a concurrency limit, and Vapi dials through the list for you — as soon as one call finishes, the next contact is dialed, keeping up to your concurrency limit in flight until everyone has been called.

You can create and manage campaigns from the **dashboard** or programmatically via the **API** — both drive the same system.

<Note>
Campaigns are in limited availability. If you don't see **Campaigns** in your dashboard, or the API returns `403 Campaigns V2 is not enabled for your organization`, contact [Vapi support](/support) to have it enabled.
</Note>

## Key capabilities

- **Scale**: call thousands of contacts from a single campaign
- **Concurrency control**: cap how many calls run at once with `maxConcurrency`
- **Personalization**: per-contact variables and overrides for tailored conversations
- **Scheduling**: start now or within a time window
- **Pre-dial webhook**: approve or skip each contact right before it's dialed
- **Monitoring**: per-call outcomes, transcripts, and analysis as calls complete

## How a campaign runs

1. **Create** — you submit the campaign (dashboard or [`POST /v2/campaign`](/api-reference/campaigns/campaign-controller-create-v2)). Campaigns start automatically; there is no draft state.
2. **Dispatch** — Vapi claims contacts from your list and dials them, keeping up to `maxConcurrency` calls active at once. When a call ends, the next contact is dialed immediately.
3. **Complete** — each finished call is recorded on the campaign with its outcome, timestamps, and [analysis](/assistants/call-analysis). When every contact has been called, the campaign ends.

### Campaign lifecycle

| Status | Meaning |
| --- | --- |
| `scheduled` | Created; waiting for its schedule window (or about to start dialing) |
| `in-progress` | Actively dialing through the contact list |
| `ended` | All contacts have been called (`endedReason: campaign.ended.success`) |
| `cancelled` | Stopped by you before completing; in-progress calls finished naturally |

Campaigns are immutable after creation — cancellation is the only change you can make to a running campaign. Deleting a campaign archives it: it stops (if running) and disappears from your list. To iterate on a campaign, duplicate it with your changes (see [Advanced](/campaigns/advanced#re-running-a-campaign)).

## Concurrency: two limits apply

- **Campaign pacing** — `maxConcurrency` on the campaign caps how many of *its* calls run at once. It's required at creation and can't exceed your org limit.
- **Organization capacity** — your org-wide [concurrency limit](/calls/call-concurrency) is shared by *all* calls your org makes, campaign or not.

If your org is at capacity when a campaign call would dispatch, that dispatch can fail even though the campaign is under its own cap. If you place other calls while campaigns run, keep `maxConcurrency` comfortably below your org limit.

## Next steps

- **[Quickstart](/campaigns/quickstart)**: launch your first campaign from the dashboard
- **[Advanced](/campaigns/advanced)**: personalization, scheduling, the pre-dial webhook, and managing campaigns via the API
- **[API reference](/api-reference/campaigns/campaign-controller-create-v2)**: the `/v2/campaign` endpoints

<Warning>
It is a violation of FCC law to dial phone numbers without consent in an automated manner. See our [TCPA Consent Guide](/tcpa-consent) before launching campaigns.
</Warning>
102 changes: 102 additions & 0 deletions fern/campaigns/quickstart.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: Campaigns quickstart
subtitle: Launch your first batch outbound campaign from the Vapi Dashboard
slug: campaigns/quickstart
description: Create, launch, and monitor a batch outbound calling campaign step by step in the Vapi Dashboard.
---

## Overview

Launch a batch outbound campaign from the dashboard: upload your contacts, pick who handles the calls, set your concurrency, and hit launch.

**In this quickstart, you'll learn to:**

- Prepare a contact list (CSV or manual entry)
- Configure and launch a campaign
- Monitor progress and per-call outcomes

Prefer to do this programmatically? See [managing campaigns via the API](/campaigns/advanced#manage-campaigns-via-the-api).

## Prerequisites

- A [Vapi account](https://dashboard.vapi.ai) with Campaigns enabled (see [availability](/campaigns/overview))
- An **assistant** or **squad** to handle the calls
- A **phone number** — imported from a provider (Twilio, Vonage, Telnyx) or a free Vapi number
- Your contacts' phone numbers in E.164 format (e.g. `+14155551234`)

## 1. Prepare your contacts

The fastest path is a CSV. Only one column is required:

| number | name | customer_issue |
| --- | --- | --- |
| +14151231234 | John | password reset |
| +14153455678 | Mary | address update |

- `number` is **required**, lowercase, in E.164 format
- `name` is optional
- Any additional columns become [dynamic variables](/assistants/dynamic-variables) for that contact's call — `{{customer_issue}}` in your assistant's prompt resolves per contact
- Column names can't contain spaces and must start with a letter
- Save as UTF-8; avoid blank rows and duplicate headers

For a handful of contacts, you can skip the CSV and enter them manually during campaign creation.

## 2. Launch a campaign

<Steps>
<Step title="Open Campaigns">
Go to [dashboard.vapi.ai](https://dashboard.vapi.ai) and click **Campaigns** in the left sidebar.
</Step>

<Step title="Create a campaign">
Click **Create Campaign** and give it a name (e.g. "Post-Service Feedback").
</Step>

<Step title="Select a phone number">
Pick the number to call from. To maximize answer rates, review the [trusted calling best practices](/calls/outbound-calling#trusted-calling-and-caller-id) — unregistered numbers get flagged as spam.
</Step>

<Step title="Add contacts">
Choose **Upload CSV** (a template is available to download) or **Enter manually**. Uploaded rows are validated before launch.
</Step>

<Step title="Choose who handles the calls">
Select an **Assistant** or a **Squad**.
</Step>

<Step title="Set max concurrency">
Use the slider to cap how many calls run at once. The maximum is your organization's concurrency limit — leave headroom if you place other calls while the campaign runs. See [concurrency](/campaigns/overview#concurrency-two-limits-apply).
</Step>

<Step title="(Optional) Add a pre-dial webhook">
Provide a server URL to approve or skip each contact right before dialing — useful for do-not-call checks or last-minute eligibility. Details in [Advanced](/campaigns/advanced#pre-dial-webhook).
</Step>

<Step title="Choose when to send">
**Send Now** starts dialing immediately. **Schedule for later** starts dialing at the time you pick.
</Step>

<Step title="Launch">
Click **Launch campaign**. The campaign starts automatically — there is no draft state.
</Step>
</Steps>

## 3. Monitor your campaign

<Steps>
<Step title="Watch progress">
Open the campaign to see its status (`scheduled` → `in-progress` → `ended`) and calls completing in real time.
</Step>

<Step title="Review call outcomes">
Each finished call shows the contact, duration, outcome, and links to the full call details — transcript, recording, and [analysis](/assistants/call-analysis).
</Step>

<Step title="Cancel if needed">
Cancelling stops new calls from being dialed; calls already in progress finish naturally. Campaigns can't be edited after launch — to change something, cancel and duplicate it with your changes (see [Advanced](/campaigns/advanced#re-running-a-campaign)).
</Step>
</Steps>

<Warning>
It is a violation of FCC law to dial phone numbers without consent in an automated manner. See our [TCPA Consent Guide](/tcpa-consent) before launching campaigns.
</Warning>
Loading
Loading