Create batch campaign
curl --request POST \
--url https://api.nixflex.com/v1/calls/batchimport requests
url = "https://api.nixflex.com/v1/calls/batch"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.nixflex.com/v1/calls/batch', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.nixflex.com/v1/calls/batch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.nixflex.com/v1/calls/batch"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.nixflex.com/v1/calls/batch")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.nixflex.com/v1/calls/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyCalls
Create batch campaign
POST /v1/calls/batch
POST
/
v1
/
calls
/
batch
Create batch campaign
curl --request POST \
--url https://api.nixflex.com/v1/calls/batchimport requests
url = "https://api.nixflex.com/v1/calls/batch"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.nixflex.com/v1/calls/batch', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.nixflex.com/v1/calls/batch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.nixflex.com/v1/calls/batch"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.nixflex.com/v1/calls/batch")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.nixflex.com/v1/calls/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyCreates a batch campaign: queues many outbound calls under one campaign ID. Useful for appointment reminders, lead qualification, surveys.
All
With
Request
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 | Yes | The number to dial from. Must be a number you have imported and attached to this agent. Works on Twilio and Telnyx. |
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
{
"phone": "+447111000001",
"variables": { "patient_name": "Sarah" },
"prompt_override": "Optional - replaces the campaign prompt for this recipient only"
}
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
scheduledand 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_minutesgreater thanwindow_end_minutes, e.g. 18:00-02:00). - No window set = the campaign fires when the date is due (legacy behaviour).
One campaign = one timezone. Calling recipients across multiple countries? Split them by region into separate campaigns, each with its own
timezone.Response
201 Created (schedule_type now launches immediately):
{
"campaign_id": "batchcall_7f8fe206a5522cb7",
"status": "running",
"valid_count": 3,
"invalid_count": 0,
"invalid": []
}
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. The agent leaves a short message built from your
prompt, then ends the call withended_reason: voicemail_detected. See Voicemail detection to control or disable the message.
Errors
| Code | Cause |
|---|---|
missing_field | prompt missing - every campaign needs a call purpose |
invalid_phone_format | One or more recipient phones are invalid. error.details.invalid lists each one with the reason. Resend with skip_invalid: true to dial the valid ones only. |
campaign_creation_failed | from_number missing, not registered to your account, attached to a different agent, or a Telnyx number. Also invalid_timezone (bad IANA value). The message states the exact reason. |
campaign_launch_failed | The campaign passed validation but could not be launched - usually no carrier credentials for the from_number. The campaign is marked failed. |
Error response shape
Every error follows the same shape. On a rejected batch,error.details.invalid names each bad number and why, so you can show it to your own users:
{
"error": {
"type": "invalid_request",
"code": "invalid_phone_format",
"message": "1 recipient(s) have invalid phone numbers. Fix them or pass skip_invalid=true to dial only valid ones.",
"doc_url": "https://docs.nixflex.com/errors/invalid_phone_format",
"details": {
"total": 2,
"valid_count": 1,
"invalid_count": 1,
"invalid": [
{
"phone": "+44745357377",
"reason": "not a real number for that country code - check the digits (wrong length, or an area/mobile prefix that does not exist)",
"row_index": 0
}
],
"hint": "Fix the numbers or resend with skip_invalid=true to dial only the valid ones."
}
}
}
Numbers are checked against real numbering rules, not just E.164 shape.
+44745357377 looks like a UK mobile but is one digit short, so it can never connect - it is rejected here rather than accepted and lost.Launching a scheduled campaign
Aschedule_type: schedule campaign fires automatically inside its window. You can also launch it manually with Launch batch.