Scripts + DTMF actions
Quản lý kịch bản TTS (template + voice + DTMF actions) cho AutoCall. Bao gồm CRUD scripts, enum DTMF action types, danh sách queue helper và preview TTS.
Tạo script
POST /api/autocall/scriptsBody:
{
"name": "Chăm sóc chuẩn",
"description": "Template cho campaign chăm sóc khách hàng kích hoạt lại sau 30-60 ngày",
"template_text": "Xin chào {{salutation_name}}, chúng tôi xin nhắc bạn về lịch hẹn ngày {{callback_date}}. Vui lòng bấm phím 1 để xác nhận tham gia, phím 2 để chuyển nhân viên hỗ trợ, phím 9 để bỏ qua.",
"voice_id": "voice_id_premium_02",
"tts_tier": "B",
"max_repeat_per_attempt": 1,
"status": "draft",
"dtmf_actions": [
{
"digit": "1",
"label": "Xác nhận thanh toán",
"action_type": "playback_then_hangup",
"target_audio_url": "/media/thank_you.wav"
},
{
"digit": "2",
"label": "Chuyển nhân viên",
"action_type": "queue",
"target_queue_name": "support@default"
},
{
"digit": "9",
"label": "Bỏ qua",
"action_type": "hangup"
}
]
}Fields:
| Field | Required | Type | Mô tả |
|---|---|---|---|
name | Có | string, max:255 | Tên script |
template_text | Có | text | Template với placeholder |
voice_id | Có | string | ID giọng đọc — PHẢI lấy từ GET /api/autocall/voices (Voices). Hệ thống tự routing đến provider tương ứng. Truyền voice_id không tồn tại trong thư viện voice khả dụng -> 422. |
tts_tier | Không | enum (A/B, default B) | A = premium, render realtime từ nhà cung cấp TTS cao cấp với key của khách hàng, tính theo char usage. B = local unlimited, dùng thư viện voice tiếng Việt khả dụng sẵn, không phí, throughput cao. Khuyến nghị default B cho khối lượng lớn. |
max_repeat_per_attempt | Không | integer (1-5, default 1) | Số lần phát lại audio nếu user không bấm DTMF |
status | Không | enum (draft/active/archived, default draft) | Chỉ script active mới được engine sử dụng cho campaign chạy thực tế |
dtmf_actions[] | Không | array max:12 | Cấu hình phím bấm, mỗi item gồm digit/label/action_type/target_* |
Field voice_provider KHÔNG có trong request body
Không truyền trong request. Hệ thống tự routing theo voice_id -> tier tương ứng (A premium / B local). Response vẫn trả field này. — read-only, cho biết provider đã được sử dụng.
Error 422 voice_id không tồn tại:
{
"message": "The given data was invalid.",
"errors": {
"voice_id": [
"voice_id 'voice_id_premium_01' không tồn tại trong thư viện voice khả dụng. Lấy danh sách voice hợp lệ qua GET /api/autocall/voices."
]
}
}DTMF action types
action_type | Field bắt buộc kèm theo | Hành vi |
|---|---|---|
playback | target_audio_url | Phát file rồi chờ DTMF tiếp |
playback_then_hangup | target_audio_url | Phát file rồi ngắt máy |
switch_script | target_script_id | Chuyển sang script khác và phát lại từ đầu. |
queue | target_queue_name (= name định danh queue) | Chuyển vào queue, agent live tiếp nhận |
hangup | — | Ngắt máy ngay |
repeat_script | — | Phát lại toàn bộ script hiện tại. |
Lấy enum động qua API
Thay vì khai báo cố định 6 action type trong ứng dụng tích hợp, KH có thể fetch enum đầy đủ từ GET /api/autocall/dtmf-action-types (xem mục List action types). Tương tự, danh sách target_queue_name lấy từ GET /api/autocall/queues (xem List queue).
Validate ràng buộc tham chiếu (server-side)
playback/playback_then_hangup->target_audio_urlPHẢI khớpfile_pathcủa 1 media file thuộc tài khoản (lấy từGET /api/media-files). Truyền URL ngoài -> 422.switch_script->target_script_idPHẢI là script tồn tại trong tài khoản (lấy từGET /api/autocall/scripts). Truyền ID rác -> 422.queue->target_queue_namePHẢI khớpnamecủa queue active (lấy từGET /api/autocall/queues). Sai -> 422.
Error 422 sample:
{
"message": "The given data was invalid.",
"errors": {
"dtmf_actions.0.target_audio_url": [
"target_audio_url '/data/wrong/path.wav' không tồn tại trong Media Files của khách hàng. Upload file qua POST /api/media-files trước, rồi dùng file_path trả về."
]
}
}List action types cho DTMF (enum + label)
GET /api/autocall/dtmf-action-typesMục đích: trả về enum 6 action type kèm label tiếng Việt + danh sách field bắt buộc kèm theo. KH integration dùng để dùng để tạo danh sách lựa chọn "Hành vi khi bấm phím" trong UI cấu hình DTMF mà không cần hardcode enum.
Response 200:
{
"data": [
{
"code": "playback",
"label": "Phát audio",
"description": "Phát file âm thanh URL chỉ định, sau đó tiếp tục kịch bản.",
"required_fields": ["target_audio_url"]
},
{
"code": "playback_then_hangup",
"label": "Phát audio rồi cúp máy",
"description": "Phát file âm thanh xong tự động cúp máy.",
"required_fields": ["target_audio_url"]
},
{
"code": "switch_script",
"label": "Chuyển sang kịch bản khác",
"description": "Chuyển cuộc gọi sang kịch bản AutoCall khác (chuỗi IVR).",
"required_fields": ["target_script_id"]
},
{
"code": "queue",
"label": "Chuyển vào hàng đợi agent",
"description": "Chuyển cuộc gọi vào hàng đợi để agent live tiếp nhận.",
"required_fields": ["target_queue_name"]
},
{
"code": "repeat_script",
"label": "Lặp lại kịch bản",
"description": "Phát lại kịch bản hiện tại từ đầu (cho khách nghe lại).",
"required_fields": []
},
{
"code": "hangup",
"label": "Cúp máy",
"description": "Kết thúc cuộc gọi ngay lập tức.",
"required_fields": []
}
]
}Field response:
| Field | Type | Mô tả |
|---|---|---|
code | string | Giá trị enum dùng cho dtmf_actions[].action_type khi POST/PUT /scripts |
label | string | Nhãn tiếng Việt hiển thị trong dropdown UI |
description | string | Mô tả ngắn để giải thích thêm cho user end |
required_fields | string[] | Danh sách field bắt buộc đi kèm khi pick action này (vd playback -> bắt buộc kèm target_audio_url) |
List queue cho action_type=queue
GET /api/autocall/queuesMục đích: trả về danh sách queue active để client dùng làm target_queue_name khi pick action_type=queue. Dùng để tạo danh sách lựa chọn "Chọn hàng đợi". trong 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"
}
]
}Field response:
| Field | Type | Mô tả |
|---|---|---|
id | integer | ID queue trong DB (tham khảo) |
name | string | Tên kỹ thuật của queue — DÙNG làm target_queue_name khi POST/PUT script với DTMF queue action |
queue_number | string | Số extension queue (vd 2001) — dùng hiển thị cho user end |
strategy | string | Chiến lược phân phối: ring_all / longest_idle_agent / round_robin / ... |
Lưu ý quan trọng
Field target_queue_name trong dtmf_actions[] PHẢI khớp name của queue (không phải queue_number hay id). Nếu sai, cuộc gọi sẽ thất bại vì hệ thống không nhận diện được queue.
Response 201 mẫu sau khi tạo script
{
"data": {
"name": "Chăm sóc chuẩn",
"description": "Template cho campaign chăm sóc khách hàng kích hoạt lại sau 30-60 ngày",
"template_text": "Xin chào {{tong_can_thanh_toan}}, chúng tôi xin nhắc bạn về lịch hẹn ngày {{ngay_den_han}}. Vui lòng bấm phím 1 để xác nhận tham gia, phím 2 để chuyển nhân viên hỗ trợ, phím 9 để bỏ qua.",
"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": "Xác nhận thanh toán",
"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": "Chuyển nhân viên",
"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": "Bỏ qua",
"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
Hệ thống serialize field theo thứ tự insert/create + auto-injected (created_at/updated_at/id) ở cuối. Ứng dụng tích hợp không nên phụ thuộc vào thứ tự key. — parse theo key name. tenant_id xuất hiện trong cả script level và mỗi dtmf_actions[] item — Lặp lại ở cả hai entity, đây là hành vi thiết kế của hệ thống. vì cả 2 entity đều có tenant_id.
List / show / update / xoá
| Endpoint | Mục đích |
|---|---|
GET /api/autocall/scripts?status=active | List scripts (bộ lọc (tuỳ chọn)) |
GET /api/autocall/scripts/{id} | Chi tiết kèm dtmfActions |
PUT /api/autocall/scripts/{id} | Cập nhật. Nếu gửi dtmf_actions -> thay thế toàn bộ |
DELETE /api/autocall/scripts/{id} | Soft xoá (nếu đang dùng cho campaign -> 422) |
GET /api/autocall/scripts — list
Query params:
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
status | string | Không | Lọc theo trạng thái script | draft / active / archived |
{
"data": [
{
"id": 2,
"tenant_id": 1,
"name": "Nhắc lịch hẹn",
"description": null,
"template_text": "Công ty ABC trân trọng thông báo lịch hẹn dịch vụ {{ngay_den_han}} xác nhận tham gia trước 18 giờ {{ngay_den_han}}. Trường hợp đã xác nhận, vui lòng bỏ qua cuộc gọi này. Liên hệ tổng đài ấn phím 3. Xin cảm ơn",
"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
}
]
}Field response:
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
data[].id | integer | ID script | Số nguyên dương |
data[].tenant_id | integer | ID khách hàng sở hữu script | — |
data[].name | string | Tên script | Tối đa 255 ký tự |
data[].description | string | null | Mô tả | Tối đa 2000 ký tự |
data[].template_text | text | Template TTS với placeholder | — |
data[].voice_provider | string | Provider TTS (read-only, derive từ voice_id) | tts_provider_01 / local / ... |
data[].voice_id | string | ID giọng đọc TTS | Lấy từ GET /api/autocall/voices |
data[].tts_tier | string | Tier TTS | A (premium realtime) / B (local pre-rendered) |
data[].audio_fixed_paths | object | null | Cache các đoạn audio cố định đã render sẵn. đã render sẵn. (template không-biến) đã render. Có thể bỏ qua khi tích hợp nếu không sử dụng. | — |
data[].max_repeat_per_attempt | integer | Số lần phát lại audio nếu user không bấm DTMF | 1-5 |
data[].status | string | Trạng thái script | draft / active / archived |
data[].created_by | integer | User ID người tạo | — |
data[].created_at | datetime ISO 8601 | Thời điểm tạo (UTC, microseconds) | — |
data[].updated_at | datetime ISO 8601 | Thời điểm cập nhật gần nhất (UTC, microseconds) | — |
data[].deleted_at | datetime ISO 8601 | null | Soft delete timestamp | null nếu chưa xoá |
Format timestamp
API trả ISO 8601 với microseconds YYYY-MM-DDTHH:mm:ss.uuuuuuZ (vd 2026-06-22T03:34:01.000000Z). Ứng dụng tích hợp parse theo chuẩn ISO 8601. (microseconds optional).
GET /api/autocall/scripts/{id} — chi tiết
Object đầy đủ kèm dtmf_actions[]:
{
"data": {
"id": 2,
"tenant_id": 1,
"name": "Nhắc lịch hẹn",
"description": null,
"template_text": "Công ty ABC trân trọng thông báo lịch hẹn dịch vụ {{ngay_den_han}}, với số tiền là {{tong_can_thanh_toan}}. ... Xin cảm ơn",
"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": "Xác nhận thanh toán",
"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": "Chuyển tổng đài",
"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} — cập nhật
Request:
PUT /api/autocall/scripts/2
Content-Type: application/json
Authorization: Bearer <token>Body (cập nhật từng phần — chỉ gửi field cần đổi; nếu gửi dtmf_actions thì hệ thống xoá toàn bộ DTMF cũ + tạo lại theo array mới):
{
"name": "Nhắc nợ đến hạn — v2",
"description": "Cập nhật template cho Q3-2026",
"voice_id": "voice_id_premium_01",
"max_repeat_per_attempt": 2,
"dtmf_actions": [
{
"digit": "1",
"label": "Xác nhận thanh toán",
"action_type": "playback_then_hangup",
"target_audio_url": "/media/thank_you.wav"
},
{
"digit": "3",
"label": "Chuyển tổng đài",
"action_type": "queue",
"target_queue_name": "support@default"
}
]
}Response 200 (full script đã update + dtmf_actions[] mới):
{
"data": {
"id": 2,
"tenant_id": 1,
"name": "Nhắc nợ đến hạn — v2",
"description": "Cập nhật template cho Q3-2026",
"template_text": "Công ty ABC trân trọng thông báo lịch hẹn dịch vụ {{ngay_den_han}}, với số tiền là {{tong_can_thanh_toan}}. ... Xin cảm ơn",
"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": "Xác nhận thanh toán",
"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": "Chuyển tổng đài",
"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 replace DTMF
Nếu body có gửi dtmf_actions[], hệ thống xoá toàn bộ DTMF cũ của script + tạo lại theo array mới. Nếu KHÔNG gửi dtmf_actions -> giữ nguyên DTMF cũ. -> IDs của DTMF mới sẽ KHÁC với IDs cũ (cũ đã bị delete).
DELETE /api/autocall/scripts/{id}
{ "data": { "deleted": true } }Response 422 (script đang dùng cho campaign active):
{ "message": "Không thể xoá script đang được campaign sử dụng. Hãy archive campaign trước." }Preview TTS — render demo
POST /api/autocall/scripts/{id}/previewPath params:
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
id | integer | Có | ID script cần preview | Số nguyên dương tồn tại trong tài khoản |
Body (optional):
{
"variables": {
"salutation_name": "anh An",
"amount": "500000",
"callback_date": "2026-06-30"
}
}Body fields:
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
variables | object | Không | Map {variable_code: value} để thay thế placeholder bằng giá trị tương ứng trong template. Bỏ trống -> hệ thống dùng giá trị demo mặc định theo data_type của mỗi biến | Key phải khớp variable_code đã định nghĩa |
Response 200:
{
"data": {
"audio_url": "/api/autocall/scripts/12/preview-audio/preview_abc123.wav",
"duration_sec": 12.4,
"chars_used": 187
}
}Field response:
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
data.audio_url | string | Đường dẫn nội bộ để phát trực tiếp file WAV preview. GET với Authorization header trả binary audio/wav | Path relative cho app.zorio.vn |
data.duration_sec | number | Thời lượng audio đã render (giây) | > 0, làm tròn 1 chữ số thập phân |
data.chars_used | integer | Số ký tự đã sử dụng (tính phí với Tier A) | >= 0 |
Rate limit preview
TTS preview giới hạn 10 req/phút/token (vì charge TTS provider tier A).
