Guides
Batches
Call a list of contacts from a CSV file, on a schedule, with skipped rows explained.
A batch is a CSV of contacts plus an agent. You upload it, schedule it, and the platform creates one call per row. Every call in a batch is a normal execution: it gets its own transcript, cost and webhooks.
In the dashboard: Deploy → Batches.
The CSV
contact_number,name,due_date,consent
+919876500123,Asha,20 October,yes
+919876500124,Ravi,22 October,yes
+919876500125,Meera,25 October,- A
contact_numbercolumn is required. Without it:The CSV must have a contact_number column. - Numbers must be E.164. Spaces and hyphens inside a number are removed first. A row whose number is still invalid is dropped and counted only in
total_contacts. - Every other column becomes a variable for that call, exactly like
user_datain Outbound calling. consentis a special column when your account requires consent; see Compliance. Values read as consent:1,true,yes,y,consent,given,haan,हाँ.- The file must be text. A spreadsheet or PDF renamed to
.csvis refused.
Create a batch
curl -X POST https://api.sevennadi.si/batches \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
-F "agent_id=3c90c3cc-0d44-4b50-8822-8dd25736052a" \
-F "file=@contacts.csv" \
-F "from_phone_numbers=+918012345678" \
-F 'retry_config={"enabled": true, "max_retries": 2}' \
-F "webhook_url=https://hooks.example.test/batch"Response, 201:
{
"batch_id": "5f0c2d7e9a1b4c3d8e6f7a8b9c0d1e2f",
"state": "created",
"valid_contacts": 3,
"total_contacts": 3,
"skipped_contacts": 1,
"skipped_rows": [
{"contact_number": "+919876500125", "reason": "no consent: the row has no consent and the account requires it"}
]
}| Form field | Meaning |
|---|---|
agent_id | Required |
file | Required. The CSV |
from_phone_numbers | Caller IDs. Repeat the field, or send a JSON array or a comma-separated list. Rows rotate through them in order |
retry_config | A JSON object, as for single calls. max_retries must be 1 to 3 |
webhook_url | Where this batch's call webhooks go, instead of the agent's URL |
skipped_rows at this point is a preview: rows on your do-not-call list, rows without consent when it is required, and numbers on the do-not-disturb registry when dnd_check is block. Nothing has been called yet.
Schedule it
A batch does nothing until it is scheduled.
curl -X POST https://api.sevennadi.si/batches/5f0c2d7e9a1b4c3d8e6f7a8b9c0d1e2f/schedule \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
-F "scheduled_at=2026-10-14T10:30:00+05:30" \
-F "bypass_call_guardrails=false"{"message": "success", "state": "scheduled"}The time needs a UTC offset and must be at least two minutes ahead. Start times are rounded up to the next ten minutes. You can schedule again to move a batch that is still created or scheduled; later it answers 400 with Batch is already running (or its status).
What happens at the start time
For each row the compliance rules are applied again, now including the per-number daily cap:
- A row that fails is not called. It is added to the batch's
skipped_rowswith the reason. - A row outside the agent's calling hours, in that recipient's timezone, becomes a
rescheduledcall for the next allowed start. - Every other row becomes a
queuedcall.
All calls are queued together. Your account's concurrency limit decides how many run at once; the rest wait their turn.
Follow a batch
curl https://api.sevennadi.si/batches/5f0c2d7e9a1b4c3d8e6f7a8b9c0d1e2f \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"{
"batch_id": "5f0c2d7e9a1b4c3d8e6f7a8b9c0d1e2f",
"status": "running",
"scheduled_at": "2026-10-14T05:00:00Z",
"file_name": "contacts.csv",
"valid_contacts": 3,
"total_contacts": 3,
"from_phone_number": "+918012345678",
"from_phone_numbers": ["+918012345678"],
"execution_status": {"completed": 1, "in-progress": 1},
"skipped_contacts": 1,
"skipped_rows": [{"contact_number": "+919876500125", "reason": "no consent: the row has no consent and the account requires it"}],
"created_at": "2026-10-13T09:40:00Z",
"updated_at": "2026-10-13T09:41:10Z",
"humanized_created_at": "1 minute ago"
}| Batch status | Meaning |
|---|---|
created | Uploaded, not scheduled |
scheduled | Waiting for its start time |
running | Calls are queued or in progress |
completed | Every call reached a final status. A call waiting for an automatic retry is not final |
stopped | You stopped it |
failed | The agent was deleted before the start time |
execution_status counts the batch's calls by call status. GET /batches/{agent_id}/all lists an agent's batches.
Executions
GET /v2/batches/{batch_id}/executions returns the calls, paged (page_number from 1, page_size 1 to 50, default 20):
{"page_number": 1, "page_size": 20, "total": 2, "has_more": false, "data": [{"id": "...", "status": "completed", "batch_id": "5f0c2d7e9a1b4c3d8e6f7a8b9c0d1e2f"}]}Each execution of a batch also carries batch_run_details with its status and how often it was retried. GET /batches/{batch_id}/executions returns the same calls as one unpaged list.
Stop or delete
POST /batches/{batch_id}/stop: calls not yet dialled becomestopped; live calls are hung up. Answers{"message": "success", "state": "stopped"}. A batch that is not scheduled or running answers404withBatch is not queued or running.DELETE /batches/{batch_id}: stops it if active, then removes it from lists.
When the balance runs out
A batch is not burnt through at zero balance. It stays running, GET /batches/{batch_id} shows "paused_reason": "balance-low", and its remaining calls stay queued. After a top-up they are queued again automatically. See Billing.
Limitations
- Rows are stored with the batch; there is no endpoint to add or edit rows after upload. Create a new batch instead.
skipped_rowsin the create response is only a preview. The daily cap depends on the day the batch runs, so the final list is set at the start time.- If your account is restricted to verified numbers, a batch containing unverified numbers is refused with
403, listing up to five of them. - A stopped batch cannot be resumed.