Skip to content

Campaigns

Manage AutoCall campaigns — CRUD + lifecycle controls (launch / pause / resume / archive / unarchive) + statistics.

Create a campaign

http
POST /api/autocall/campaigns

Body:

json
{
  "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:

FieldRequiredTypeDescription
nameYesstring, max:255Campaign name
descriptionNostring, max:2000Description
script_idYesintegerTTS script ID (see Scripts). Must exist AND be status='active'. Passing a draft/archived script returns 422.
caller_id_group_idNointegerCaller-ID pool ID. Leave empty → engine falls back to the account's default trunk. Must exist + status='active'.
max_retriesNointeger (0-10, default 3)Maximum retries when an attempt fails
retry_interval_minutesNointeger (1-43200, default 60)Delay between attempts (minutes)
retry_only_whenNoenum (no_answer/busy/failed/any, default no_answer)Only retry when attempt result matches
max_concurrentNointeger (1-200, default 10)Maximum concurrent calls
originate_timeoutNointeger (10-180, default 40)Timeout waiting for B-leg to answer (seconds)
write_to_main_cdrNoboolean (default false)Whether to write into the main system CDR. False = log internally to AutoCall only.
start_atNodatetime UTCTime at which dialing is allowed to begin. Leave empty = launch immediately
end_atNodatetime UTCStop time (must be > start_at)

Response 201 (full field set returned):

json
{
  "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):

json
{
  "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."]
  }
}

Lifecycle controls

EndpointActionFrom → To
POST /api/autocall/campaigns/{id}/launchActivatedraft/pausedactive
POST /api/autocall/campaigns/{id}/pausePauseactivepaused
POST /api/autocall/campaigns/{id}/resumeResumepausedactive (alias of launch)
POST /api/autocall/campaigns/{id}/archiveArchivedraft/paused/donearchived (campaign must NOT be active)
POST /api/autocall/campaigns/{id}/unarchiveRestorearchivedpaused
PUT /api/autocall/campaigns/{id}Update metadatakeeps current status
DELETE /api/autocall/campaigns/{id}Delete PERMANENTLYrequires archived first (hard delete cascade: campaign + leads + attempts + dtmf_events)

Mandatory rules

  • You must pause before archive (an active campaign cannot be archived).
  • You must archive before delete (permanent deletion → all report data is lost).

Response 200 (applies to every launch/pause/resume/archive/unarchive action):

json
{
  "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):

FieldTypeDescription
gateway_namestring | nullOverride of the dial gateway (default null = default routing)
caller_idstring | nullOverride of a single caller ID (default null = use the pool from caller_id_group_id)
total_leadsintegerTotal leads imported into the campaign
total_callsintegerTotal calls dialed
total_connectedintegerTotal successfully connected calls
total_failedintegerTotal failed calls
deleted_attimestamp | nullSoft 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):

json
{ "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}):

json
{ "data": { "deleted": true } }

List / show details / stats

GET /api/autocall/campaigns?status=active

Query params:

FieldTypeRequiredDescriptionValid values
statusstringNoFilter by campaign statusdraft / active / paused / done / archived
archivedintegerNoFetch archived campaigns. By default archived ones are HIDDEN1 = archived only; empty = exclude archived
qstringNoFuzzy search by campaign name (LIKE %keyword%)Any text string

Response 200:

json
{
  "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:

FieldTypeDescriptionValid values
data[].idintegerCampaign IDPositive integer
data[].namestringCampaign nameUp to 255 characters
data[].statusstringCurrent lifecycle statusdraft / active / paused / done / archived
data[].scriptobjectAttached TTS script (eager-loaded)Object with id, name, voice_id
data[].script.idintegerScript IDPositive integer
data[].script.namestringScript name
data[].script.voice_idstringTTS voice ID used by the script
data[].total_leadsintegerTotal leads imported into the campaign>= 0
data[].total_callsintegerTotal calls dialed>= 0
data[].total_connectedintegerTotal connected calls (B-leg answered)>= 0
data[].total_failedintegerTotal failed calls>= 0
data[].created_atdatetime ISO 8601Campaign creation time (UTC)

GET /api/autocall/campaigns/{id}

Returns the full details including script.dtmfActions and agents.

Path params:

FieldTypeRequiredDescriptionValid values
idintegerYesCampaign IDPositive integer existing in the account

Response 200:

json
{
  "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:

FieldTypeDescriptionValid values
data.idintegerCampaign IDPositive integer
data.namestringCampaign name
data.descriptionstring | nullDescription
data.statusstringLifecycle statusdraft / active / paused / done / archived
data.script_idintegerID of the TTS script in use
data.caller_id_group_idinteger | nullCaller-ID rotation pool ID
data.max_retriesintegerMaximum retries when an attempt fails0-10
data.retry_interval_minutesintegerDelay between attempts (minutes)1-43200
data.retry_only_whenstringOnly retry when the result matchesno_answer / busy / failed / any
data.max_concurrentintegerMaximum concurrent calls1-200
data.originate_timeoutintegerTimeout waiting for B-leg to answer (seconds)10-180
data.write_to_main_cdrbooleanWhether to record into the main system CDRtrue / false
data.start_atdatetime ISO 8601 | nullEarliest time dialing may begin (UTC)
data.end_atdatetime ISO 8601 | nullTime dialing must stop (UTC)
data.total_leadsintegerTotal leads imported>= 0
data.total_callsintegerTotal calls dialed>= 0
data.total_connectedintegerTotal connected calls>= 0
data.total_failedintegerTotal failed calls>= 0
data.tenant_idintegerID of the owning account
data.created_byintegerUser ID of the creator
data.created_atdatetime ISO 8601Creation time (UTC)
data.updated_atdatetime ISO 8601Last update time (UTC)
data.scriptobjectAttached TTS script + DTMF keypress list
data.script.dtmf_actions[]arrayDTMF keypress configuration of the script (see Scripts)
data.agents[]arrayAgents specifically assigned to the campaign (empty if none)

Response 404 (campaign does not exist or belongs to a different account):

json
{ "message": "Campaign not found" }

GET /api/autocall/campaigns/{id}/stats

Path params:

FieldTypeRequiredDescriptionValid values
idintegerYesCampaign IDPositive integer

Response 200:

json
{
  "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:

FieldTypeDescriptionValid values
data.total_leadsintegerTotal leads imported into the campaign>= 0
data.total_callsintegerTotal calls dialed>= 0
data.total_connectedintegerTotal connected calls (B-leg answered)>= 0
data.total_failedintegerTotal failed calls (sum of failed + no_answer + busy)>= 0
data.total_no_answerintegerTotal unanswered calls>= 0
data.total_busyintegerTotal busy calls>= 0
data.connection_ratenumberSuccessful 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:

FieldTypeRequiredDescriptionValid values
idintegerYesCampaign IDPositive integer

Query params:

FieldTypeRequiredDescriptionValid values
pageintegerNoPage number (1-indexed)>= 1, default 1
per_pageintegerNoAttempts per page1-200, default 50

Response 200:

json
{
  "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:

FieldTypeDescriptionValid values
data[].idintegerAttempt IDPositive integer
data[].uuidstringCall UUID (used for hangup or lookup)UUID v4
data[].campaign_idintegerCampaign ID
data[].lead_idintegerLead ID
data[].lead_phone_numberstringLead phone number (E.164 84xxx)
data[].attempt_numberintegerWhich attempt this is1-N
data[].started_atdatetime ISO 8601Originate start time (UTC)
data[].answered_atdatetime ISO 8601 | nullB-leg answer time (UTC)null if not answered
data[].ended_atdatetime ISO 8601Call end time (UTC)
data[].duration_secintegerTotal call duration (seconds)>= 0
data[].resultstringCall outcomeconnected / no_answer / busy / failed / abandoned
data[].hangup_causestringHangup code from Zorio PBXNORMAL_CLEARING / NO_ANSWER / USER_BUSY / ...
data[].dtmf_pressedstring | nullDTMF key pressed by the called party (if any)0-9, *, #, null
data[].final_actionstring | nullFinal action executedqueue:<name> / playback:<url> / hangup / switch:<id>
current_pageintegerCurrent page>= 1
last_pageintegerLast page>= 1
per_pageintegerItems per page1-200
totalintegerTotal attempts matching the filter>= 0

Caller-ID Groups (read-only picker)

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:

json
{
  "data": [
    {
      "id": 3,
      "name": "Pool VNPT 028",
      "label": "Pool VNPT 028",
      "description": "5 numbers in 028xxxx, Round Robin rotation"
    }
  ]
}

Response fields:

FieldTypeDescriptionValid values
data[].idintegerPool ID — used as caller_id_group_id when calling POST /api/autocall/campaignsPositive integer
data[].namestringPool name (technical identifier)Up to 255 characters
data[].labelstringFriendly display name for the UIUp to 255 characters
data[].descriptionstring | nullShort description of the pool (rotation strategy, list of numbers...)

Hangup an in-progress call

Hangup an in-progress call from an AutoCall campaign — AutoCall and Telesales share the same hangup endpoint.

http
POST /api/telesales/calls/{uuid}/hangup

Path params:

FieldTypeRequiredDescriptionValid values
uuidstringYesCall UUID (from the call.answered webhook event or the uuid field of an attempt)UUID v4 belonging to the current account

Sample request:

bash
curl -X POST https://app.zorio.vn/api/telesales/calls/abc-def-123/hangup \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"

Response 200:

json
{ "success": true }

Response fields:

FieldTypeDescriptionValid values
successbooleanHangup command successfully sent to Zorio PBXtrue

Error 404 (uuid does not exist or does not belong to the account):

json
{ "error": "Call not found" }

Permission

Requires manage_telesales_campaigns or the admin role. The call ends with hangup cause NORMAL_CLEARING.

Cấp phép theo điều khoản sử dụng của Zorio.