English
English
Appearance
English
English
Appearance
Manage TTS scripts (template + voice + DTMF actions) for AutoCall. Includes script CRUD, the DTMF action types enum, a queue helper list, and TTS preview.
POST /api/autocall/scriptsBody:
{
"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:
| Field | Required | Type | Description |
|---|---|---|---|
name | Yes | string, max:255 | Script name |
template_text | Yes | text | Template with placeholders |
voice_id | Yes | string | Voice 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_tier | No | enum (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_attempt | No | integer (1-5, default 1) | Times to replay the audio if the user does not press DTMF |
status | No | enum (draft/active/archived, default draft) | Only active scripts can be used by the engine for real campaign runs |
dtmf_actions[] | No | array max:12 | Keypress 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:
{
"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."
]
}
}action_type | Required companion field | Behavior |
|---|---|---|
playback | target_audio_url | Play the file then wait for the next DTMF |
playback_then_hangup | target_audio_url | Play the file then hang up |
switch_script | target_script_id | Load a different script, replay from the beginning |
queue | target_queue_name (= queue name identifier) | Transfer into a queue for a live agent to answer |
hangup | — | Hang up immediately |
repeat_script | — | Replay 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_hangup → target_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_script → target_script_id MUST be an existing script in the account (from GET /api/autocall/scripts). Passing a bogus ID → 422.queue → target_queue_name MUST match the name of an active queue (from GET /api/autocall/queues). Mismatched → 422.Error 422 sample:
{
"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."
]
}
}GET /api/autocall/dtmf-action-typesPurpose: 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:
{
"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:
| Field | Type | Description |
|---|---|---|
code | string | Enum value to use for dtmf_actions[].action_type in POST/PUT /scripts |
label | string | Display label for the dropdown UI |
description | string | Short description shown to the end user |
required_fields | string[] | List of companion fields that must accompany this action (e.g. playback → requires target_audio_url) |
action_type=queue GET /api/autocall/queuesPurpose: 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:
{
"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:
| Field | Type | Description |
|---|---|---|
id | integer | Queue DB ID (for reference) |
name | string | Queue technical name — USED as target_queue_name when POST/PUT a script with a DTMF queue action |
queue_number | string | Queue extension number (e.g. 2001) — displayed to the end user |
strategy | string | Distribution 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.
{
"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.
| Endpoint | Purpose |
|---|---|
GET /api/autocall/scripts?status=active | List 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:
| Field | Type | Required | Description | Valid values |
|---|---|---|---|---|
status | string | No | Filter by script status | draft / active / archived |
{
"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:
| Field | Type | Description | Valid values |
|---|---|---|---|
data[].id | integer | Script ID | Positive integer |
data[].tenant_id | integer | ID of the account that owns the script | — |
data[].name | string | Script name | Up to 255 characters |
data[].description | string | null | Description | Up to 2000 characters |
data[].template_text | text | TTS template with placeholders | — |
data[].voice_provider | string | TTS provider (read-only, derived from voice_id) | tts_provider_01 / local / ... |
data[].voice_id | string | TTS voice ID | From GET /api/autocall/voices |
data[].tts_tier | string | TTS tier | A (premium realtime) / B (local pre-rendered) |
data[].audio_fixed_paths | object | null | Cache of rendered fixed audio segments (variable-free template parts). Integrators do not need to handle this | — |
data[].max_repeat_per_attempt | integer | Times to replay audio if the user does not press DTMF | 1-5 |
data[].status | string | Script status | draft / active / archived |
data[].created_by | integer | User ID of the creator | — |
data[].created_at | datetime ISO 8601 | Creation time (UTC, microseconds) | — |
data[].updated_at | datetime ISO 8601 | Last update time (UTC, microseconds) | — |
data[].deleted_at | datetime ISO 8601 | null | Soft delete timestamp | null 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[]:
{
"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:
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):
{
"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[]):
{
"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} { "data": { "deleted": true } }Response 422 (script is in use by an active campaign):
{ "message": "Cannot delete a script in use by a campaign. Archive the campaign first." }POST /api/autocall/scripts/{id}/previewPath params:
| Field | Type | Required | Description | Valid values |
|---|---|---|---|---|
id | integer | Yes | Script ID to preview | Positive integer existing in the account |
Body (optional):
{
"variables": {
"salutation_name": "Mr. An",
"amount": "500000",
"callback_date": "2026-06-30"
}
}Body fields:
| Field | Type | Required | Description | Valid values |
|---|---|---|---|---|
variables | object | No | Map {variable_code: value} to substitute placeholders in the template. Leave empty → the system uses default demo values according to each variable's data_type | Keys must match defined variable_code values |
Response 200:
{
"data": {
"audio_url": "/api/autocall/scripts/12/preview-audio/preview_abc123.wav",
"duration_sec": 12.4,
"chars_used": 187
}
}Response fields:
| Field | Type | Description | Valid values |
|---|---|---|---|
data.audio_url | string | Internal path used to stream the preview WAV. A GET with the Authorization header returns binary audio/wav | Path relative to app.zorio.vn |
data.duration_sec | number | Rendered audio duration (seconds) | > 0, rounded to 1 decimal |
data.chars_used | integer | Characters 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).