# Create a campaign (/en/api-reference/api-ref/conversational-ai/platform/create-campaign)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

> Documentation: Conversational AI API.
> Documentation version: Preview.
> This document describes only this version. [Version index](/llms/doc-versions-convoai-api-v2.txt).


Creates an outbound campaign that dials a list of contacts with a saved agent.

- OpenAPI: /openapi/conversational-ai/rest-api-platform.en.yaml
- Operation ID: create-campaign
- Method: POST
- Path: /v1/projects/{appid}/campaigns
- Endpoint: https://preview.ai.agora.io/conversational-ai/v1/projects/{appid}/campaigns

## Servers

- https://preview.ai.agora.io/conversational-ai

Each contact is one dial task, and each dial starts a telephony session with the agent in `agent_id`. You supply the contacts when you create the campaign, and you can't change them afterward.

Before you create a campaign, [create the agent](/en/api-reference/api-ref/conversational-ai/platform/create-agent) and [register the phone number](/en/api-reference/api-ref/conversational-ai/platform/create-number) to call from.


:::warning[Caution]
A campaign places real phone calls. When `schedule.mode` is `immediate`, dialing starts as soon as the campaign is created. To test, create a `scheduled` campaign with a start time in the future, then [cancel it](/en/api-reference/api-ref/conversational-ai/platform/cancel-campaign).
:::

## Authorization

This endpoint requires authentication.

- `tokenAuth`
- `basicAuth`

## Parameters

- `appid` (path, required, string) - The App ID of your project.

## Request body

- `name` (string, required) - A name for the campaign that helps you identify it, 1 to 255 characters.
  - Min length: `1`
  - Max length: `255`
- `agent_id` (string, required) - The ID of the saved agent that makes the calls.
  - Format: `uuid`
- `transport` (object, required) - How the campaign reaches its contacts.
  - `transport.type` (string, required) - `telephony`: The campaign places phone calls.
  - `transport.from` (string, required) - The `number_id` of the number to call from, or a list of up to 100 unique `number_id` values. With a list, the campaign rotates across the numbers.
    - Format: `uuid`
    - Min items: `1`
    - Max items: `100`
    - `transport.from.items` (string)
      - Format: `uuid`
  - `transport.call_policy` (object) - Limits and behavior for each call the campaign places. Each limit you omit adds no limit.
    - `transport.call_policy.max_ring_duration_ms` (integer) - The maximum time in milliseconds from starting the dial until the call is answered. If the call isn't answered in time, the dial attempt ends.
      - Range: `[1, 259200000]`
    - `transport.call_policy.max_call_duration_seconds` (integer) - The maximum length of the call in seconds, measured from when it's answered. The session's `lifecycle` limits can end the call earlier.
      - Range: `[1, 259200]`
    - `transport.call_policy.max_silence_duration_ms` (integer) - Hang up when nobody on the call has spoken for this many milliseconds. This is separate from `pipeline.silence_config`, which only prompts the user.
      - Range: `[30000, 259200000]`
    - `transport.call_policy.end_call` (object) - Conditions under which the agent can end the call itself. Every condition is off unless you set it to `true`.
      - `transport.call_policy.end_call.on_user_request` (boolean) - End the call when the caller clearly says goodbye or asks to hang up.
        - Default: `false`
      - `transport.call_policy.end_call.on_voicemail` (boolean) - End the call when the agent reaches voicemail, such as a "please leave a message after the tone" announcement.
        - Default: `false`
      - `transport.call_policy.end_call.on_ai_assistant` (boolean) - End the call when the call is answered by an AI assistant, a voice secretary, or another automated answering system.
        - Default: `false`
- `contacts` (array, required) - The contacts to call, from 1 to 50,000. The whole request body can be at most 10 MiB. You can't change the list after you create the campaign. A phone number that appears more than once is called once for each entry.
  - Min items: `1`
  - Max items: `50000`
  - `contacts.items` (object) - One contact to call. Each contact is a separate dial task.
    - `contacts.items.phone` (string, required) - The phone number to call, in E.164 format, for example `+12025550123`.
    - `contacts.items.values` (object) - Values for the agent's variables for this contact, keyed by variable name, up to 100. Each key must be a variable the agent declares, and each value must match its `type` and `options`. Pass a value for every `required` variable. If any contact breaks these rules, the whole request fails with `400`, and no campaign is created. `null` values are rejected.
- `schedule` (object, required) - When the campaign dials. Pass the fields of one schedule mode only.
  - `schedule.mode` (string, required) - `immediate`: The campaign starts dialing as soon as it's created.
  - `schedule.timezone` (string, required) - The IANA time zone for `days`, for example `America/New_York`. Daylight saving time is handled automatically. Required when you pass `days`.
    - Min length: `1`
    - Max length: `255`
  - `schedule.start` (string, required) - When the campaign can start dialing, as a local date and time in `YYYY-MM-DDTHH:MM` format with no offset, for example `2026-11-02T09:00`. A local time that doesn't exist or occurs twice because of a daylight saving change is rejected.
  - `schedule.end` (string, required) - When the campaign stops placing new calls, as a local date and time in `YYYY-MM-DDTHH:MM` format, later than `start`. Calls in progress continue. A local time that doesn't exist or occurs twice because of a daylight saving change is rejected.
  - `schedule.days` (array) - The weekly hours when new calls can start. Outside these hours, no new calls start. A day you don't list has no dialing. Omit `days` to dial at any time.
    - Min items: `1`
    - Max items: `7`
    - `schedule.days.items` (object)
      - `schedule.days.items.weekday` (string, required) - The day of the week. Each day can appear only once.
        - Allowed: `mon` | `tue` | `wed` | `thu` | `fri` | `sat` | `sun`
      - `schedule.days.items.ranges` (array, required) - One to 10 windows on this day, which can't overlap.
        - Min items: `1`
        - Max items: `10`
        - `schedule.days.items.ranges.items` (object)
          - `schedule.days.items.ranges.items.start` (string, required) - The start of the window, as a local time in 24-hour `HH:MM` format.
          - `schedule.days.items.ranges.items.end` (string, required) - The end of the window, as a local time in `HH:MM` format, later than `start`. Use `24:00` for the end of the day.
- `lifecycle` (object) - The lifecycle settings for each session the campaign starts. Each session can last up to 1 hour, and lasts up to 5 minutes by default.
  - `lifecycle.idle_timeout_ms` (integer) - How long in milliseconds the session continues with no participant present: after the subscribed user leaves the RTC channel, or after the other side of a phone call hangs up. It isn't silence detection. Set to `0` to turn off the idle timer. Can't be greater than `max_duration_ms`.
    - Default: `30000`
    - Range: `[0, 3600000]`
  - `lifecycle.max_duration_ms` (integer) - The maximum length of each session in milliseconds, measured from `start_ts`. Defaults to 5 minutes. The maximum is 1 hour.
    - Default: `300000`
    - Range: `[1000, 3600000]`
  - `lifecycle.graceful_stop` (object) - How the agent finishes when you stop the session, or when the idle timer expires. It doesn't extend `max_duration_ms`, and it doesn't add a farewell message.
    - `lifecycle.graceful_stop.enabled` (boolean) - Whether the agent finishes what it's saying before it leaves.
      - Default: `false`
    - `lifecycle.graceful_stop.timeout_ms` (integer) - The longest time in milliseconds the agent can take to finish.
      - Default: `30000`
      - Range: `[0, 120000]`
- `pacing` (object) - How fast the campaign dials. Each contact is called once.
  - `pacing.max_concurrent` (integer) - The maximum number of calls in progress at the same time.
    - Default: `10`
    - Range: `[1, 10000]`
  - `pacing.max_calls_per_second` (integer) - The maximum number of new dials in any one-second window. If you omit it, the campaign uses your project's default dial rate. A value above the project's limit is rejected with `InvalidFieldValue`.
    - Range: `[1, 1000]`
- `data_policy` (object) - How Agora handles the session's data. You set it when the session starts and can't change it afterward.
  - `data_policy.retention` (string) - How long Agora keeps the conversation data:

- `30_days`: Agora keeps the data for 30 days.
- `none`: Agora doesn't keep the data, and variable extraction doesn't run. Use it, for
  example, when a caller declines recording.
    - Allowed: `30_days` | `none`
    - Default: `30_days`

### Request body example

```json
{
  "name": "q3-renewals",
  "agent_id": "3f6c2a9e-8d1b-4e7a-9c55-0b2f7d1e4a10",
  "transport": {
    "type": "telephony",
    "from": "92e68758-3d3a-4f27-8b93-3fd8d834467c"
  },
  "contacts": [
    {
      "phone": "+12025550123",
      "values": {
        "name": "Ava",
        "renewal_year": 2027
      }
    }
  ],
  "lifecycle": {
    "idle_timeout_ms": 30000
  },
  "pacing": {
    "max_concurrent": 10,
    "max_calls_per_second": 2
  },
  "schedule": {
    "mode": "scheduled",
    "timezone": "America/New_York",
    "start": "2026-11-02T09:00",
    "end": "2026-11-13T17:00",
    "days": [
      {
        "weekday": "mon",
        "ranges": [
          {
            "start": "09:00",
            "end": "12:00"
          },
          {
            "start": "13:00",
            "end": "17:00"
          }
        ]
      },
      {
        "weekday": "wed",
        "ranges": [
          {
            "start": "09:00",
            "end": "17:00"
          }
        ]
      }
    ]
  }
}
```

## Responses

### 201

The campaign was created. The response body contains the campaign.

- `campaign_id` (string) - The ID of the campaign, a UUID.
  - Format: `uuid`
- `name` (string) - The name of the campaign.
- `status` (string) - The status of the campaign:

- `scheduled`: The campaign is waiting for its start time.
- `running`: The campaign is dialing, or can dial during its schedule.
- `paused`: You paused the campaign.
- `completed`: The campaign has finished.
- `canceled`: You canceled the campaign.
- `failed`: The campaign failed. The details are in `failure`.
  - Allowed: `scheduled` | `running` | `paused` | `completed` | `canceled` | `failed`
- `agent_id` (string) - The ID of the agent that makes the calls.
  - Format: `uuid`
- `transport` (object)
  - `transport.type` (string) - Always `telephony`.
  - `transport.from` (array) - The `number_id` values of the numbers the campaign calls from.
    - `transport.from.items` (string)
  - `transport.call_policy` (object) - Limits and behavior for each call the campaign places. Each limit you omit adds no limit.
    - `transport.call_policy.max_ring_duration_ms` (integer) - The maximum time in milliseconds from starting the dial until the call is answered. If the call isn't answered in time, the dial attempt ends.
      - Range: `[1, 259200000]`
    - `transport.call_policy.max_call_duration_seconds` (integer) - The maximum length of the call in seconds, measured from when it's answered. The session's `lifecycle` limits can end the call earlier.
      - Range: `[1, 259200]`
    - `transport.call_policy.max_silence_duration_ms` (integer) - Hang up when nobody on the call has spoken for this many milliseconds. This is separate from `pipeline.silence_config`, which only prompts the user.
      - Range: `[30000, 259200000]`
    - `transport.call_policy.end_call` (object) - Conditions under which the agent can end the call itself. Every condition is off unless you set it to `true`.
      - `transport.call_policy.end_call.on_user_request` (boolean) - End the call when the caller clearly says goodbye or asks to hang up.
        - Default: `false`
      - `transport.call_policy.end_call.on_voicemail` (boolean) - End the call when the agent reaches voicemail, such as a "please leave a message after the tone" announcement.
        - Default: `false`
      - `transport.call_policy.end_call.on_ai_assistant` (boolean) - End the call when the call is answered by an AI assistant, a voice secretary, or another automated answering system.
        - Default: `false`
- `lifecycle` (object) - When each session the campaign starts ends on its own.
  - `lifecycle.idle_timeout_ms` (integer) - How long in milliseconds the session continues with no participant present: after the subscribed user leaves the RTC channel, or after the other side of a phone call hangs up. It isn't silence detection. Set to `0` to turn off the idle timer. Can't be greater than `max_duration_ms`.
    - Default: `30000`
    - Range: `[0, 3600000]`
  - `lifecycle.max_duration_ms` (integer) - The maximum length of each session in milliseconds, measured from `start_ts`. Defaults to 5 minutes. The maximum is 1 hour.
    - Default: `300000`
    - Range: `[1000, 3600000]`
  - `lifecycle.graceful_stop` (object) - How the agent finishes when you stop the session, or when the idle timer expires. It doesn't extend `max_duration_ms`, and it doesn't add a farewell message.
    - `lifecycle.graceful_stop.enabled` (boolean) - Whether the agent finishes what it's saying before it leaves.
      - Default: `false`
    - `lifecycle.graceful_stop.timeout_ms` (integer) - The longest time in milliseconds the agent can take to finish.
      - Default: `30000`
      - Range: `[0, 120000]`
- `schedule` (object) - When the campaign dials. Pass the fields of one schedule mode only.
  - `schedule.mode` (string, required) - `immediate`: The campaign starts dialing as soon as it's created.
  - `schedule.timezone` (string, required) - The IANA time zone for `days`, for example `America/New_York`. Daylight saving time is handled automatically. Required when you pass `days`.
    - Min length: `1`
    - Max length: `255`
  - `schedule.start` (string, required) - When the campaign can start dialing, as a local date and time in `YYYY-MM-DDTHH:MM` format with no offset, for example `2026-11-02T09:00`. A local time that doesn't exist or occurs twice because of a daylight saving change is rejected.
  - `schedule.end` (string, required) - When the campaign stops placing new calls, as a local date and time in `YYYY-MM-DDTHH:MM` format, later than `start`. Calls in progress continue. A local time that doesn't exist or occurs twice because of a daylight saving change is rejected.
  - `schedule.days` (array) - The weekly hours when new calls can start. Outside these hours, no new calls start. A day you don't list has no dialing. Omit `days` to dial at any time.
    - Min items: `1`
    - Max items: `7`
    - `schedule.days.items` (object)
      - `schedule.days.items.weekday` (string, required) - The day of the week. Each day can appear only once.
        - Allowed: `mon` | `tue` | `wed` | `thu` | `fri` | `sat` | `sun`
      - `schedule.days.items.ranges` (array, required) - One to 10 windows on this day, which can't overlap.
        - Min items: `1`
        - Max items: `10`
        - `schedule.days.items.ranges.items` (object)
          - `schedule.days.items.ranges.items.start` (string, required) - The start of the window, as a local time in 24-hour `HH:MM` format.
          - `schedule.days.items.ranges.items.end` (string, required) - The end of the window, as a local time in `HH:MM` format, later than `start`. Use `24:00` for the end of the day.
- `counts` (object) - The number of contacts in each state. Retrieve the campaign to get current counts.
  - `counts.contacts` (integer) - The total number of contacts.
  - `counts.pending` (integer) - Contacts not called yet, or waiting for a retry.
  - `counts.calling` (integer) - Contacts in a call now.
  - `counts.completed` (integer) - Contacts whose call has finished.
  - `counts.failed` (integer) - Contacts not reached after all attempts.
  - `counts.canceled` (integer) - Contacts that won't be called because the campaign was canceled.
- `create_ts` (integer) - The time the campaign was created, as a Unix timestamp in seconds.
- `start_ts` (integer | null | null) - The time the campaign started dialing, as a Unix timestamp in seconds. `null` before it starts.
- `complete_ts` (integer | null | null) - The time the campaign finished, as a Unix timestamp in seconds. `null` until it finishes.
- `failure` (object) - Details of the failure when `status` is `failed`. Otherwise `null`.
  - `failure.code` (string, required) - A stable runtime error code, for example `provider_error`.
  - `failure.provider` (string) - The model provider that caused the failure, for example `openai`.
  - `failure.message` (string, required) - A human-readable explanation.
  - `failure.ts` (integer, required) - The time the failure occurred, as a Unix timestamp in seconds.
### 400

The request is invalid. Check `reason` and `detail` in the response body.

- `detail` (string, required) - A human-readable description of the error. It names the field or resource involved, when there is one. Don't parse it; branch on `reason` instead.
- `reason` (string, required) - A machine-readable error reason, such as `InvalidFieldValue` or `ResourceNotFound`. For all reasons, see [Status codes](/en/api-reference/api-ref/conversational-ai/platform/status-codes).
### 401

Authentication is missing or invalid. The reason is `InvalidPermission`.

- `detail` (string, required) - A human-readable description of the error. It names the field or resource involved, when there is one. Don't parse it; branch on `reason` instead.
- `reason` (string, required) - A machine-readable error reason, such as `InvalidFieldValue` or `ResourceNotFound`. For all reasons, see [Status codes](/en/api-reference/api-ref/conversational-ai/platform/status-codes).
### 404

The resource in the path doesn't exist. The reason is `ResourceNotFound`.

- `detail` (string, required) - A human-readable description of the error. It names the field or resource involved, when there is one. Don't parse it; branch on `reason` instead.
- `reason` (string, required) - A machine-readable error reason, such as `InvalidFieldValue` or `ResourceNotFound`. For all reasons, see [Status codes](/en/api-reference/api-ref/conversational-ai/platform/status-codes).
### default

The request failed. The response body contains the `reason` and `detail` of the failure. For possible reasons, see [Status codes](/en/api-reference/api-ref/conversational-ai/platform/status-codes).

- `detail` (string, required) - A human-readable description of the error. It names the field or resource involved, when there is one. Don't parse it; branch on `reason` instead.
- `reason` (string, required) - A machine-readable error reason, such as `InvalidFieldValue` or `ResourceNotFound`. For all reasons, see [Status codes](/en/api-reference/api-ref/conversational-ai/platform/status-codes).

## Response examples

### 201

```json
{
  "campaign_id": "b4f0581e-f138-43a2-851c-a8f8d228bb37",
  "name": "q3-renewals",
  "status": "scheduled",
  "agent_id": "3f6c2a9e-8d1b-4e7a-9c55-0b2f7d1e4a10",
  "transport": {
    "type": "telephony",
    "from": [
      "92e68758-3d3a-4f27-8b93-3fd8d834467c"
    ]
  },
  "lifecycle": {
    "idle_timeout_ms": 30000,
    "max_duration_ms": 300000,
    "graceful_stop": {
      "enabled": false,
      "timeout_ms": 30000
    }
  },
  "schedule": {
    "mode": "scheduled",
    "timezone": "America/New_York",
    "start": "2026-11-02T09:00",
    "end": "2026-11-13T17:00"
  },
  "counts": {
    "contacts": 1,
    "pending": 1,
    "calling": 0,
    "completed": 0,
    "failed": 0,
    "canceled": 0
  },
  "create_ts": 1787673602,
  "start_ts": null,
  "complete_ts": null,
  "failure": null
}
```
