Create Batch Job
curl --request POST \
--url https://api.talkifai.dev/batch/create \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form file='@example-file' \
--form 'fromPhoneNumberId=<string>' \
--form 'name=<string>' \
--form 'description=<string>' \
--form concurrentCallLimit=5 \
--form maxAttempts=3 \
--form scheduledStartTime=2023-11-07T05:31:56Z \
--form timezone=UTCimport requests
url = "https://api.talkifai.dev/batch/create"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = {
"fromPhoneNumberId": "<string>",
"name": "<string>",
"description": "<string>",
"concurrentCallLimit": "5",
"maxAttempts": "3",
"scheduledStartTime": "2023-11-07T05:31:56Z",
"timezone": "UTC"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<string>');
form.append('fromPhoneNumberId', '<string>');
form.append('name', '<string>');
form.append('description', '<string>');
form.append('concurrentCallLimit', '5');
form.append('maxAttempts', '3');
form.append('scheduledStartTime', '2023-11-07T05:31:56Z');
form.append('timezone', 'UTC');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.talkifai.dev/batch/create', 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.talkifai.dev/batch/create",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"fromPhoneNumberId\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"concurrentCallLimit\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"maxAttempts\"\r\n\r\n3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"scheduledStartTime\"\r\n\r\n2023-11-07T05:31:56Z\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"timezone\"\r\n\r\nUTC\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.talkifai.dev/batch/create"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"fromPhoneNumberId\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"concurrentCallLimit\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"maxAttempts\"\r\n\r\n3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"scheduledStartTime\"\r\n\r\n2023-11-07T05:31:56Z\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"timezone\"\r\n\r\nUTC\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.talkifai.dev/batch/create")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"fromPhoneNumberId\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"concurrentCallLimit\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"maxAttempts\"\r\n\r\n3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"scheduledStartTime\"\r\n\r\n2023-11-07T05:31:56Z\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"timezone\"\r\n\r\nUTC\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.talkifai.dev/batch/create")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"fromPhoneNumberId\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"concurrentCallLimit\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"maxAttempts\"\r\n\r\n3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"scheduledStartTime\"\r\n\r\n2023-11-07T05:31:56Z\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"timezone\"\r\n\r\nUTC\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"success": true,
"batchJobId": "batch_xyz789",
"totalContacts": 100,
"estimatedDuration": "15 minutes",
"estimatedCost": "$3.50",
"status": "scheduled",
"scheduledStartTime": "2023-11-07T05:31:56Z",
"message": "<string>"
}Batch Calling
Create Batch Job
Create a new batch calling job by uploading a CSV file or sending contacts via JSON.
POST
/
batch
/
create
Create Batch Job
curl --request POST \
--url https://api.talkifai.dev/batch/create \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form file='@example-file' \
--form 'fromPhoneNumberId=<string>' \
--form 'name=<string>' \
--form 'description=<string>' \
--form concurrentCallLimit=5 \
--form maxAttempts=3 \
--form scheduledStartTime=2023-11-07T05:31:56Z \
--form timezone=UTCimport requests
url = "https://api.talkifai.dev/batch/create"
files = { "file": ("example-file", open("example-file", "rb")) }
payload = {
"fromPhoneNumberId": "<string>",
"name": "<string>",
"description": "<string>",
"concurrentCallLimit": "5",
"maxAttempts": "3",
"scheduledStartTime": "2023-11-07T05:31:56Z",
"timezone": "UTC"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('file', '<string>');
form.append('fromPhoneNumberId', '<string>');
form.append('name', '<string>');
form.append('description', '<string>');
form.append('concurrentCallLimit', '5');
form.append('maxAttempts', '3');
form.append('scheduledStartTime', '2023-11-07T05:31:56Z');
form.append('timezone', 'UTC');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.talkifai.dev/batch/create', 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.talkifai.dev/batch/create",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"fromPhoneNumberId\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"concurrentCallLimit\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"maxAttempts\"\r\n\r\n3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"scheduledStartTime\"\r\n\r\n2023-11-07T05:31:56Z\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"timezone\"\r\n\r\nUTC\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.talkifai.dev/batch/create"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"fromPhoneNumberId\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"concurrentCallLimit\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"maxAttempts\"\r\n\r\n3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"scheduledStartTime\"\r\n\r\n2023-11-07T05:31:56Z\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"timezone\"\r\n\r\nUTC\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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.talkifai.dev/batch/create")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"fromPhoneNumberId\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"concurrentCallLimit\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"maxAttempts\"\r\n\r\n3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"scheduledStartTime\"\r\n\r\n2023-11-07T05:31:56Z\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"timezone\"\r\n\r\nUTC\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.talkifai.dev/batch/create")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"fromPhoneNumberId\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"concurrentCallLimit\"\r\n\r\n5\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"maxAttempts\"\r\n\r\n3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"scheduledStartTime\"\r\n\r\n2023-11-07T05:31:56Z\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"timezone\"\r\n\r\nUTC\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"success": true,
"batchJobId": "batch_xyz789",
"totalContacts": 100,
"estimatedDuration": "15 minutes",
"estimatedCost": "$3.50",
"status": "scheduled",
"scheduledStartTime": "2023-11-07T05:31:56Z",
"message": "<string>"
}Overview
Creates a batch calling job that will automatically dial contacts from your uploaded list. The job can start immediately or be scheduled for a future time. Key Features:- CSV file upload (up to 10,000 contacts)
- Automatic phone number validation (E.164 format)
- Scheduled start with timezone support
- Configurable retry policy
- Two-level concurrency control
Prerequisites
Before creating a batch job:- Register a phone number in Studio → Phone Numbers
- Assign an outbound agent to that phone number
- Ensure sufficient credits in your organization account
- Prepare CSV file with at least a
phonecolumn
The agent assigned to the phone number must use Pipeline or Realtime architecture. Text-only agents cannot make voice calls.
Request
CSV Upload (Recommended)
POST /batch/create
Content-Type: multipart/form-data
file: [CSV file]
fromPhoneNumberId: phone_abc123
name: January Renewal Campaign
description: Follow up on expiring subscriptions
concurrentCallLimit: 5
maxAttempts: 3
scheduledStartTime: 2025-02-28T09:00:00 (optional)
timezone: America/New_York (optional, default: UTC)
JSON Request (Programmatic)
POST /batch/create
Content-Type: application/json
{
"fromPhoneNumberId": "phone_abc123",
"name": "January Renewal Campaign",
"description": "Follow up on expiring subscriptions",
"contacts": [
{
"phone": "+12025550101",
"name": "John Smith",
"customerName": "John Smith",
"renewalDate": "Feb 1"
},
{
"phone": "+12025550102",
"name": "Jane Doe",
"customerName": "Jane Doe",
"renewalDate": "Feb 5"
}
],
"concurrentCallLimit": 5,
"maxAttempts": 3,
"scheduledStartTime": "2025-02-28T09:00:00",
"timezone": "America/New_York"
}
Request Parameters
Form/Body Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
file | File | ✅ (CSV mode) | — | CSV file with contacts |
fromPhoneNumberId | string | ✅ | — | Phone number ID to call from |
name | string | ✅ | — | Batch job name (max 200 chars) |
description | string | ❌ | null | Optional description (max 1000 chars) |
contacts | array | ✅ (JSON mode) | — | List of contact objects |
concurrentCallLimit | integer | ❌ | 5 | Max concurrent calls (1-50) |
maxAttempts | integer | ❌ | 3 | Max retry attempts (1-10) |
scheduledStartTime | string | ❌ | null | Schedule start (ISO 8601 format) |
timezone | string | ❌ | "UTC" | IANA timezone for scheduled time |
Contact Object
| Field | Type | Required | Description |
|---|---|---|---|
phone | string | ✅ | Phone number in E.164 format |
name | string | ❌ | Contact name |
* | any | ❌ | Additional custom fields (stored as-is) |
CSV Format
Your CSV file must have aphone column. Additional columns are stored as custom fields.
phone,name,customerName,renewalDate
+12025550101,John Smith,John Smith,Feb 1
+12025550102,Jane Doe,Jane Doe,Feb 5
+12025550103,Bob Johnson,Bob Johnson,Feb 10
| Column | Required | Description |
|---|---|---|
phone | ✅ | Phone number in E.164 format (+12025550101) |
name | ❌ | Contact name |
| any other | ❌ | Additional columns stored as custom fields |
Phone Number Format: All phone numbers must be in E.164 format (e.g.,
+12025550101, +442071838750). Numbers without + or with formatting characters will be rejected.Response
{
"success": true,
"batchJobId": "batch_xyz789",
"totalContacts": 2,
"estimatedDuration": "2 minutes",
"estimatedCost": "$0.50",
"status": "scheduled",
"scheduledStartTime": "2025-02-28T14:00:00Z",
"message": "Batch job created with 2 contacts"
}
Response Fields
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the job was created successfully |
batchJobId | string | Unique batch job identifier |
totalContacts | integer | Number of valid contacts |
estimatedDuration | string | Human-readable duration estimate |
estimatedCost | string | Estimated cost in USD |
status | string | Initial status (scheduled or pending) |
scheduledStartTime | string | Scheduled start time (ISO 8601, UTC) |
message | string | Success message |
Status Values
| Status | Description |
|---|---|
pending | Job created, will start immediately |
scheduled | Job created, waiting for scheduled start time |
running | Job is actively processing contacts |
completed | All contacts processed |
failed | Job failed due to errors |
cancelled | Job was cancelled manually |
Examples
Example 1: Immediate Start (CSV)
curl -X POST "https://api.talkifai.com/batch/create" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@contacts.csv" \
-F "fromPhoneNumberId=phone_abc123" \
-F "name=Payment Reminders" \
-F "concurrentCallLimit=5" \
-F "maxAttempts=3"
Example 2: Scheduled Start with Timezone
curl -X POST "https://api.talkifai.com/batch/create" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fromPhoneNumberId": "phone_abc123",
"name": "Morning Follow-ups",
"contacts": [
{"phone": "+12025550101", "name": "John Smith"},
{"phone": "+12025550102", "name": "Jane Doe"}
],
"scheduledStartTime": "2025-03-01T09:00:00",
"timezone": "America/New_York",
"concurrentCallLimit": 3,
"maxAttempts": 2
}'
Example 3: Custom Fields for Personalization
curl -X POST "https://api.talkifai.com/batch/create" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fromPhoneNumberId": "phone_abc123",
"name": "Renewal Campaign",
"contacts": [
{
"phone": "+12025550101",
"name": "John Smith",
"customerName": "John Smith",
"renewalDate": "Feb 1",
"planType": "Premium"
}
],
"concurrentCallLimit": 5
}'
You are calling {{customerName}} about their {{planType}} plan renewal on {{renewalDate}}.
Greet them by name and confirm the renewal date.
Cost Estimation
The system estimates cost based on:- Number of contacts
- Agent architecture (pipeline vs realtime)
- Average call duration (assumed 2 minutes)
- Pipeline agent: ~3.50(0.035/call)
- Realtime agent: ~7.00(0.07/call)
Actual costs may vary based on call duration. The estimate assumes 2-minute average calls.
Error Handling
400 Bad Request
{
"detail": "CSV must have 'phone' column. Found columns: ['name', 'email']"
}
| Error Code | Cause |
|---|---|
invalid_csv | CSV file missing or invalid format |
missing_phone_column | CSV missing required phone column |
no_valid_contacts | CSV contains no valid phone numbers |
too_many_contacts | Exceeds 10,000 contact limit |
invalid_timezone | Invalid IANA timezone |
past_scheduled_time | Scheduled time is in the past |
invalid_phone_format | Phone number not in E.164 format |
insufficient_credits | Organization has insufficient credits |
no_outbound_agent | Phone number has no outbound agent assigned |
404 Not Found
{
"detail": "Phone number phone_abc123 not found"
}
500 Internal Server Error
{
"detail": "Failed to process CSV file: Database connection error"
}
How It Works
1. Upload CSV / Send JSON
│
▼
2. Validate phone numbers (E.164 format)
│
▼
3. Look up fromPhoneNumberId
├── Not found → 404 Error
└── Found → Get org + agent
│
▼
4. Check agent architecture
├── Text agent → 400 Error
└── Voice agent → Continue
│
▼
5. Validate contacts (remove duplicates)
│
▼
6. Check organization credits
├── Insufficient → 400 Error
└── Sufficient → Continue
│
▼
7. Create batch job record
│
├── Scheduled? → Store in Redis defer queue
└── Immediate? → Enqueue contacts to worker queue
│
▼
8. Return batch job ID
Related
- Batch Jobs List — List all batch jobs
- Batch Job Detail — Get detailed job status
- Cancel Batch Job — Cancel a running job
- Batch Calling Guide — Complete guide to batch campaigns
- Telephony — Configure phone numbers
Authorizations
Your TalkifAI API key. Get it from Studio → Settings → API Keys.
Body
multipart/form-dataapplication/json
CSV file with contacts (must have 'phone' column)
Phone number ID to call from
Batch job name
Optional description
Required range:
1 <= x <= 50Required range:
1 <= x <= 10Schedule start time (ISO 8601)
IANA timezone for scheduled time
⌘I