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

# Batch calling API

> Create, schedule and manage phone or WhatsApp voice batches, import recipients and retrieve call results.

Batch calls use the [Developer API](https://cntillion.com/docs/api-reference/developer-api.md) workspace
keys. The base URL is `https://app.cntillion.com/api/v2`. Reads accept either
key permission. Every write requires a **Full access** key, a plan with
campaigns and an `Idempotency-Key` header.

A batch belongs to one agent. Its settings and recipients can change while
it is a draft. Launch freezes them; duplicate a launched batch to make changes.
The dashboard and API share the same calling checks and results.

## Endpoints

Paths below are relative to `/api/v2`.

| Method | Path | Behavior |
| - | - | - |
| GET | `/batch-calls` | List this workspace's batches. |
| GET | `/agents/{agentId}/batch-calls` | List an agent's batches. |
| POST | `/agents/{agentId}/batch-calls` | Create a draft, optionally with recipients and launch. |
| GET | `/batch-calls/{batchId}` | Read settings, progress and result counts. |
| PUT | `/batch-calls/{batchId}` | Replace draft settings using its latest `updatedAt`. |
| GET | `/batch-calls/{batchId}/recipients` | Read recipient statuses, overrides and conversation references. |
| POST | `/batch-calls/{batchId}/import` | Begin or replace a recipient import. |
| POST | `/batch-calls/{batchId}/recipients` | Append or replay a chunk of that import. |
| POST | `/batch-calls/{batchId}/launch` | Launch or schedule a completed draft. |
| POST | `/batch-calls/{batchId}/pause` | Pause a running or scheduled batch. |
| POST | `/batch-calls/{batchId}/resume` | Resume a paused batch. |
| POST | `/batch-calls/{batchId}/cancel` | Cancel pending recipients. Calls already started may finish. |
| POST | `/batch-calls/{batchId}/duplicate` | Copy recipients and settings to a new draft. |
| POST | `/batch-calls/{batchId}/retry` | Create a draft containing eligible failed or unanswered recipients. |
| DELETE | `/batch-calls/{batchId}` | Delete a draft, completed or cancelled batch with no active calls. |

## Create a draft

```bash theme={null}
curl 'https://app.cntillion.com/api/v2/agents/YOUR_AGENT_ID/batch-calls' \
  -H "Authorization: Bearer $CENTILLION_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: appointments-november-draft' \
  -d '{
    "name": "November appointments",
    "settings": {
      "channel": "telephony",
      "phoneLineId": "YOUR_PHONE_LINE_ID",
      "branchId": null,
      "ringingTimeoutSeconds": 60,
      "concurrencyLimit": null,
      "allowedOverrides": ["first_message"]
    },
    "timezone": "America/Los_Angeles",
    "startsAt": null,
    "consentConfirmed": true,
    "sendRatePerMinute": 20,
    "recipients": [{
      "phone": "+12025550101",
      "name": "Jamie",
      "variables": {"name": "Jamie", "appointment_date": "2026-11-02"},
      "overrides": {"first_message": "Hello {{name}}. This is a reminder about {{appointment_date}}."}
    }],
    "launch": false
  }'
```

The response is `201`, with the batch in `data`. Keep `data.id` and
`data.updatedAt` for subsequent requests. `data.config.import.kind` is `ready`
after a complete recipient list is saved. Without recipients, it is `empty`.

| Field | Meaning |
| - | - |
| `name` | A name from 1 to 120 characters. |
| `settings.channel` | `telephony` or `whatsapp`. Omission retains the telephony format. |
| `settings.phoneLineId` | For telephony, an active outbound number assigned to this agent, or `null` while drafting. |
| `settings.connectionId` | For WhatsApp, an active calling account assigned to this agent, or `null` while drafting. Omit `phoneLineId`. |
| `settings.branchId` | A published branch ID, or `null` to follow the agent's traffic split. |
| `settings.ringingTimeoutSeconds` | Integer from 5 to 120. |
| `settings.answeringMachineDetection` | Optional boolean. `false` keeps a call going past voicemail or an automated greeting. On by default. |
| `settings.concurrencyLimit` | A positive integer within workspace and agent limits, or `null` for Automatic. |
| `settings.allowedOverrides` | Any of `prompt`, `first_message`, `voice_id` and `language`. |
| `timezone` | An IANA timezone, such as `America/Los_Angeles`. |
| `startsAt` | An ISO timestamp in UTC for a future start, or `null` to start when launched. |
| `consentConfirmed` | Confirms that you have the recipients' permission to call them. A batch launches only when it is `true`. It does not create consent records. |
| `sendRatePerMinute` | Integer from 1 to 20. |
| `recipients` | Up to 1,000 inline recipients. Omit it or use `[]` for a draft without an import. |
| `launch` | Set `true` to create, import and launch atomically. Defaults to `false`. |

Each recipient requires `phone`, `name`, `variables` and `overrides`. Use an
E.164 `phone` with its country code for both transports. The dashboard's
WhatsApp file column `whatsapp_user_id` is normalized to this API format.
Use an empty name or empty objects when those fields are not needed.

Variables are strings with simple names, up to 60 values per recipient.
Each value can contain up to 2,048 characters, and each recipient must fit
within 16 KB. Protected names such as `system`, `call`, `contact`, `campaign`,
`constructor` and `__proto__` are unavailable. Overrides must be enabled in
`allowedOverrides`. `language` accepts `en`, `es` or `it`; a `voice_id` must use
the agent's voice provider and be saved in the workspace. The request body
must fit within 4 MB.

The API rejects duplicate numbers. Creating a batch and importing inline
recipients is atomic: a failed import leaves no partial batch or new contacts.

## Update or launch

Send `PUT /batch-calls/{batchId}` with the complete draft settings from the
table above and `updatedAt` from the latest response. Omit `recipients` and
`launch` from the update body. Updating settings keeps the imported recipients.
If another request changed the draft, the API returns `409`; fetch the latest
draft before applying your changes.

Launch with:

```json theme={null}
{"updatedAt": "2026-10-27T19:00:00.000Z"}
```

Send that body to `POST /batch-calls/{batchId}/launch` with a new
`Idempotency-Key`. A future `startsAt` produces `SCHEDULED`; otherwise the
batch becomes `RUNNING`. Available capacity, the agent's call limits and the
calling number's daily limit can postpone individual recipients. A batch has
no calling hours or quiet hours: it calls at any hour until its list is done.

Calls don't check consent records, calling hours or how often a number is
called. Those are your responsibility, and `consentConfirmed` is your
confirmation. Numbers on the workspace's do-not-call list, and numbers that
reached someone other than the contact on an earlier call, are skipped. The
agent needs voice enabled and a calling number that can place calls. WhatsApp
also requires cloud voice calling, a calling-enabled account and an active
WhatsApp calling permission for the recipient.

## Import larger lists

Up to 50,000 recipients can be imported into a draft. The total serialized
import must remain within 25 MB.

1. Begin with `POST /batch-calls/{batchId}/import`:

   ```json theme={null}
   {
     "updatedAt": "2026-10-27T19:00:00.000Z",
     "token": "a27c63cd-02d9-4b68-8d69-58acd9006c20",
     "fileName": "appointments.csv",
     "headers": ["phone_number", "name", "appointment_date"],
     "expectedRows": 1200
   }
   ```

2. Send consecutive chunks to `POST /batch-calls/{batchId}/recipients`:

   ```json theme={null}
   {
     "token": "a27c63cd-02d9-4b68-8d69-58acd9006c20",
     "offset": 0,
     "rows": [{
       "phone": "+12025550101",
       "name": "Jamie",
       "variables": {"name": "Jamie", "appointment_date": "2026-11-02"},
       "overrides": {}
     }]
   }
   ```

Generate a UUID token for each import. Use `whatsapp_user_id` in `headers`
for a WhatsApp batch. Headers describe the original file; the API receives
parsed JSON rows. Each chunk holds 1 to 200 rows. Increase `offset` by the
number of rows committed in the previous chunk. Each chunk needs its own
idempotency key; retry a lost response with the same key and body.

A repeated offset with identical rows is also safe after the response replay
expires. Different rows at a previously committed offset are refused. Starting
a new import replaces the previous recipient list. The batch cannot launch
until `import.kind` becomes `ready` and all expected rows are present.

## Read results and manage a batch

List endpoints accept `limit` and `cursor`. Use the returned
`pagination.cursor` for the next page with the same filters. Batch lists
also accept `status` and `search`; the workspace list accepts `agent_id`.
Statuses are `DRAFT`, `SCHEDULED`, `RUNNING`, `PAUSED`, `COMPLETED` and
`CANCELLED`.

The recipient list accepts a `status` filter of `QUEUED`, `WAITING_FOR_VOICE`,
`SENDING`, `SENT`, `FAILED`, `SKIPPED` or `NEEDS_REVIEW`. A `SENT` row can still
have an active call. Inspect `voiceCall.endedAt`, `dialOutcome`, `outcome` and
`lastError` for its result. `voiceCall.conversationLogId` identifies the
conversation when available. Uploaded variables and overrides are returned
with the recipient.

Send `{}` for pause, resume, cancel, duplicate and retry actions. Duplication
and retry return `201` with a new draft, with consent confirmation cleared.
Retry excludes recipients marked for review because their prior dial could
not be confirmed. Review them before making another attempt. Deleting a batch
keeps contacts and conversation history.

A reused idempotency key with a different body returns
`409 IDEMPOTENCY_KEY_REUSED`. Missing batches return `404 BATCH_NOT_FOUND`.
A stale revision, incomplete import, unavailable calling setup or unsupported
state transition returns `409 BATCH_STATE_CONFLICT`, with the reason in
`error.message`.

Recipient `variables` accept text, finite numbers and booleans. Names beginning
with `secret__` or `system__`, internal names beginning with `__`, and built-in
namespaces are reserved. Batch calls use the supplied recipient data and skip
the initiation webhook. The same agent can answer incoming calls. See
[one agent for inbound and outbound calls](https://cntillion.com/docs/agents/personalization.md#one-agent-for-inbound-and-outbound-calls).


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