Skip to main content
Batch calls use the Developer API 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.

Create a draft

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. 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:
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:
  2. Send consecutive chunks to POST /batch-calls/{batchId}/recipients:
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.
Last modified on November 2, 2026