> ## Documentation Index
> Fetch the complete documentation index at: https://docs.support.onclarity.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Run an outbound campaign

> Call a list of numbers with per-contact variables, concurrency limits, calling hours, retries and callbacks.

A **campaign** calls a list of phone numbers with a voice agent. Each number is a campaign **item** with its own variables, so the agent can greet every contact by name or talk about their own order. Clarity dials the items in the background, a few at a time, and records each call as a [conversation](/voice/conversations.md).

For a single call, [dispatch it directly](/voice/outbound-calls.md).

## Lifecycle

| Status | Meaning |
| - | - |
| `draft` | Created without a `start_time`. Editable. Nothing is dialed. |
| `scheduled` | Has a `start_time`. Starts dialing when that time comes. Still editable. |
| `running` | Dialing. Locked: cancel it to stop. |
| `canceled` | Stopped. Calls already in progress finish; nothing new is dialed. Can be started again. |
| `ended` | Every item has reached a final status. |

> [!NOTE]
>
> Starting, scheduling and cancelling a campaign changes what is dialed live, so these calls need the publish permission. Your organization API key has it.

## Create a campaign

**1. Create it as a draft with its items**

```bash theme={null}
curl -X POST https://voice-api.onclarity.com/v1/projects/$PROJECT_ID/campaigns \
  -H "Authorization: Bearer $CLARITY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "October renewals",
    "agent_id": "5e1d7c2a-0f4b-4b8e-8c3d-9a6f2e1b7c40",
    "from_number": "+14155550100",
    "max_concurrency": 5,
    "retries": 2,
    "retry_delay_seconds": 3600,
    "calling_window": {
      "start": "09:00",
      "end": "18:00",
      "days": [1, 2, 3, 4, 5],
      "timezone": "America/New_York"
    },
    "items": [
      {
        "name": "Dana Levi",
        "phone_number": "+14155550123",
        "custom_vars": { "plan": "Pro", "renewal_date": "2026-11-01" }
      },
      {
        "name": "Sam Ortiz",
        "phone_number": "+14155550124",
        "custom_vars": { "plan": "Starter", "renewal_date": "2026-11-03" }
      }
    ]
  }'
```

The response is `201` with `{ "campaign": {...}, "items": [...] }`. Without a `start_time` the campaign is a `draft`.

**2. Add more items while it is a draft**

```bash theme={null}
curl -X POST https://voice-api.onclarity.com/v1/projects/$PROJECT_ID/campaigns/$CAMPAIGN_ID/items \
  -H "Authorization: Bearer $CLARITY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "name": "Noor Haddad", "phone_number": "+14155550125", "custom_vars": { "plan": "Pro", "renewal_date": "2026-11-04" } }
    ]
  }'
```

The response is `201` with `{ "items": [...] }`.

**3. Start it**

```bash theme={null}
curl -X POST https://voice-api.onclarity.com/v1/projects/$PROJECT_ID/campaigns/$CAMPAIGN_ID/start \
  -H "Authorization: Bearer $CLARITY_API_KEY"
```

`start` sets `start_time` to now and answers with `{ "campaign": {...} }` in status `scheduled`. Clarity's scheduler turns it `running` shortly after and dials within the calling window.

To schedule instead of starting now, send a `start_time` when you create or update the campaign.

### Campaign fields

**name** `string` *required*

Display name.

**agent\_id** `string`

The agent that places the calls. Items can override it. The agent must be in the project and published (`409 AGENT_NOT_PUBLISHED` otherwise); an unknown id is refused with `422`. An item with no agent of its own on a campaign with no agent fails when it is dialed.

**from\_number** `string`

The caller ID. When it is not an outbound-enabled number of the project, it is ignored and the agent's own caller ID is used, the same order as for [dispatch](/voice/outbound-calls.md#before-you-start). Unlike `dispatch-call`, the campaign does not fail.

**start\_time** `string`

ISO 8601 with a time zone offset, for example `2026-11-01T09:00:00-05:00`. Setting it schedules the campaign; setting it to `null` on update turns a scheduled campaign back into a draft.

**max\_concurrency** `integer` (default: 5)

How many calls run at the same time. From 5 to 20.

**retries** `integer` (default: 0)

How many more times to dial a number whose call did not connect.

**retry\_delay\_seconds** `integer` (default: 0)

How long to wait before each retry.

**calling\_window** `object`

When dialing is allowed. `null` or absent means any time.

* `start`, `end`: `HH:MM`, 24-hour. Calls start at or after `start` and before `end`. An `end` earlier than `start` wraps past midnight.
* `days`: ISO weekdays, Monday `1` to Sunday `7`. Absent or empty means every day.
* `timezone`: an IANA zone such as `Europe/London`.

The window controls when calls start; a call in progress at `end` is not cut off.

**callback** `boolean` (default: false)

The default for each item's `callback`. See [Callbacks](#callbacks).

**items** `array`

The contacts to call. You can also add them later.

### Item fields

**phone\_number** `string` *required*

The number to call, in E.164 format. A number that cannot be parsed is stored with status `invalid` and never dialed.

**name** `string`

The contact's name. The agent can use it as the `contact_name` variable.

**custom\_vars** `object`

Values for the variables your agent's prompt uses, keyed by variable name, for example `{"plan": "Pro"}` for a `plan` variable. Values must be strings. A variable the prompt does not use is ignored. A `contact_name` key here takes precedence over `name`.

**agent\_id** `string`

Overrides the campaign's agent for this contact.

**callback** `boolean`

Whether this contact can ask to be called back. Defaults to the campaign's `callback`.

## How items are dialed

| Item status | Meaning |
| - | - |
| `scheduled` | Waiting to be dialed, or waiting for a retry. |
| `ongoing` | A call is in progress. |
| `ended` | The call connected. |
| `failed` | The call failed for good, or every retry went unanswered. `failed_reason` says which. |
| `rescheduled` | The contact asked for a callback; a new item was created for it. |
| `invalid` | The phone number could not be parsed. Never dialed. |

* **Connected calls end the item.** A call that reached someone is `ended`. Clarity does not yet tell a voicemail from a person in this step, so a call picked up by voicemail also counts as connected.
* **Unanswered calls are retried.** Busy, no answer, declined, or rang and dropped: the item goes back to `scheduled` for another attempt after `retry_delay_seconds`, until `retries` is used up. Then it is `failed` with a reason such as `no answer after 3 attempt(s)`.
* **Some failures are never retried.** The carrier answers that the number does not exist or is incomplete (SIP 404, 484, 485 or 604).
* **The campaign ends on its own** when no item is `scheduled` or `ongoing`.

Each item's `attempt_count` counts dials so far, and its `conversation_id` points to the latest call. Conversations placed by a campaign carry `campaign_id` and `campaign_item_id`, so you can filter and join them.

### Callbacks

When an item has `callback: true`, the agent is asked to note at the end of the call whether the contact wants a callback and when. If they do, and the campaign is still `running`, Clarity creates a new item for the requested time with the same name, number and variables, and marks the original `rescheduled`. A requested time in the past is dialed as soon as possible.

## Monitor a campaign

```bash theme={null}
curl https://voice-api.onclarity.com/v1/projects/$PROJECT_ID/campaigns/$CAMPAIGN_ID/analytics \
  -H "Authorization: Bearer $CLARITY_API_KEY"
```

```json theme={null}
{
  "analytics": {
    "campaign_id": "c4a9e2f0-3b1d-4c7e-9a8f-6d5b2e1c0f93",
    "status": "running",
    "total_items": 3,
    "by_status": {
      "draft": 0,
      "scheduled": 1,
      "ongoing": 1,
      "failed": 0,
      "ended": 1,
      "rescheduled": 0,
      "invalid": 0
    },
    "in_flight_calls": 1,
    "completion": { "terminal": 1, "total": 3, "pct": 33.3 },
    "retries_made": 1,
    "callbacks_created": 0,
    "timeseries": [
      { "ts": "2026-10-20T14:02:00.000Z", "ended": 1, "failed": 0, "rescheduled": 0, "total": 1 }
    ]
  }
}
```

* `completion.pct` is the share of items in a final status, as a percentage.
* `timeseries` groups items by the minute they last changed.

To see each contact, read the campaign with its items: `GET /v1/projects/{projectId}/campaigns/{id}` returns `{ "campaign": {...}, "items": [...] }`. Items there also carry `duration_sec` (total call time) and `resolution` (`resolved`, `unresolved` or `null`).

To list campaigns, use `GET /v1/projects/{projectId}/campaigns`. Filter with `status`, comma-separated, for example `?status=scheduled,running`. Each campaign carries `item_counts` by status.

## Change, stop and reuse

| Action | Request | Allowed when |
| - | - | - |
| Edit settings | `PUT /campaigns/{id}` with the fields to change | `draft` or `scheduled` |
| Edit an item | `PUT /campaigns/{id}/items/{itemId}` | `draft` or `scheduled` |
| Remove an item | `DELETE /campaigns/{id}/items/{itemId}` | `draft` or `scheduled` |
| Cancel | `POST /campaigns/{id}/cancel` | Not `canceled` or `ended` |
| Resume | `POST /campaigns/{id}/start` | `canceled` |
| Copy | `POST /campaigns/{id}/clone` | Any status |
| Delete | `DELETE /campaigns/{id}` | `draft`, `canceled` or `ended` |

All paths are under `/v1/projects/{projectId}`. A request outside the allowed status answers `409`.

* **Cancel** lets calls in progress finish. It stops new dials, retries and callbacks.
* **Resume** dials the items that had not reached a final status. A campaign canceled before its scheduled time resumes at that time; otherwise it resumes now.
* **Clone** creates a new `draft` named `<name> (copy)` with the same settings and contacts, and none of the results. Callback items are not copied.
* Changing an item's `phone_number` re-checks it: a bad number turns the item `invalid`, a good one makes it `scheduled` again.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.