Bộ dữ liệu Telesales (Datasets)
Bộ dữ liệu (Dataset) là đơn vị cấu hình schema cho một chiến dịch Telesales — chứa:
- Trường tuỳ chỉnh cho lead (
phase=lead) — dữ liệu nhập trước khi gọi (VD số hợp đồng, sản phẩm, giá trị đơn hàng) - Trường tuỳ chỉnh cho cuộc gọi (
phase=attempt) — kết quả agent điền lúc wrap-up (VD ngày cam kết, phương thức thanh toán, ghi chú) - Danh sách mã kết quả (dispositions) áp dụng cho chiến dịch dùng bộ dữ liệu này
- Hiển thị conditional — field chỉ hiện khi mã kết quả nằm trong danh sách
disposition_in(VD "Ngày cam kết" chỉ hiện khi disposition = "Promise-to-Pay")
Từ 14/08/2026, mọi chiến dịch mới bắt buộc gắn với 1 bộ dữ liệu. Chiến dịch cũ được ánh xạ tự động sang bộ dữ liệu mặc định của tenant. API /api/custom-fields cho 2 entity TelesalesLead + TelesalesCallAttempt đã ngừng phục vụ (410 Gone) — xem trang Custom Fields để biết chi tiết.
Quyền truy cập
| Endpoint | Quyền yêu cầu |
|---|---|
GET (đọc) | Module telesales cấp viewer trở lên |
POST/PUT/DELETE (ghi) | Permission manage_telesales_dataset (tier admin) |
POST /api/v1/telesales/datasets/{code}/leads (push lead) | Sanctum PAT với ability telesales:push |
Cấu trúc bộ dữ liệu
Mỗi bộ dữ liệu có cấu trúc:
| Trường | Kiểu | Mô tả |
|---|---|---|
id | integer | Auto-increment |
code | string | Unique per tenant, dùng trong URL push lead |
name | string | Tên hiển thị (VD "FinanceCorp Life Insurance — Renewal") |
description | string | Mô tả ngắn |
industry_tag | string | null | Enum tuỳ chọn: insurance, finance, tourism, healthcare, realestate, survey, other |
is_default | boolean | 1 bộ mặc định per tenant (auto-select trong wizard tạo chiến dịch) |
is_active | boolean | false = ẩn khỏi list, không dùng được cho chiến dịch mới |
fields[] | array | Danh sách trường tuỳ chỉnh (chi tiết dưới) |
dispositions[] | array | Danh sách mã kết quả áp dụng (N-N link với telesales_dispositions) |
field_count | integer | Cache count |
disposition_count | integer | Cache count |
Mỗi trường (field) có cấu trúc:
| Trường | Kiểu | Mô tả |
|---|---|---|
id | integer | |
field_code | string | Unique per dataset, snake_case (VD policy_number) |
label | string | Nhãn hiển thị |
type | string | Enum: text, textarea, number, date, datetime, select, multiselect, phone, email, boolean |
required | boolean | Bắt buộc khi push lead / submit disposition |
options | array | null | Cho type select/multiselect: [{value, label}, ...] |
visible_when | object | null | Điều kiện hiển thị, VD {"disposition_in": ["RN-02", "RI-02"]} |
phase | string | lead hoặc attempt |
help_text | string | null | Gợi ý hiển thị dưới input |
display_order | integer | Thứ tự trong form |
GET /api/v1/telesales/datasets — Danh sách bộ dữ liệu
Trả về tất cả bộ dữ liệu của tenant.
GET /api/v1/telesales/datasets
Authorization: Bearer <api_token>
Accept: application/jsonQuery params:
| Param | Mô tả |
|---|---|
is_active | true (mặc định) chỉ trả bộ active. false để bao gồm bộ đã lưu trữ. |
industry_tag | Filter theo ngành (VD insurance) |
Response 200:
{
"data": [
{
"id": 5,
"code": "sample_life_renewal",
"name": "FinanceCorp Life Insurance — Renewal",
"description": "Chiến dịch nhắc phí + tái tục hợp đồng bảo hiểm",
"industry_tag": "insurance",
"is_default": true,
"is_active": true,
"field_count": 23,
"disposition_count": 14
},
{
"id": 6,
"code": "sample_consumer_loan",
"name": "SampleFin — Consumer Loan",
"industry_tag": "finance",
"is_default": false,
"field_count": 15,
"disposition_count": 10
}
]
}GET /api/v1/telesales/datasets/{id} — Chi tiết bộ dữ liệu
Trả về full metadata + fields + dispositions.
GET /api/v1/telesales/datasets/5
Authorization: Bearer <api_token>Response 200:
{
"data": {
"id": 5,
"code": "sample_life_renewal",
"name": "FinanceCorp Life Insurance — Renewal",
"description": "...",
"industry_tag": "insurance",
"is_default": true,
"is_active": true,
"fields": [
{
"id": 101,
"field_code": "full_name",
"label": "Họ tên",
"type": "text",
"required": true,
"phase": "lead",
"display_order": 10
},
{
"id": 102,
"field_code": "policy_number",
"label": "Số hợp đồng",
"type": "text",
"required": true,
"phase": "lead",
"help_text": "Định dạng POL-YYYY-NNNN",
"display_order": 20
},
{
"id": 103,
"field_code": "annual_premium",
"label": "Phí bảo hiểm hàng năm",
"type": "number",
"required": false,
"phase": "lead",
"display_order": 30
},
{
"id": 201,
"field_code": "commitment_date",
"label": "Ngày cam kết thanh toán",
"type": "date",
"required": true,
"phase": "attempt",
"visible_when": {
"disposition_in": ["RN-02", "RI-02"]
},
"display_order": 10
},
{
"id": 202,
"field_code": "payment_method",
"label": "Phương thức thanh toán",
"type": "select",
"options": [
{ "value": "cash", "label": "Tiền mặt" },
{ "value": "bank", "label": "Chuyển khoản" },
{ "value": "atm", "label": "Thẻ ATM" },
{ "value": "wallet", "label": "Ví điện tử" }
],
"phase": "attempt",
"visible_when": {
"disposition_in": ["RN-01", "RI-01"]
},
"display_order": 20
}
],
"dispositions": [
{
"code": "RN-01",
"label": "Renewal — Thành công",
"category": "converted",
"color": "#16A34A"
},
{
"code": "RN-02",
"label": "Renewal — Hẹn thanh toán",
"category": "callback",
"color": "#F59E0B"
}
]
}
}POST /api/v1/telesales/datasets/{id}/fields — Tạo trường mới
POST /api/v1/telesales/datasets/5/fields
Authorization: Bearer <api_token>
Content-Type: application/json
{
"field_code": "policy_number",
"label": "Số hợp đồng",
"type": "text",
"required": true,
"phase": "lead",
"display_order": 20,
"help_text": "Định dạng POL-YYYY-NNNN"
}Response 201 trả về field vừa tạo:
{
"data": {
"id": 102,
"dataset_id": 5,
"field_code": "policy_number",
"label": "Số hợp đồng",
"type": "text",
"required": true,
"phase": "lead",
"display_order": 20
}
}Validation:
field_codephải khớp regex^[a-z][a-z0-9_]{0,63}$và unique trong datasettypephải nằm trong danh sách hỗ trợ- Với type
select/multiselect, bắt buộc cóoptions(array of{value, label}) phasebắt buộcleadhoặcattempt
Error 422 nếu trùng code:
{
"error": "field_code_taken",
"message": "Mã trường \"policy_number\" đã tồn tại trong bộ dữ liệu này."
}PUT /api/v1/telesales/datasets/{id}/fields/{fid} — Sửa trường
Không cho phép đổi field_code, type, phase sau khi tạo (data đã lưu có thể bị corrupt). Cho phép đổi label, required, options, visible_when, help_text, display_order.
PUT /api/v1/telesales/datasets/5/fields/102
Content-Type: application/json
{
"label": "Mã hợp đồng (Policy No.)",
"required": false,
"help_text": "Có thể để trống nếu chưa có"
}DELETE /api/v1/telesales/datasets/{id}/fields/{fid} — Xoá trường
Soft delete: is_active=false. Data đã lưu trong custom_fields JSON của leads/attempts vẫn giữ. Có thể restore bằng PUT ... {"is_active": true}.
POST /api/v1/telesales/datasets/{id}/fields/reorder — Sắp xếp lại thứ tự
POST /api/v1/telesales/datasets/5/fields/reorder
Content-Type: application/json
{
"items": [
{ "id": 101, "display_order": 10 },
{ "id": 102, "display_order": 20 },
{ "id": 103, "display_order": 30 }
]
}GET /api/v1/telesales/datasets/{id}/dispositions — Danh sách mã kết quả áp dụng
GET /api/v1/telesales/datasets/5/dispositionsResponse: array các mã kết quả đã gán cho bộ dữ liệu (subset của catalog telesales_dispositions chung của tenant).
PUT /api/v1/telesales/datasets/{id}/dispositions — Cập nhật mã kết quả áp dụng
PUT /api/v1/telesales/datasets/5/dispositions
Content-Type: application/json
{
"dispositions": [
"RN-01", "RN-02", "RN-03", "RN-04",
"RI-01", "RI-02", "RI-03",
"CB-01", "DO-01", "OT-01"
]
}POST /api/v1/telesales/datasets/{dataset_code}/leads — Push lead từ hệ thống ngoài
Endpoint dùng cho đối tác đẩy lead từ CRM/hệ thống nội bộ vào Zorio. Auth qua Sanctum PAT với ability telesales:push.
POST /api/v1/telesales/datasets/sample_life_renewal/leads
Authorization: Bearer <api_token>
Content-Type: application/json
{
"leads": [
{
"full_name": "Alice Sample",
"phone": "0900000001",
"lead_data": {
"policy_number": "POL-2024-0001",
"annual_premium": 12000000,
"policy_status": "lapsed"
}
},
{
"full_name": "Bob Sample",
"phone": "0900000002",
"lead_data": {
"policy_number": "POL-2024-0002",
"annual_premium": 15000000
}
}
],
"campaign_code": "SAMPLE_RENEWAL_202608"
}Response 200:
{
"ok": true,
"inserted": 2,
"skipped_duplicate": 0,
"skipped_dnc": 0,
"errors": [],
"dataset": {
"id": 5,
"code": "sample_life_renewal",
"name": "FinanceCorp Life Insurance — Renewal"
},
"campaign": {
"id": 123,
"code": "SAMPLE_RENEWAL_202608",
"created": false
}
}Block dataset trong response cho phép client tracking bộ dữ liệu nào đã được ánh xạ, phòng trường hợp dataset_code sai chính tả trong URL nhưng vẫn được match fuzzy (hiện chưa hỗ trợ nhưng để tương thích ngược).
Validation
- Mỗi lead phải có
phone(bắt buộc). Auto normalize0xxx→84xxx. lead_data(nếu có) phải khớp schema fieldphase=leadcủa bộ dữ liệu:- Field không tồn tại → row bị bỏ qua với
errors[i].error = "unknown_field" - Field required thiếu → row bị bỏ qua với
errors[i].error = "missing_required" - Data type sai → row bị bỏ qua với
errors[i].error = "invalid_type"
- Field không tồn tại → row bị bỏ qua với
- Phone trùng trong campaign hiện tại →
skipped_duplicate++ - Phone nằm trong DNC list →
skipped_dnc++ - Batch limit: 500 lead/request. Batch lớn hơn cần chia nhiều request.
Rate limit
- 60 request/phút/token
- Tenant vượt quota → HTTP 429 với
Retry-Afterheader
Pipeline kiểm tra khi ghi custom_fields (phase=attempt)
Khi agent submit disposition qua POST /api/telesales/calls/{uuid}/disposition, giá trị custom_fields được validate theo schema phase=attempt của dataset của chiến dịch. Pipeline 5 bước:
- Từ chối field không có trong dataset — key không match → 422
unknown_field - Chuyển đổi kiểu dữ liệu —
"42"→ int;"true"→ bool - Áp dụng validation — required, min/max, options membership
- Filter theo
visible_when— field không match disposition đang gửi → bị bỏ qua (không lưu) - Persist vào
telesales_call_attempts.custom_fieldsJSON
Response 200 kèm custom_fields_report:
{
"data": { "attempt_id": 999 },
"custom_fields_report": {
"summary": {
"total_fields": 3,
"accepted": 2,
"rejected": 1,
"filtered_by_visible_when": 0
},
"errors": [
{
"field": "commitment_date",
"error": "missing_required",
"message": "Trường \"Ngày cam kết thanh toán\" bắt buộc khi disposition = RN-02"
}
]
}
}Migration từ Custom Fields cũ
Nếu integration của bạn trước đây gọi /api/custom-fields cho 2 entity Telesales, xem hướng dẫn migration đầy đủ tại trang Custom Fields.
Tóm tắt các bước:
- Gọi
GET /api/v1/telesales/datasets→ cache list bộ dữ liệu của tenant - Với mỗi bộ dữ liệu quan tâm, gọi
GET /api/v1/telesales/datasets/{id}→ cache fields + dispositions - Thay mọi call
GET /api/custom-fields?entity=TelesalesLeadbằng đọcfields[]từ dataset (filterphase='lead') - Thay mọi call
POST/PUT/DELETE /api/custom-fieldsbằng endpoint dataset tương ứng - Khi push lead qua endpoint mới, dùng response
datasetblock để confirm
Sample code
Python (requests)
import requests
BASE = "https://api.zorio.vn"
TOKEN = "your_api_token"
headers = {"Authorization": f"Bearer {TOKEN}", "Accept": "application/json"}
# List datasets
datasets = requests.get(f"{BASE}/api/v1/telesales/datasets", headers=headers).json()
# Get schema
dataset = requests.get(f"{BASE}/api/v1/telesales/datasets/5", headers=headers).json()['data']
lead_fields = [f for f in dataset['fields'] if f['phase'] == 'lead']
# Push lead
result = requests.post(
f"{BASE}/api/v1/telesales/datasets/sample_life_renewal/leads",
headers=headers,
json={
"leads": [{"full_name": "Alice", "phone": "0900000001",
"lead_data": {"policy_number": "POL-2024-0001"}}],
"campaign_code": "SAMPLE_RENEWAL_202608"
}
).json()
print(f"Inserted {result['inserted']} into dataset {result['dataset']['name']}")Node.js (axios)
const axios = require('axios');
const client = axios.create({
baseURL: 'https://api.zorio.vn',
headers: { Authorization: `Bearer ${process.env.ZORIO_TOKEN}` },
});
// Push lead batch
const { data } = await client.post('/api/v1/telesales/datasets/sample_life_renewal/leads', {
leads: [
{ full_name: 'Alice', phone: '0900000001',
lead_data: { policy_number: 'POL-2024-0001' } },
],
campaign_code: 'SAMPLE_RENEWAL_202608',
});
console.log(`Inserted ${data.inserted} into dataset ${data.dataset.name}`);cURL
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"leads": [
{ "full_name": "Alice", "phone": "0900000001",
"lead_data": { "policy_number": "POL-2024-0001" } }
],
"campaign_code": "SAMPLE_RENEWAL_202608"
}' \
https://api.zorio.vn/api/v1/telesales/datasets/sample_life_renewal/leadsFAQ
Q1. Dataset có nested (parent-child) không?
A: Chưa. Mỗi dataset độc lập. Nếu muốn share field common, hiện phải copy sang từng dataset.
Q2. Chuyển dataset cho chiến dịch đang chạy được không?
A: Không khuyến nghị. Lead đã import theo schema cũ → data sẽ mismatch. Nên tạo chiến dịch mới với dataset khác.
Q3. Xoá dataset đang dùng cho chiến dịch được không?
A: Không. Trả 422 dataset_in_use. Phải xoá/lưu trữ chiến dịch trước.
Q4. Bulk create/update field không?
A: Chưa hỗ trợ. Loop từng call. Sẽ bổ sung trong bản v1.18+.
Q5. Webhook nào fire khi lead push qua dataset endpoint?
A: telesales.lead.imported — fire per-lead sau khi INSERT thành công. Subscribe trong Portal → Webhooks.
