Skip to content

Scripts + DTMF actions

Manage TTS scripts (template + voice + DTMF actions) for AutoCall. Includes script CRUD, the DTMF action types enum, a queue helper list, and TTS preview.

Create a script

http
POST /api/autocall/scripts

Body:

json
{
  "name": "Standard care",
  "description": "Template for the reactivation care campaign for customers inactive 30-60 days",
  "template_text": "Hello {{salutation_name}}, this is a reminder of your appointment on {{callback_date}}. Press 1 to confirm, press 2 to be transferred to an agent, press 9 to skip.",
  "voice_id": "voice_id_premium_02",
  "tts_tier": "B",
  "max_repeat_per_attempt": 1,
  "status": "draft",
  "dtmf_actions": [
    {
      "digit": "1",
      "label": "Confirm appointment",
      "action_type": "playback_then_hangup",
      "target_audio_url": "/media/thank_you.wav"
    },
    {
      "digit": "2",
      "label": "Transfer to agent",
      "action_type": "queue",
      "target_queue_name": "support@default"
    },
    {
      "digit": "9",
      "label": "Skip",
      "action_type": "hangup"
    }
  ]
}

Fields:

FieldRequiredTypeDescription
nameYesstring, max:255Script name
template_textYestextTemplate with placeholders
voice_idYesstringVoice ID — MUST come from GET /api/autocall/voices (Voices). The system automatically routes to the matching provider. A voice_id that does not exist in the available voice library returns 422.
tts_tierNoenum (A/B, default B)A = premium, rendered realtime by a premium TTS provider with your own key, billed per character. B = local unlimited, uses the pre-rendered Vietnamese voice library, no fee, high throughput. Recommended default B for high volume.
max_repeat_per_attemptNointeger (1-5, default 1)Times to replay the audio if the user does not press DTMF
statusNoenum (draft/active/archived, default draft)Only active scripts can be used by the engine for real campaign runs
dtmf_actions[]Noarray max:12Keypress configuration; each item has digit/label/action_type/target_*

The voice_provider field is NOT in the request body

You do NOT pass it. The system auto-routes by voice_id → the matching tier (A premium / B local). The server response still RETURNS this field — read-only, indicating which provider was used.

Error 422 — voice_id does not exist:

json
{
  "message": "The given data was invalid.",
  "errors": {
    "voice_id": [
      "voice_id 'voice_id_premium_01' does not exist in the available voice library. Fetch the valid voice list via GET /api/autocall/voices."
    ]
  }
}

DTMF action types

action_typeRequired companion fieldBehavior
playbacktarget_audio_urlPlay the file then wait for the next DTMF
playback_then_hanguptarget_audio_urlPlay the file then hang up
switch_scripttarget_script_idLoad a different script, replay from the beginning
queuetarget_queue_name (= queue name identifier)Transfer into a queue for a live agent to answer
hangupHang up immediately
repeat_scriptReplay the full current script audio

Fetch the enum dynamically via API

Instead of hardcoding the 6 action types client-side, you can fetch the full enum from GET /api/autocall/dtmf-action-types (see List action types). Similarly, the list of target_queue_name values comes from GET /api/autocall/queues (see List queues).

Reference-integrity validation (server-side)

  • playback / playback_then_hanguptarget_audio_url MUST match the file_path of a media file belonging to the account (from GET /api/media-files). Passing an external URL → 422.
  • switch_scripttarget_script_id MUST be an existing script in the account (from GET /api/autocall/scripts). Passing a bogus ID → 422.
  • queuetarget_queue_name MUST match the name of an active queue (from GET /api/autocall/queues). Mismatched → 422.

Error 422 sample:

json
{
  "message": "The given data was invalid.",
  "errors": {
    "dtmf_actions.0.target_audio_url": [
      "target_audio_url '/data/wrong/path.wav' does not exist in the customer's Media Files. Upload via POST /api/media-files first, then use the returned file_path."
    ]
  }
}

List action types for DTMF (enum + label)

http
GET /api/autocall/dtmf-action-types

Purpose: returns the 6 action types together with localized labels and the list of required companion fields. Integrators use this to populate the "Behavior on keypress" dropdown in the UI without hardcoding the enum.

Response 200:

json
{
  "data": [
    {
      "code": "playback",
      "label": "Play audio",
      "description": "Play the specified audio file, then continue the script.",
      "required_fields": ["target_audio_url"]
    },
    {
      "code": "playback_then_hangup",
      "label": "Play audio then hang up",
      "description": "Play the audio file and then hang up automatically.",
      "required_fields": ["target_audio_url"]
    },
    {
      "code": "switch_script",
      "label": "Switch to another script",
      "description": "Switch the call to a different AutoCall script (chained IVR).",
      "required_fields": ["target_script_id"]
    },
    {
      "code": "queue",
      "label": "Transfer to agent queue",
      "description": "Transfer the call into a queue for a live agent.",
      "required_fields": ["target_queue_name"]
    },
    {
      "code": "repeat_script",
      "label": "Repeat the script",
      "description": "Replay the current script from the beginning (so the called party can listen again).",
      "required_fields": []
    },
    {
      "code": "hangup",
      "label": "Hang up",
      "description": "End the call immediately.",
      "required_fields": []
    }
  ]
}

Response fields:

FieldTypeDescription
codestringEnum value to use for dtmf_actions[].action_type in POST/PUT /scripts
labelstringDisplay label for the dropdown UI
descriptionstringShort description shown to the end user
required_fieldsstring[]List of companion fields that must accompany this action (e.g. playback → requires target_audio_url)

List queues for action_type=queue

http
GET /api/autocall/queues

Purpose: returns the list of active queues so the client can use them as target_queue_name when picking action_type=queue. Populates the "Select queue" dropdown in the UI.

Response 200:

json
{
  "data": [
    {
      "id": 1,
      "name": "support_q1",
      "queue_number": "2001",
      "strategy": "ring_all"
    },
    {
      "id": 2,
      "name": "sales_outbound",
      "queue_number": "2002",
      "strategy": "longest_idle_agent"
    }
  ]
}

Response fields:

FieldTypeDescription
idintegerQueue DB ID (for reference)
namestringQueue technical name — USED as target_queue_name when POST/PUT a script with a DTMF queue action
queue_numberstringQueue extension number (e.g. 2001) — displayed to the end user
strategystringDistribution strategy: ring_all / longest_idle_agent / round_robin / ...

Important note

The target_queue_name field in dtmf_actions[] MUST match the queue's name (not queue_number or id). Mismatches cause dialing to fail because the system cannot resolve the queue.

Sample Response 201 after creating a script

json
{
  "data": {
    "name": "Standard care",
    "description": "Template for the reactivation care campaign for customers inactive 30-60 days",
    "template_text": "Hello {{salutation_name}}, this is a reminder of your appointment on {{appointment_date}}. Press 1 to confirm, press 2 to be transferred to an agent, press 9 to skip.",
    "voice_provider": "tts_provider_01",
    "voice_id": "evln-vi-nature",
    "tts_tier": "A",
    "max_repeat_per_attempt": 1,
    "status": "draft",
    "created_by": 3,
    "tenant_id": 1,
    "updated_at": "2026-06-25T04:37:49.000000Z",
    "created_at": "2026-06-25T04:37:49.000000Z",
    "id": 4,
    "dtmf_actions": [
      {
        "id": 1,
        "tenant_id": 1,
        "script_id": 4,
        "digit": "1",
        "label": "Confirm appointment",
        "action_type": "playback_then_hangup",
        "target_audio_url": "/media/thank_you.wav",
        "target_script_id": null,
        "target_queue_name": null,
        "sort_order": 0,
        "created_at": "2026-06-25T04:37:49.000000Z",
        "updated_at": "2026-06-25T04:37:49.000000Z"
      },
      {
        "id": 2,
        "tenant_id": 1,
        "script_id": 4,
        "digit": "2",
        "label": "Transfer to agent",
        "action_type": "queue",
        "target_audio_url": null,
        "target_script_id": null,
        "target_queue_name": "support@default",
        "sort_order": 1,
        "created_at": "2026-06-25T04:37:49.000000Z",
        "updated_at": "2026-06-25T04:37:49.000000Z"
      },
      {
        "id": 3,
        "tenant_id": 1,
        "script_id": 4,
        "digit": "9",
        "label": "Skip",
        "action_type": "hangup",
        "target_audio_url": null,
        "target_script_id": null,
        "target_queue_name": null,
        "sort_order": 2,
        "created_at": "2026-06-25T04:37:49.000000Z",
        "updated_at": "2026-06-25T04:37:49.000000Z"
      }
    ]
  }
}

Field order + tenant_id

The system serializes fields in insert/create order plus auto-injected ones (created_at/updated_at/id) at the end. The client MUST NOT rely on key order — parse by key name. tenant_id appears at both the script level and on each dtmf_actions[] item — redundant but correct, because both entities carry tenant_id.

List / show / update / delete

EndpointPurpose
GET /api/autocall/scripts?status=activeList scripts (optional filter)
GET /api/autocall/scripts/{id}Details including dtmfActions
PUT /api/autocall/scripts/{id}Update. If dtmf_actions is sent → entire list is replaced
DELETE /api/autocall/scripts/{id}Soft delete (returns 422 if still used by a campaign)

GET /api/autocall/scripts — list

Query params:

FieldTypeRequiredDescriptionValid values
statusstringNoFilter by script statusdraft / active / archived
json
{
  "data": [
    {
      "id": 2,
      "tenant_id": 1,
      "name": "Appointment reminder",
      "description": null,
      "template_text": "ABC Company kindly informs you of the service appointment on {{appointment_date}}. Please confirm before 6 PM on {{appointment_date}}. If you have already confirmed, please ignore this call. Press 3 to reach our hotline. Thank you.",
      "voice_provider": "tts_provider_01",
      "voice_id": "voice_id_premium_02",
      "tts_tier": "B",
      "audio_fixed_paths": null,
      "max_repeat_per_attempt": 1,
      "status": "active",
      "created_by": 1,
      "created_at": "2026-06-22T03:34:01.000000Z",
      "updated_at": "2026-06-22T08:11:49.000000Z",
      "deleted_at": null
    }
  ]
}

Response fields:

FieldTypeDescriptionValid values
data[].idintegerScript IDPositive integer
data[].tenant_idintegerID of the account that owns the script
data[].namestringScript nameUp to 255 characters
data[].descriptionstring | nullDescriptionUp to 2000 characters
data[].template_texttextTTS template with placeholders
data[].voice_providerstringTTS provider (read-only, derived from voice_id)tts_provider_01 / local / ...
data[].voice_idstringTTS voice IDFrom GET /api/autocall/voices
data[].tts_tierstringTTS tierA (premium realtime) / B (local pre-rendered)
data[].audio_fixed_pathsobject | nullCache of rendered fixed audio segments (variable-free template parts). Integrators do not need to handle this
data[].max_repeat_per_attemptintegerTimes to replay audio if the user does not press DTMF1-5
data[].statusstringScript statusdraft / active / archived
data[].created_byintegerUser ID of the creator
data[].created_atdatetime ISO 8601Creation time (UTC, microseconds)
data[].updated_atdatetime ISO 8601Last update time (UTC, microseconds)
data[].deleted_atdatetime ISO 8601 | nullSoft delete timestampnull until deleted

Timestamp format

The API returns ISO 8601 with microseconds YYYY-MM-DDTHH:mm:ss.uuuuuuZ (e.g. 2026-06-22T03:34:01.000000Z). Clients should parse standard ISO 8601 (microseconds optional).

GET /api/autocall/scripts/{id} — details

Full object including dtmf_actions[]:

json
{
  "data": {
    "id": 2,
    "tenant_id": 1,
    "name": "Appointment reminder",
    "description": null,
    "template_text": "ABC Company kindly informs you of the service appointment on {{appointment_date}}, with a total amount of {{total_amount}}. ... Thank you.",
    "voice_provider": "tts_provider_01",
    "voice_id": "voice_id_premium_02",
    "tts_tier": "B",
    "audio_fixed_paths": null,
    "max_repeat_per_attempt": 1,
    "status": "active",
    "created_by": 1,
    "created_at": "2026-06-22T03:34:01.000000Z",
    "updated_at": "2026-06-22T08:11:49.000000Z",
    "deleted_at": null,
    "dtmf_actions": [
      {
        "id": 7,
        "script_id": 2,
        "digit": "1",
        "label": "Confirm appointment",
        "action_type": "playback_then_hangup",
        "target_audio_url": "/media/thank_you.wav",
        "target_script_id": null,
        "target_queue_name": null,
        "sort_order": 0,
        "created_at": "2026-06-22T03:34:01.000000Z",
        "updated_at": "2026-06-22T03:34:01.000000Z"
      },
      {
        "id": 8,
        "script_id": 2,
        "digit": "3",
        "label": "Transfer to hotline",
        "action_type": "queue",
        "target_audio_url": null,
        "target_script_id": null,
        "target_queue_name": "support_queue",
        "sort_order": 1,
        "created_at": "2026-06-22T03:34:01.000000Z",
        "updated_at": "2026-06-22T03:34:01.000000Z"
      }
    ]
  }
}

PUT /api/autocall/scripts/{id} — update

Request:

http
PUT /api/autocall/scripts/2
Content-Type: application/json
Authorization: Bearer <token>

Body (partial update — send only fields to change; if you send dtmf_actions the system deletes all old DTMF entries and recreates them from the new array):

json
{
  "name": "Appointment reminder — v2",
  "description": "Updated template for Q3-2026",
  "voice_id": "voice_id_premium_01",
  "max_repeat_per_attempt": 2,
  "dtmf_actions": [
    {
      "digit": "1",
      "label": "Confirm appointment",
      "action_type": "playback_then_hangup",
      "target_audio_url": "/media/thank_you.wav"
    },
    {
      "digit": "3",
      "label": "Transfer to hotline",
      "action_type": "queue",
      "target_queue_name": "support@default"
    }
  ]
}

Response 200 (full updated script + new dtmf_actions[]):

json
{
  "data": {
    "id": 2,
    "tenant_id": 1,
    "name": "Appointment reminder — v2",
    "description": "Updated template for Q3-2026",
    "template_text": "ABC Company kindly informs you of the service appointment on {{appointment_date}}, with a total amount of {{total_amount}}. ... Thank you.",
    "voice_provider": "tts_provider_01",
    "voice_id": "voice_id_premium_01",
    "tts_tier": "B",
    "audio_fixed_paths": null,
    "max_repeat_per_attempt": 2,
    "status": "active",
    "created_by": 1,
    "created_at": "2026-06-22T03:34:01.000000Z",
    "updated_at": "2026-06-25T04:49:00.000000Z",
    "deleted_at": null,
    "dtmf_actions": [
      {
        "id": 9,
        "tenant_id": 1,
        "script_id": 2,
        "digit": "1",
        "label": "Confirm appointment",
        "action_type": "playback_then_hangup",
        "target_audio_url": "/media/thank_you.wav",
        "target_script_id": null,
        "target_queue_name": null,
        "sort_order": 0,
        "created_at": "2026-06-25T04:49:00.000000Z",
        "updated_at": "2026-06-25T04:49:00.000000Z"
      },
      {
        "id": 10,
        "tenant_id": 1,
        "script_id": 2,
        "digit": "3",
        "label": "Transfer to hotline",
        "action_type": "queue",
        "target_audio_url": null,
        "target_script_id": null,
        "target_queue_name": "support@default",
        "sort_order": 1,
        "created_at": "2026-06-25T04:49:00.000000Z",
        "updated_at": "2026-06-25T04:49:00.000000Z"
      }
    ]
  }
}

PUT replaces DTMF

If the body includes dtmf_actions[], the system deletes the script's existing DTMF entries and recreates them from the new array. If dtmf_actions is NOT sent → existing DTMF entries are preserved. → IDs of new DTMF entries will DIFFER from the old IDs (the old ones were deleted).

DELETE /api/autocall/scripts/{id}

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

Response 422 (script is in use by an active campaign):

json
{ "message": "Cannot delete a script in use by a campaign. Archive the campaign first." }

Preview TTS — render a demo

http
POST /api/autocall/scripts/{id}/preview

Path params:

FieldTypeRequiredDescriptionValid values
idintegerYesScript ID to previewPositive integer existing in the account

Body (optional):

json
{
  "variables": {
    "salutation_name": "Mr. An",
    "amount": "500000",
    "callback_date": "2026-06-30"
  }
}

Body fields:

FieldTypeRequiredDescriptionValid values
variablesobjectNoMap {variable_code: value} to substitute placeholders in the template. Leave empty → the system uses default demo values according to each variable's data_typeKeys must match defined variable_code values

Response 200:

json
{
  "data": {
    "audio_url": "/api/autocall/scripts/12/preview-audio/preview_abc123.wav",
    "duration_sec": 12.4,
    "chars_used": 187
  }
}

Response fields:

FieldTypeDescriptionValid values
data.audio_urlstringInternal path used to stream the preview WAV. A GET with the Authorization header returns binary audio/wavPath relative to app.zorio.vn
data.duration_secnumberRendered audio duration (seconds)> 0, rounded to 1 decimal
data.chars_usedintegerCharacters consumed (billed for Tier A)>= 0

Preview rate limit

TTS preview is capped at 10 req/min/token (since it incurs TTS provider Tier A charges).

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