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

# Create batch campaign

> POST /v1/calls/batch

Creates a batch campaign: queues many outbound calls under one campaign ID. Useful for appointment reminders, lead qualification, surveys.

## Request

```bash theme={null}
curl -X POST https://api.nixflex.com/v1/calls/batch \
  -H "Authorization: Bearer KEY_ID:KEY_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_id": "agent_125207e452f8714a",
    "from_number": "+447446466847",
    "name": "Tuesday appointment reminders",
    "prompt": "You are Sarah from Bright Smile Dental. Call to remind the patient about their check-up tomorrow and confirm they can attend.",
    "timezone": "Europe/London",
    "recipients": [
      { "phone": "+447111000001" },
      { "phone": "+447111000002", "variables": { "patient_name": "John" } },
      { "phone": "+447111000003" }
    ],
    "schedule_type": "schedule",
    "scheduled_date": "2026-06-15",
    "window_start_minutes": 540,
    "window_end_minutes": 1080,
    "window_days": ["mon","tue","wed","thu","fri"]
  }'
```

## Body parameters

| Field                  | Type   | Required    | Notes                                                                                                                                                                                                                      |
| ---------------------- | ------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agent_id`             | string | Yes         | Agent used for all calls in this batch                                                                                                                                                                                     |
| `prompt`               | string | Yes         | The call purpose for every call in the campaign - what the agent should say and why it is calling. Per-recipient `prompt_override` replaces it for that recipient.                                                         |
| `name`                 | string | No          | Display name for the campaign                                                                                                                                                                                              |
| `from_number`          | string | See note    | Your Twilio number to dial from. Optional only when the agent has exactly one outbound-enabled number (it auto-resolves). If the agent has multiple numbers attached, this field is required - the launch fails otherwise. |
| `recipients`           | array  | Yes         | Recipient objects (see below)                                                                                                                                                                                              |
| `skip_invalid`         | bool   | No          | Default `false`: reject the whole request if any phone is invalid. `true`: dial valid numbers only; invalid ones are listed in the response.                                                                               |
| `schedule_type`        | enum   | No          | `now` (launch immediately, default) or `schedule`                                                                                                                                                                          |
| `scheduled_date`       | date   | If schedule | `YYYY-MM-DD` - the day the campaign becomes due                                                                                                                                                                            |
| `window_start_minutes` | int    | No          | Calling window start, minutes since midnight (540 = 9:00am)                                                                                                                                                                |
| `window_end_minutes`   | int    | No          | Calling window end, minutes since midnight (1080 = 6:00pm)                                                                                                                                                                 |
| `window_days`          | array  | No          | Allowed weekdays, e.g. `["mon","tue","wed","thu","fri"]`                                                                                                                                                                   |
| `timezone`             | string | No          | IANA timezone the calling window runs in, e.g. `Europe/London`, `Asia/Dubai`, `America/New_York`. Invalid values are rejected with `invalid_timezone`.                                                                     |

### Recipient object

```json theme={null}
{
  "phone": "+447111000001",
  "variables": { "patient_name": "Sarah" },
  "prompt_override": "Optional - replaces the campaign prompt for this recipient only"
}
```

All `variables` are automatically injected as context for the agent - you do not need `{{placeholders}}` in your prompt.

## Calling window and timezone

Scheduled campaigns do not fire at midnight - they fire **inside the calling window, in local time**:

* The scheduler checks every 60 seconds. A campaign that is due but outside its window stays `scheduled` and is re-checked each minute - it fires within a minute of the window opening.
* The window runs in this priority of timezones: **campaign `timezone`** (if you sent one) -> **the agent's timezone** -> `Europe/London`.
* Resellers: your end-user picks their timezone in your app, you send it per campaign - their choice wins over the agent default.
* Overnight windows are supported (`window_start_minutes` greater than `window_end_minutes`, e.g. 18:00-02:00).
* No window set = the campaign fires when the date is due (legacy behaviour).

<Note>
  One campaign = one timezone. Calling recipients across multiple countries? Split them by region into separate campaigns, each with its own `timezone`.
</Note>

## Response

`201 Created` (schedule\_type `now` launches immediately):

```json theme={null}
{
  "campaign_id": "batchcall_7f8fe206a5522cb7",
  "status": "running",
  "valid_count": 3,
  "invalid_count": 0,
  "invalid": []
}
```

With `schedule_type: schedule` the response returns `status: scheduled` and the campaign fires inside its window on the scheduled date.

## Behaviour

* Calls are queued and processed respecting your per-key concurrent call limit
* Each recipient becomes one outbound call with its own `call_id`
* Failed calls are logged but do not stop the campaign
* Voicemail detection runs per call; detected voicemails end cleanly with `ended_reason: voicemail_detected`

## Errors

| Code                       | Cause                                                                                                                   |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `missing_field`            | `prompt` missing - every campaign needs a call purpose                                                                  |
| `invalid_phone_format`     | One or more recipient phones invalid (response lists them; resend with `skip_invalid: true` to dial valid only)         |
| `campaign_creation_failed` | Includes `invalid_timezone` (bad IANA value) and `from_number` ownership failures - the message states the exact reason |

## Launching a scheduled campaign

A `schedule_type: schedule` campaign fires automatically inside its window. You can also launch it manually with [Launch batch](/api-reference/calls/batch-launch).
