English
English
Appearance
English
English
Appearance
Manage AutoCall campaigns — CRUD + lifecycle controls (launch / pause / resume / archive / unarchive) + statistics.
POST /api/autocall/campaignsBody:
{
"name": "Customer care Q3-2026",
"description": "Reactivation outreach for customers inactive 30-60 days",
"script_id": 12,
"caller_id_group_id": 3,
"max_retries": 3,
"retry_interval_minutes": 60,
"retry_only_when": "no_answer",
"max_concurrent": 10,
"originate_timeout": 40,
"write_to_main_cdr": false,
"start_at": "2026-06-25T08:00:00Z",
"end_at": "2026-06-30T17:30:00Z"
}Fields:
| Field | Required | Type | Description |
|---|---|---|---|
name | Yes | string, max:255 | Campaign name |
description | No | string, max:2000 | Description |
script_id | Yes | integer | TTS script ID (see Scripts). Must exist AND be status='active'. Passing a draft/archived script returns 422. |
caller_id_group_id | No | integer | Caller-ID pool ID. Leave empty → engine falls back to the account's default trunk. Must exist + status='active'. |
max_retries | No | integer (0-10, default 3) | Maximum retries when an attempt fails |
retry_interval_minutes | No | integer (1-43200, default 60) | Delay between attempts (minutes) |
retry_only_when | No | enum (no_answer/busy/failed/any, default no_answer) | Only retry when attempt result matches |
max_concurrent | No | integer (1-200, default 10) | Maximum concurrent calls |
originate_timeout | No | integer (10-180, default 40) | Timeout waiting for B-leg to answer (seconds) |
write_to_main_cdr | No | boolean (default false) | Whether to write into the main system CDR. False = log internally to AutoCall only. |
start_at | No | datetime UTC | Time at which dialing is allowed to begin. Leave empty = launch immediately |
end_at | No | datetime UTC | Stop time (must be > start_at) |
Response 201 (full field set returned):
{
"data": {
"id": 19,
"tenant_id": 1,
"name": "Customer care Q3-2026",
"description": "Reactivation outreach for customers inactive 30-60 days",
"script_id": 12,
"status": "draft",
"gateway_name": null,
"caller_id": null,
"caller_id_group_id": 8,
"max_retries": 3,
"retry_interval_minutes": 60,
"retry_only_when": "no_answer",
"write_to_main_cdr": false,
"max_concurrent": 10,
"originate_timeout": 40,
"start_at": "2026-06-25T08:00:00.000000Z",
"end_at": "2026-06-30T17:30:00.000000Z",
"total_leads": 0,
"total_calls": 0,
"total_connected": 0,
"total_failed": 0,
"created_by": 3,
"created_at": "2026-06-25T09:53:47.000000Z",
"updated_at": "2026-06-25T09:53:47.000000Z",
"deleted_at": null
}
}Note
The response includes every configuration field of the campaign. Stats fields (total_*) are 0 at creation time (no lead/call activity yet). Fields gateway_name and caller_id (single-number override) are null when the pool referenced by caller_id_group_id is used. deleted_at is null until the campaign is archived (soft delete).
Response 422 (validation):
{
"message": "The given data was invalid.",
"errors": {
"script_id": ["The selected script id is invalid."],
"retry_only_when": ["The selected retry only when is invalid."]
}
}| Endpoint | Action | From → To |
|---|---|---|
POST /api/autocall/campaigns/{id}/launch | Activate | draft/paused → active |
POST /api/autocall/campaigns/{id}/pause | Pause | active → paused |
POST /api/autocall/campaigns/{id}/resume | Resume | paused → active (alias of launch) |
POST /api/autocall/campaigns/{id}/archive | Archive | draft/paused/done → archived (campaign must NOT be active) |
POST /api/autocall/campaigns/{id}/unarchive | Restore | archived → paused |
PUT /api/autocall/campaigns/{id} | Update metadata | keeps current status |
DELETE /api/autocall/campaigns/{id} | Delete PERMANENTLY | requires archived first (hard delete cascade: campaign + leads + attempts + dtmf_events) |
Mandatory rules
pause before archive (an active campaign cannot be archived).archive before delete (permanent deletion → all report data is lost).Response 200 (applies to every launch/pause/resume/archive/unarchive action):
{
"data": {
"id": 21,
"tenant_id": 1,
"name": "Customer care Q3-2026 2",
"description": "Reactivation outreach for customers inactive 30-60 days",
"script_id": 12,
"status": "active",
"gateway_name": null,
"caller_id": null,
"caller_id_group_id": 8,
"max_retries": 3,
"retry_interval_minutes": 60,
"retry_only_when": "no_answer",
"write_to_main_cdr": false,
"max_concurrent": 10,
"originate_timeout": 40,
"start_at": "2026-06-25T08:00:00.000000Z",
"end_at": "2026-06-30T17:30:00.000000Z",
"total_leads": 0,
"total_calls": 0,
"total_connected": 0,
"total_failed": 0,
"created_by": 3,
"created_at": "2026-06-25T14:26:16.000000Z",
"updated_at": "2026-06-25T14:27:41.000000Z",
"deleted_at": null
}
}Extra fields (not present in the POST body):
| Field | Type | Description |
|---|---|---|
gateway_name | string | null | Override of the dial gateway (default null = default routing) |
caller_id | string | null | Override of a single caller ID (default null = use the pool from caller_id_group_id) |
total_leads | integer | Total leads imported into the campaign |
total_calls | integer | Total calls dialed |
total_connected | integer | Total successfully connected calls |
total_failed | integer | Total failed calls |
deleted_at | timestamp | null | Soft delete timestamp (null while not archived) |
Timestamps format
ISO 8601 with microseconds .000000Z. Client parsers must support this format (e.g. new Date("2026-06-25T14:26:16.000000Z") in JavaScript works correctly).
Response 422 (state machine failure):
{ "message": "Only draft/paused campaigns can be launched" }
{ "message": "Only active campaigns can be paused" }
{ "message": "Campaign must be paused before archiving" }
{ "message": "Only archived campaigns can be restored" }
{ "message": "Campaign must be archived before permanent deletion (Archive → Delete)" }Response 200 (PUT /api/autocall/campaigns/{id} — update metadata): returns the updated campaign object, same shape as Response 201.
Response 200 (DELETE /api/autocall/campaigns/{id}):
{ "data": { "deleted": true } }GET /api/autocall/campaigns?status=active Query params:
| Field | Type | Required | Description | Valid values |
|---|---|---|---|---|
status | string | No | Filter by campaign status | draft / active / paused / done / archived |
archived | integer | No | Fetch archived campaigns. By default archived ones are HIDDEN | 1 = archived only; empty = exclude archived |
q | string | No | Fuzzy search by campaign name (LIKE %keyword%) | Any text string |
Response 200:
{
"data": [
{
"id": 36,
"name": "Customer care Q3-2026",
"status": "active",
"script": { "id": 12, "name": "Standard care", "voice_id": "evln-vi-nature" },
"total_leads": 487,
"total_calls": 312,
"total_connected": 198,
"total_failed": 89,
"created_at": "2026-06-24T07:30:00Z"
}
]
}Response fields:
| Field | Type | Description | Valid values |
|---|---|---|---|
data[].id | integer | Campaign ID | Positive integer |
data[].name | string | Campaign name | Up to 255 characters |
data[].status | string | Current lifecycle status | draft / active / paused / done / archived |
data[].script | object | Attached TTS script (eager-loaded) | Object with id, name, voice_id |
data[].script.id | integer | Script ID | Positive integer |
data[].script.name | string | Script name | — |
data[].script.voice_id | string | TTS voice ID used by the script | — |
data[].total_leads | integer | Total leads imported into the campaign | >= 0 |
data[].total_calls | integer | Total calls dialed | >= 0 |
data[].total_connected | integer | Total connected calls (B-leg answered) | >= 0 |
data[].total_failed | integer | Total failed calls | >= 0 |
data[].created_at | datetime ISO 8601 | Campaign creation time (UTC) | — |
GET /api/autocall/campaigns/{id} Returns the full details including script.dtmfActions and agents.
Path params:
| Field | Type | Required | Description | Valid values |
|---|---|---|---|---|
id | integer | Yes | Campaign ID | Positive integer existing in the account |
Response 200:
{
"data": {
"id": 36,
"name": "Customer care Q3-2026",
"description": "Reactivation outreach for customers inactive 30-60 days",
"status": "active",
"script_id": 12,
"caller_id_group_id": 3,
"max_retries": 3,
"retry_interval_minutes": 60,
"retry_only_when": "no_answer",
"max_concurrent": 10,
"originate_timeout": 40,
"write_to_main_cdr": false,
"start_at": "2026-06-25T08:00:00Z",
"end_at": "2026-06-30T17:30:00Z",
"total_leads": 487,
"total_calls": 312,
"total_connected": 198,
"total_failed": 89,
"tenant_id": 1,
"created_by": 7,
"created_at": "2026-06-24T07:30:00Z",
"updated_at": "2026-06-25T02:34:00Z",
"script": {
"id": 12,
"name": "Standard care",
"voice_id": "evln-vi-nature",
"dtmf_actions": [
{ "id": 45, "digit": "1", "label": "Confirm", "action_type": "playback_then_hangup", "target_audio_url": "/media/thanks.wav" },
{ "id": 46, "digit": "2", "label": "Transfer to agent", "action_type": "queue", "target_queue_name": "support_queue" }
]
},
"agents": []
}
}Response fields:
| Field | Type | Description | Valid values |
|---|---|---|---|
data.id | integer | Campaign ID | Positive integer |
data.name | string | Campaign name | — |
data.description | string | null | Description | — |
data.status | string | Lifecycle status | draft / active / paused / done / archived |
data.script_id | integer | ID of the TTS script in use | — |
data.caller_id_group_id | integer | null | Caller-ID rotation pool ID | — |
data.max_retries | integer | Maximum retries when an attempt fails | 0-10 |
data.retry_interval_minutes | integer | Delay between attempts (minutes) | 1-43200 |
data.retry_only_when | string | Only retry when the result matches | no_answer / busy / failed / any |
data.max_concurrent | integer | Maximum concurrent calls | 1-200 |
data.originate_timeout | integer | Timeout waiting for B-leg to answer (seconds) | 10-180 |
data.write_to_main_cdr | boolean | Whether to record into the main system CDR | true / false |
data.start_at | datetime ISO 8601 | null | Earliest time dialing may begin (UTC) | — |
data.end_at | datetime ISO 8601 | null | Time dialing must stop (UTC) | — |
data.total_leads | integer | Total leads imported | >= 0 |
data.total_calls | integer | Total calls dialed | >= 0 |
data.total_connected | integer | Total connected calls | >= 0 |
data.total_failed | integer | Total failed calls | >= 0 |
data.tenant_id | integer | ID of the owning account | — |
data.created_by | integer | User ID of the creator | — |
data.created_at | datetime ISO 8601 | Creation time (UTC) | — |
data.updated_at | datetime ISO 8601 | Last update time (UTC) | — |
data.script | object | Attached TTS script + DTMF keypress list | — |
data.script.dtmf_actions[] | array | DTMF keypress configuration of the script (see Scripts) | — |
data.agents[] | array | Agents specifically assigned to the campaign (empty if none) | — |
Response 404 (campaign does not exist or belongs to a different account):
{ "message": "Campaign not found" }GET /api/autocall/campaigns/{id}/stats Path params:
| Field | Type | Required | Description | Valid values |
|---|---|---|---|---|
id | integer | Yes | Campaign ID | Positive integer |
Response 200:
{
"data": {
"total_leads": 487,
"total_calls": 312,
"total_connected": 198,
"total_failed": 89,
"total_no_answer": 65,
"total_busy": 18,
"connection_rate": 63.5
}
}Response fields:
| Field | Type | Description | Valid values |
|---|---|---|---|
data.total_leads | integer | Total leads imported into the campaign | >= 0 |
data.total_calls | integer | Total calls dialed | >= 0 |
data.total_connected | integer | Total connected calls (B-leg answered) | >= 0 |
data.total_failed | integer | Total failed calls (sum of failed + no_answer + busy) | >= 0 |
data.total_no_answer | integer | Total unanswered calls | >= 0 |
data.total_busy | integer | Total busy calls | >= 0 |
data.connection_rate | number | Successful connection rate (total_connected / total_calls * 100) | 0-100, rounded to 1 decimal |
GET /api/autocall/campaigns/{id}/attempts?per_page=50 List of calls for a campaign (standard pagination via page/per_page, default 50, max 200).
Path params:
| Field | Type | Required | Description | Valid values |
|---|---|---|---|---|
id | integer | Yes | Campaign ID | Positive integer |
Query params:
| Field | Type | Required | Description | Valid values |
|---|---|---|---|---|
page | integer | No | Page number (1-indexed) | >= 1, default 1 |
per_page | integer | No | Attempts per page | 1-200, default 50 |
Response 200:
{
"data": [
{
"id": 8721,
"uuid": "093e1024-1234-5678-9abc-def012345678",
"campaign_id": 36,
"lead_id": 78912,
"lead_phone_number": "0912345678",
"attempt_number": 1,
"started_at": "2026-06-24T08:32:00Z",
"answered_at": "2026-06-24T08:32:05Z",
"ended_at": "2026-06-24T08:32:47Z",
"duration_sec": 42,
"result": "connected",
"hangup_cause": "NORMAL_CLEARING",
"dtmf_pressed": "2",
"final_action": "queue:support_queue"
}
],
"current_page": 1,
"last_page": 7,
"per_page": 50,
"total": 312
}Response fields:
| Field | Type | Description | Valid values |
|---|---|---|---|
data[].id | integer | Attempt ID | Positive integer |
data[].uuid | string | Call UUID (used for hangup or lookup) | UUID v4 |
data[].campaign_id | integer | Campaign ID | — |
data[].lead_id | integer | Lead ID | — |
data[].lead_phone_number | string | Lead phone number (E.164 84xxx) | — |
data[].attempt_number | integer | Which attempt this is | 1-N |
data[].started_at | datetime ISO 8601 | Originate start time (UTC) | — |
data[].answered_at | datetime ISO 8601 | null | B-leg answer time (UTC) | null if not answered |
data[].ended_at | datetime ISO 8601 | Call end time (UTC) | — |
data[].duration_sec | integer | Total call duration (seconds) | >= 0 |
data[].result | string | Call outcome | connected / no_answer / busy / failed / abandoned |
data[].hangup_cause | string | Hangup code from Zorio PBX | NORMAL_CLEARING / NO_ANSWER / USER_BUSY / ... |
data[].dtmf_pressed | string | null | DTMF key pressed by the called party (if any) | 0-9, *, #, null |
data[].final_action | string | null | Final action executed | queue:<name> / playback:<url> / hangup / switch:<id> |
current_page | integer | Current page | >= 1 |
last_page | integer | Last page | >= 1 |
per_page | integer | Items per page | 1-200 |
total | integer | Total attempts matching the filter | >= 0 |
GET /api/autocall/caller-id-pools Returns the list of caller-ID pools so you can pick a caller_id_group_id when creating a campaign.
Response 200:
{
"data": [
{
"id": 3,
"name": "Pool VNPT 028",
"label": "Pool VNPT 028",
"description": "5 numbers in 028xxxx, Round Robin rotation"
}
]
}Response fields:
| Field | Type | Description | Valid values |
|---|---|---|---|
data[].id | integer | Pool ID — used as caller_id_group_id when calling POST /api/autocall/campaigns | Positive integer |
data[].name | string | Pool name (technical identifier) | Up to 255 characters |
data[].label | string | Friendly display name for the UI | Up to 255 characters |
data[].description | string | null | Short description of the pool (rotation strategy, list of numbers...) | — |
Hangup an in-progress call from an AutoCall campaign — AutoCall and Telesales share the same hangup endpoint.
POST /api/telesales/calls/{uuid}/hangupPath params:
| Field | Type | Required | Description | Valid values |
|---|---|---|---|---|
uuid | string | Yes | Call UUID (from the call.answered webhook event or the uuid field of an attempt) | UUID v4 belonging to the current account |
Sample request:
curl -X POST https://app.zorio.vn/api/telesales/calls/abc-def-123/hangup \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"Response 200:
{ "success": true }Response fields:
| Field | Type | Description | Valid values |
|---|---|---|---|
success | boolean | Hangup command successfully sent to Zorio PBX | true |
Error 404 (uuid does not exist or does not belong to the account):
{ "error": "Call not found" }Permission
Requires manage_telesales_campaigns or the admin role. The call ends with hangup cause NORMAL_CLEARING.