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ách hàng KHÔNG truyền. Hệ thống tự routing theo voice_id -> tier tương ứng (A premium / B local). Response server 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 | Load script khác, replay 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 | — | Replay full audio script hiện tại |
Lấy enum động qua API
Thay vì hardcode 6 action type ở client, 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 để build dropdown "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. Build dropdown "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, dial sẽ fail 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. Client KHÔNG dựa thứ tự key — parse theo key name. tenant_id xuất hiện trong cả script level và mỗi dtmf_actions[] item — redundant nhưng đúng vì cả 2 entity đều có tenant_id.
List / show / update / delete
| Endpoint | Mục đích |
|---|---|
GET /api/autocall/scripts?status=active | List scripts (filter optional) |
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 -> replace toàn bộ |
DELETE /api/autocall/scripts/{id} | Soft delete (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 (template không-biến) đã render. Không bắt buộc xử lý khi tích hợp | — |
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). Client parse theo ISO 8601 chuẩn (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 (partial update — 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} để substitute placeholder 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ộ để stream 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).
