Script + Outcome
Nhóm endpoint cấu hình kịch bản hội thoại (Call Scripts) và bộ mã kết quả cuộc gọi (Disposition Codes). Đây là 2 thành phần cốt lõi mà agent dùng trong mỗi cuộc gọi: script hướng dẫn cách nói, disposition phân loại kết quả khi kết thúc.
5. Call Scripts
| Endpoint | Mục đích |
|---|---|
GET /api/telesales/scripts | Danh sách script của khách hàng |
GET /api/telesales/scripts/{id} | Chi tiết script kèm các placeholder được phát hiện |
POST /api/telesales/scripts | Tạo script mới |
PUT /api/telesales/scripts/{id} | Cập nhật script |
DELETE /api/telesales/scripts/{id} | Xoá script |
POST /api/telesales/scripts/{id}/preview | Preview nội dung với lead + agent thật |
Script dùng cú pháp Markdown. Biến được tự động phát hiện từ nội dung — không cần khai báo riêng. Tại thời điểm agent mở lead, biến được thay thế bằng giá trị thực từ custom_fields của lead + profile của agent. Các placeholder chuyên ngành (số hợp đồng, account ID, mã đơn hàng, ...) được khai báo qua custom_fields của từng khách hàng.
GET /api/telesales/scripts — danh sách
Query parameters
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
q | string | optional | Tìm theo name (LIKE %keyword%) | |
campaign_id | integer | optional | Lọc các script đang được chiến dịch này dùng | |
page | integer | optional | Số trang | ≥ 1 (default 1) |
per_page | integer | optional | Bản ghi mỗi trang | 1-200 (default 50) |
Response fields (mỗi item trong data[])
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
id | integer | ID script | |
name | string | Tên script (unique trong tài khoản) | max 150 ký tự |
content_preview | string | Nội dung Markdown — cắt còn 200 ký tự đầu | |
placeholders_detected[] | array<string> | Danh sách biến phát hiện trong nội dung | |
used_by_campaigns[] | array<integer> | Mảng ID chiến dịch đang gắn script này | |
created_at | datetime | Thời điểm tạo (ISO 8601 UTC) | |
updated_at | datetime | Lần cập nhật cuối (ISO 8601 UTC) |
Response 200:
{
"data": [
{
"id": 12,
"name": "Default Outbound Script",
"content_preview": "## Opening\nHello {{customer_name}}...",
"placeholders_detected": ["customer_name", "agent_name", "brand_name"],
"used_by_campaigns": [36, 42],
"created_at": "2026-06-06T06:00:00Z",
"updated_at": "2026-06-06T06:00:00Z"
}
],
"meta": { "current_page": 1, "last_page": 1, "per_page": 50, "total": 3 }
}content_preview được cắt còn 200 ký tự đầu. Dùng GET /api/telesales/scripts/{id} để lấy nội dung đầy đủ.
GET /api/telesales/scripts/{id} — chi tiết
Path parameter
| Field | Type | Mô tả |
|---|---|---|
id | integer | ID script |
Response fields
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
id | integer | ID script | |
name | string | Tên script | max 150 ký tự |
content | string | Toàn bộ nội dung Markdown | max 32 KB |
placeholders_detected[] | array<string> | Biến được parse từ content | |
used_by_campaigns[] | array<object> | Mảng object {id, name} của chiến dịch đang gắn script | |
created_at | datetime | Thời điểm tạo (ISO 8601 UTC) | |
updated_at | datetime | Lần cập nhật cuối (ISO 8601 UTC) |
Response 200:
{
"data": {
"id": 12,
"name": "Default Outbound Script",
"content": "## Opening\nHello {{customer_name}}, this is {{agent_name}} from {{brand_name}}...\n\n## Body\n... talk track ...\n\n## Closing\nThank you.",
"placeholders_detected": ["customer_name", "agent_name", "brand_name"],
"used_by_campaigns": [
{ "id": 36, "name": "Summer Promo" },
{ "id": 42, "name": "Retention Q3" }
],
"created_at": "2026-06-06T06:00:00Z",
"updated_at": "2026-06-06T06:00:00Z"
}
}Response 404: { "message": "Script not found." }
POST /api/telesales/scripts — tạo
Request body fields
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
name | string | ✅ | Tên script — unique trong tài khoản | max 150 ký tự |
content | string | ✅ | Nội dung Markdown, biến tự phát hiện | max 32 KB |
Body:
{
"name": "Default Outbound Script",
"content": "## Opening\nHello {{customer_name}}, this is {{agent_name}} from {{brand_name}}...\n\n## Body\n... talk track for the campaign ...\n\n## Closing\nThank you for your time."
}Response fields
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
id | integer | ID script vừa tạo | |
name | string | Tên script | |
content | string | Nội dung Markdown | |
placeholders_detected[] | array<string> | Biến parse được từ content | |
created_at | datetime | Thời điểm tạo (ISO 8601 UTC) |
Response 201:
{
"data": {
"id": 12,
"name": "Default Outbound Script",
"content": "## Opening\nHello {{customer_name}}...",
"placeholders_detected": ["customer_name", "agent_name", "brand_name"],
"created_at": "2026-06-06T06:00:00Z"
}
}Response 422 — các trường hợp validate sai:
// Trùng tên script
{
"message": "The given data was invalid.",
"errors": { "name": ["The name has already been taken."] }
}
// Nội dung quá dài
{
"message": "The given data was invalid.",
"errors": { "content": ["The content may not be greater than 32768 characters."] }
}PUT /api/telesales/scripts/{id} — cập nhật
Body: giống POST.
Response 200: object script đã cập nhật, kèm placeholders_detected được tính lại từ nội dung mới.
Response 422 — xung đột:
// Script đang được chiến dịch active dùng — vẫn cho cập nhật nhưng kèm cảnh báo
{
"data": { "id": 12, ... },
"warnings": ["Script is currently used by 2 active campaigns. Agents will see new content on next call."]
}DELETE /api/telesales/scripts/{id}
Response 204 No Content (thành công).
Response 422 — script đang được chiến dịch active dùng:
{
"message": "Cannot delete a script that is in use by a campaign.",
"errors": { "id": ["Script 12 is bound to campaign(s): 36, 42. Detach from campaign first."] }
}POST /api/telesales/scripts/{id}/preview — render preview
Path parameter
| Field | Type | Mô tả |
|---|---|---|
id | integer | ID script cần render preview |
Request body fields
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
lead_id | integer | ✅ | ID lead — lấy giá trị cho từ custom_fields | Phải tồn tại |
agent_id | integer | ✅ | ID user agent — lấy giá trị cho từ profile | Phải tồn tại |
Body:
{
"lead_id": 78912,
"agent_id": 25
}Render nội dung với từng được thay bằng giá trị thực từ lead.custom_fields + profile của agent + brand của khách hàng.
Response fields
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
id | integer | ID script | |
rendered_content | string | Nội dung Markdown đã thay placeholder | |
missing_placeholders[] | array<string> | Biến không resolve được từ custom_fields / profile — UI có thể cảnh báo agent |
Response 200:
{
"data": {
"id": 12,
"rendered_content": "## Opening\nHello Anh Hung, this is Nguyen Van A from Zorio...\n\n## Body\n...",
"missing_placeholders": ["policy_number"]
}
}6. Call Outcomes (Disposition Codes)
6.1 Quản lý catalog disposition
| Endpoint | Mục đích |
|---|---|
GET /api/telesales/dispositions | Danh sách disposition của khách hàng |
GET /api/telesales/dispositions/{id} | Chi tiết một disposition |
POST /api/telesales/dispositions | Tạo disposition (admin) |
PUT /api/telesales/dispositions/{id} | Cập nhật disposition (admin) |
DELETE /api/telesales/dispositions/{id} | Xoá disposition (admin) |
GET /api/telesales/dispositions — danh sách
Query parameters
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
category | string | optional | Lọc theo nhóm kết quả | contact / no_contact / callback / remove |
is_active | bool | optional | Có bao gồm cả disposition đã ẩn không | true (default) / false |
Response fields (mỗi item trong data[])
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
id | integer | ID disposition | |
code | string | Mã immutable — agent / API dùng để gắn cuộc gọi | [A-Za-z0-9_-], max 32 ký tự |
label | string | Nhãn hiển thị | max 100 ký tự |
description | string|null | Mô tả chi tiết | max 500 ký tự |
category | string | Nhóm kết quả | contact / no_contact / callback / remove |
is_final | bool | Là kết quả cuối hay còn thử lại | true / false |
trigger_callback | bool | Tự tạo callback khi agent chọn mã này | true / false (chỉ true khi category=callback) |
trigger_dnc | bool | Tự thêm số vào DNC khi agent chọn mã này | true / false |
default_retry_delay_minutes | integer|null | Thời gian chờ trước khi quay lại lead nếu không phải kết quả final | 1-43200 (phút) |
color | string | Mã màu hex cho UI badge | #RRGGBB |
is_active | bool | Còn hiển thị trong dropdown của agent hay không | true / false |
usage_count_30d | integer | Số cuộc gọi đã dùng mã này trong 30 ngày gần nhất | ≥ 0 |
created_at | datetime | Thời điểm tạo (ISO 8601 UTC) |
Response 200:
{
"data": [
{
"id": 5,
"code": "SALE-OK",
"label": "Sale Success",
"description": "Customer agreed and committed",
"category": "contact",
"is_final": true,
"trigger_callback": false,
"trigger_dnc": false,
"default_retry_delay_minutes": null,
"color": "#10b981",
"is_active": true,
"usage_count_30d": 78,
"created_at": "2026-06-01T06:00:00Z"
}
]
}GET /api/telesales/dispositions/{id} — chi tiết
Path parameter
| Field | Type | Mô tả |
|---|---|---|
id | integer | ID disposition |
Response 200: object disposition (shape giống một phần tử trong mảng list).
Response 404: { "message": "Disposition not found." }
POST /api/telesales/dispositions — tạo (admin)
Request body fields
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
code | string | ✅ | Mã immutable — unique trong tài khoản | [A-Za-z0-9_-], max 32 ký tự |
label | string | ✅ | Nhãn hiển thị | max 100 ký tự |
description | string | optional | Mô tả chi tiết | max 500 ký tự |
category | string | ✅ | Nhóm kết quả | contact / no_contact / callback / remove |
is_final | bool | optional | Là kết quả cuối (không thử lại) | true / false (default false) |
trigger_callback | bool | optional | Tự tạo callback — yêu cầu category=callback | true / false (default false) |
trigger_dnc | bool | optional | Tự thêm số vào DNC | true / false (default false) |
default_retry_delay_minutes | integer | optional | Phút chờ trước khi quay lại lead | 1-43200 |
color | string | optional | Mã màu UI | #RRGGBB |
Body:
{
"code": "SALE-OK",
"label": "Sale Success",
"description": "Customer agreed and committed",
"category": "contact",
"is_final": true,
"trigger_callback": false,
"trigger_dnc": false,
"default_retry_delay_minutes": null,
"color": "#10b981"
}Response fields
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
id | integer | ID disposition vừa tạo | |
code | string | Mã immutable | |
label | string | Nhãn hiển thị | |
category | string | Nhóm kết quả | contact / no_contact / callback / remove |
is_final | bool | Là kết quả cuối | |
trigger_callback | bool | Tự tạo callback | |
trigger_dnc | bool | Tự thêm DNC | |
color | string | Mã màu | #RRGGBB |
is_active | bool | Hiển thị trong dropdown agent (default true) | |
created_at | datetime | Thời điểm tạo (ISO 8601 UTC) |
Response 201:
{
"data": {
"id": 5,
"code": "SALE-OK",
"label": "Sale Success",
"category": "contact",
"is_final": true,
"trigger_callback": false,
"trigger_dnc": false,
"color": "#10b981",
"is_active": true,
"created_at": "2026-06-06T06:00:00Z"
}
}Response 422 — các trường hợp validate sai:
// Trùng code
{
"message": "The given data was invalid.",
"errors": { "code": ["The code has already been taken."] }
}
// Category không hợp lệ
{
"message": "The given data was invalid.",
"errors": { "category": ["The selected category is invalid."] }
}
// trigger_callback=true nhưng category không phải 'callback'
{
"message": "The given data was invalid.",
"errors": { "trigger_callback": ["trigger_callback only applies when category='callback'."] }
}Response 403 — caller không phải admin:
{ "message": "You are not allowed to create dispositions. The admin role is required." }PUT /api/telesales/dispositions/{id} — cập nhật (admin)
Path parameter
| Field | Type | Mô tả |
|---|---|---|
id | integer | ID disposition |
Request body fields
Giống POST nhưng mọi field optional — chỉ truyền field cần đổi. code không thể đổi sau khi tạo (immutable identifier); truyền code mới sẽ trả 422.
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
label | string | Nhãn hiển thị | max 100 ký tự |
description | string|null | Mô tả chi tiết | max 500 ký tự |
category | string | Nhóm kết quả | contact / no_contact / callback / remove |
is_final | bool | Là kết quả cuối | |
trigger_callback | bool | Tự tạo callback (chỉ true khi category=callback) | |
trigger_dnc | bool | Tự thêm DNC | |
default_retry_delay_minutes | integer|null | Phút chờ retry | 1-43200 |
color | string | Mã màu | #RRGGBB |
is_active | bool | Bật/tắt hiển thị | true / false |
Response 200: object đã cập nhật.
Response 422:
// Cố ý đổi code
{
"message": "The given data was invalid.",
"errors": { "code": ["Code is immutable. Create a new disposition if a different code is needed."] }
}DELETE /api/telesales/dispositions/{id} — xoá (admin)
Path parameter
| Field | Type | Mô tả |
|---|---|---|
id | integer | ID disposition cần xoá |
Response 204 No Content (thành công — soft delete; set is_active=false).
Response 422 — disposition đang được dùng:
{
"message": "Cannot delete a disposition with associated call history.",
"errors": { "id": ["Disposition 5 has 78 calls in last 30 days. Hide instead of deleting (set is_active=false)."] }
}Ẩn vs Xoá
Để giữ lịch sử báo cáo, delete mặc định là soft delete (set is_active=false). Disposition cũ vẫn xuất hiện trong báo cáo nhưng bị ẩn khỏi dropdown của agent. Hard delete chỉ được phép khi usage_count = 0.
6.2 Catalog khởi tạo gợi ý (8 mã chung)
Mỗi tài khoản mới sẽ được seed sẵn một bộ catalog tổng quát, không gắn với ngành cụ thể. Khách hàng có thể mở rộng hoặc thay thế qua API hoặc UI quản trị.
| Code | Label | Category | is_final | trigger_callback | trigger_dnc |
|---|---|---|---|---|---|
SUCCESS | Successful Contact | contact | x | ||
CALLBACK_REQ | Customer Asked for Callback | callback | x | ||
REFUSED | Customer Refused | contact | x | ||
NO_ANSWER | No Answer | no_contact | |||
BUSY | Busy | no_contact | |||
VOICEMAIL | Voicemail Left | no_contact | |||
WRONG_NUMBER | Wrong Number | remove | x | ||
DNC_REQUEST | DNC Request | remove | x | x |
Đối với catalog chuyên ngành (chăm sóc khách hàng, lịch hẹn dịch vụ, khảo sát, lịch hẹn y tế, ...), tham khảo gói cấu hình riêng riêng cho khách hàng — sẽ seed sẵn bộ code mở rộng phù hợp workflow nghiệp vụ.
