Campaigns
Quản lý chiến dịch AutoCall — CRUD + lifecycle controls (launch / pause / resume / archive / unarchive) + thống kê.
Tạo chiến dịch
POST /api/autocall/campaignsBody:
{
"name": "Chăm sóc khách hàng Q3-2026",
"description": "Chăm sóc khách hàng kích hoạt lại sau 30-60 ngày",
"script_id": 12,
"caller_id_group_id": 3,
"max_retries": 3,
"retry_interval_minutes": 60,
"retry_only_when": "no_answer",
"max_concurrent": 10,
"originate_timeout": 40,
"write_to_main_cdr": false,
"start_at": "2026-06-25T08:00:00Z",
"end_at": "2026-06-30T17:30:00Z"
}Fields:
| Field | Required | Type | Mô tả |
|---|---|---|---|
name | Có | string, max:255 | Tên chiến dịch |
description | Không | string, max:2000 | Mô tả |
script_id | Có | integer | ID script TTS (xem Scripts). PHẢI tồn tại VÀ status='active'. Truyền script đang draft/archived -> 422. |
caller_id_group_id | Không | integer | ID pool đầu số. Bỏ trống -> engine fallback default trunk của khách hàng. PHẢI tồn tại + status='active'. |
max_retries | Không | integer (0-10, default 3) | Số lần thử lại tối đa nếu attempt fail |
retry_interval_minutes | Không | integer (1-43200, default 60) | Khoảng cách giữa các attempt (phút) |
retry_only_when | Không | enum (no_answer/busy/failed/any, default no_answer) | Chỉ retry khi attempt result match |
max_concurrent | Không | integer (1-200, default 10) | Số cuộc đồng thời tối đa |
originate_timeout | Không | integer (10-180, default 40) | Timeout chờ B-leg nhận máy (giây) |
write_to_main_cdr | Không | boolean (default false) | Có ghi vào CDR chung hệ thống không. False = chỉ ghi log nội bộ AutoCall. |
start_at | Không | datetime UTC | Thời điểm bắt đầu được dial. Bỏ trống = launch ngay |
end_at | Không | datetime UTC | Thời điểm dừng (phải > start_at) |
Response 201 (response trả đầy đủ các field):
{
"data": {
"id": 19,
"tenant_id": 1,
"name": "Chăm sóc khách hàng Q3-2026",
"description": "Chăm sóc khách hàng kích hoạt lại sau 30-60 ngày",
"script_id": 12,
"status": "draft",
"gateway_name": null,
"caller_id": null,
"caller_id_group_id": 8,
"max_retries": 3,
"retry_interval_minutes": 60,
"retry_only_when": "no_answer",
"write_to_main_cdr": false,
"max_concurrent": 10,
"originate_timeout": 40,
"start_at": "2026-06-25T08:00:00.000000Z",
"end_at": "2026-06-30T17:30:00.000000Z",
"total_leads": 0,
"total_calls": 0,
"total_connected": 0,
"total_failed": 0,
"created_by": 3,
"created_at": "2026-06-25T09:53:47.000000Z",
"updated_at": "2026-06-25T09:53:47.000000Z",
"deleted_at": null
}
}Ghi chú
Response trả tất cả field cấu hình của campaign. Stats fields (total_*) = 0 lúc tạo (chưa có lead/call activity). Field gateway_name, caller_id (single override) null nếu dùng pool theo caller_id_group_id. Field deleted_at null nếu chưa archive (soft delete).
Response 422 (validation):
{
"message": "The given data was invalid.",
"errors": {
"script_id": ["The selected script id is invalid."],
"retry_only_when": ["The selected retry only when is invalid."]
}
}Lifecycle controls
| Endpoint | Action | Từ -> Sang |
|---|---|---|
POST /api/autocall/campaigns/{id}/launch | Kích hoạt | draft/paused -> active |
POST /api/autocall/campaigns/{id}/pause | Tạm dừng | active -> paused |
POST /api/autocall/campaigns/{id}/resume | Tiếp tục | paused -> active (alias của launch) |
POST /api/autocall/campaigns/{id}/archive | Lưu trữ | draft/paused/done -> archived (yêu cầu KHÔNG đang active) |
POST /api/autocall/campaigns/{id}/unarchive | Khôi phục | archived -> paused |
PUT /api/autocall/campaigns/{id} | Cập nhật metadata | giữ status |
DELETE /api/autocall/campaigns/{id} | Xoá VĨNH VIỄN | yêu cầu archived trước (hard delete cascade: campaign + leads + attempts + dtmf_events) |
Quy tắc bắt buộc
- Phải
pausetrước khiarchive(campaign đang chạy không thể archive). - Phải
archivetrước khidelete(xoá vĩnh viễn -> mất data báo cáo).
Response 200 (áp dụng cho mọi action launch/pause/resume/archive/unarchive):
{
"data": {
"id": 21,
"tenant_id": 1,
"name": "Chăm sóc Q3-2026 2",
"description": "Chăm sóc khách hàng kích hoạt lại sau 30-60 ngày",
"script_id": 12,
"status": "active",
"gateway_name": null,
"caller_id": null,
"caller_id_group_id": 8,
"max_retries": 3,
"retry_interval_minutes": 60,
"retry_only_when": "no_answer",
"write_to_main_cdr": false,
"max_concurrent": 10,
"originate_timeout": 40,
"start_at": "2026-06-25T08:00:00.000000Z",
"end_at": "2026-06-30T17:30:00.000000Z",
"total_leads": 0,
"total_calls": 0,
"total_connected": 0,
"total_failed": 0,
"created_by": 3,
"created_at": "2026-06-25T14:26:16.000000Z",
"updated_at": "2026-06-25T14:27:41.000000Z",
"deleted_at": null
}
}Field bổ sung (không có ở body POST):
| Field | Type | Mô tả |
|---|---|---|
gateway_name | string | null | Override gateway dial cụ thể (mặc định null = dùng default routing) |
caller_id | string | null | Override caller ID đơn (mặc định null = dùng pool theo caller_id_group_id) |
total_leads | integer | Tổng lead đã import vào campaign |
total_calls | integer | Tổng cuộc gọi đã dial |
total_connected | integer | Tổng cuộc connect thành công |
total_failed | integer | Tổng cuộc fail |
deleted_at | timestamp | null | Soft delete timestamp (null nếu chưa archive) |
Timestamps format
ISO 8601 với microseconds .000000Z. Khách hàng parse phải hỗ trợ format này (vd new Date("2026-06-25T14:26:16.000000Z") trong JS hoạt động đúng).
Response 422 (state machine fail):
{ "message": "Chỉ campaign draft/paused mới launch được" }
{ "message": "Chỉ campaign active mới pause được" }
{ "message": "Phải tạm dừng chiến dịch trước khi lưu trữ" }
{ "message": "Chỉ chiến dịch đã lưu trữ mới có thể khôi phục" }
{ "message": "Phải lưu trữ chiến dịch trước khi xoá vĩnh viễn (Lưu trữ -> Xoá)" }Response 200 (PUT /api/autocall/campaigns/{id} — cập nhật metadata): trả campaign object đã update, shape giống Response 201.
Response 200 (DELETE /api/autocall/campaigns/{id}):
{ "data": { "deleted": true } }List / show details / stats
GET /api/autocall/campaigns?status=active
Query params:
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
status | string | Không | Lọc theo trạng thái chiến dịch | draft / active / paused / done / archived |
archived | integer | Không | Lấy danh sách chiến dịch đã lưu trữ. Mặc định ẨN archived | 1 = chỉ archived; bỏ trống = không archived |
q | string | Không | Tìm kiếm gần đúng theo tên chiến dịch (LIKE %keyword%) | Chuỗi text bất kỳ |
Response 200:
{
"data": [
{
"id": 36,
"name": "Chăm sóc khách hàng Q3-2026",
"status": "active",
"script": { "id": 12, "name": "Chăm sóc chuẩn", "voice_id": "evln-vi-nature" },
"total_leads": 487,
"total_calls": 312,
"total_connected": 198,
"total_failed": 89,
"created_at": "2026-06-24T07:30:00Z"
}
]
}Field response:
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
data[].id | integer | ID chiến dịch | Số nguyên dương |
data[].name | string | Tên chiến dịch | Tối đa 255 ký tự |
data[].status | string | Trạng thái lifecycle hiện tại | draft / active / paused / done / archived |
data[].script | object | Script TTS đính kèm (eager-load) | Object con với id, name, voice_id |
data[].script.id | integer | ID script | Số nguyên dương |
data[].script.name | string | Tên script | — |
data[].script.voice_id | string | ID giọng đọc TTS được script dùng | — |
data[].total_leads | integer | Tổng lead đã import vào chiến dịch | >= 0 |
data[].total_calls | integer | Tổng cuộc gọi đã dial | >= 0 |
data[].total_connected | integer | Tổng cuộc gọi connect thành công (B-leg answer) | >= 0 |
data[].total_failed | integer | Tổng cuộc gọi thất bại | >= 0 |
data[].created_at | datetime ISO 8601 | Thời điểm tạo chiến dịch (UTC) | — |
GET /api/autocall/campaigns/{id}
Trả về chi tiết đầy đủ kèm script.dtmfActions và agents.
Path params:
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
id | integer | Có | ID chiến dịch | Số nguyên dương tồn tại trong tài khoản |
Response 200:
{
"data": {
"id": 36,
"name": "Chăm sóc khách hàng Q3-2026",
"description": "Chăm sóc khách hàng kích hoạt lại sau 30-60 ngày",
"status": "active",
"script_id": 12,
"caller_id_group_id": 3,
"max_retries": 3,
"retry_interval_minutes": 60,
"retry_only_when": "no_answer",
"max_concurrent": 10,
"originate_timeout": 40,
"write_to_main_cdr": false,
"start_at": "2026-06-25T08:00:00Z",
"end_at": "2026-06-30T17:30:00Z",
"total_leads": 487,
"total_calls": 312,
"total_connected": 198,
"total_failed": 89,
"tenant_id": 1,
"created_by": 7,
"created_at": "2026-06-24T07:30:00Z",
"updated_at": "2026-06-25T02:34:00Z",
"script": {
"id": 12,
"name": "Chăm sóc chuẩn",
"voice_id": "evln-vi-nature",
"dtmf_actions": [
{ "id": 45, "digit": "1", "label": "Xác nhận", "action_type": "playback_then_hangup", "target_audio_url": "/media/thanks.wav" },
{ "id": 46, "digit": "2", "label": "Chuyển NV", "action_type": "queue", "target_queue_name": "support_queue" }
]
},
"agents": []
}
}Field response:
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
data.id | integer | ID chiến dịch | Số nguyên dương |
data.name | string | Tên chiến dịch | — |
data.description | string | null | Mô tả | — |
data.status | string | Trạng thái lifecycle | draft / active / paused / done / archived |
data.script_id | integer | ID script TTS đang dùng | — |
data.caller_id_group_id | integer | null | ID pool đầu số rotation | — |
data.max_retries | integer | Số lần thử lại tối đa nếu attempt fail | 0-10 |
data.retry_interval_minutes | integer | Khoảng cách giữa các attempt (phút) | 1-43200 |
data.retry_only_when | string | Điều kiện chỉ retry khi result match | no_answer / busy / failed / any |
data.max_concurrent | integer | Số cuộc đồng thời tối đa | 1-200 |
data.originate_timeout | integer | Timeout chờ B-leg nhận máy (giây) | 10-180 |
data.write_to_main_cdr | boolean | Có ghi CDR chung hệ thống không | true / false |
data.start_at | datetime ISO 8601 | null | Thời điểm bắt đầu được dial (UTC) | — |
data.end_at | datetime ISO 8601 | null | Thời điểm dừng dial (UTC) | — |
data.total_leads | integer | Tổng lead đã import | >= 0 |
data.total_calls | integer | Tổng cuộc đã dial | >= 0 |
data.total_connected | integer | Tổng cuộc connect thành công | >= 0 |
data.total_failed | integer | Tổng cuộc fail | >= 0 |
data.tenant_id | integer | ID khách hàng sở hữu | — |
data.created_by | integer | User ID người tạo | — |
data.created_at | datetime ISO 8601 | Thời điểm tạo (UTC) | — |
data.updated_at | datetime ISO 8601 | Thời điểm cập nhật gần nhất (UTC) | — |
data.script | object | Script TTS đính kèm + danh sách phím DTMF | — |
data.script.dtmf_actions[] | array | Cấu hình các phím bấm DTMF của script (xem Scripts) | — |
data.agents[] | array | Danh sách agent gán riêng vào chiến dịch (rỗng nếu không gán) | — |
Response 404 (campaign không tồn tại hoặc thuộc khách hàng khác):
{ "message": "Campaign không tồn tại" }GET /api/autocall/campaigns/{id}/stats
Path params:
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
id | integer | Có | ID chiến dịch | Số nguyên dương |
Response 200:
{
"data": {
"total_leads": 487,
"total_calls": 312,
"total_connected": 198,
"total_failed": 89,
"total_no_answer": 65,
"total_busy": 18,
"connection_rate": 63.5
}
}Field response:
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
data.total_leads | integer | Tổng lead đã import vào chiến dịch | >= 0 |
data.total_calls | integer | Tổng cuộc đã dial | >= 0 |
data.total_connected | integer | Tổng cuộc connect thành công (B-leg answer) | >= 0 |
data.total_failed | integer | Tổng cuộc fail (đếm gộp failed + no_answer + busy) | >= 0 |
data.total_no_answer | integer | Tổng cuộc không bắt máy | >= 0 |
data.total_busy | integer | Tổng cuộc máy bận | >= 0 |
data.connection_rate | number | Tỷ lệ kết nối thành công (total_connected / total_calls * 100) | 0-100, làm tròn 1 chữ số thập phân |
GET /api/autocall/campaigns/{id}/attempts?per_page=50
Danh sách cuộc gọi của campaign (phân trang chuẩn page/per_page, default 50, max 200).
Path params:
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
id | integer | Có | ID chiến dịch | Số nguyên dương |
Query params:
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
page | integer | Không | Trang phân trang (1-indexed) | >= 1, default 1 |
per_page | integer | Không | Số attempt mỗi trang | 1-200, default 50 |
Response 200:
{
"data": [
{
"id": 8721,
"uuid": "093e1024-1234-5678-9abc-def012345678",
"campaign_id": 36,
"lead_id": 78912,
"lead_phone_number": "0912345678",
"attempt_number": 1,
"started_at": "2026-06-24T08:32:00Z",
"answered_at": "2026-06-24T08:32:05Z",
"ended_at": "2026-06-24T08:32:47Z",
"duration_sec": 42,
"result": "connected",
"hangup_cause": "NORMAL_CLEARING",
"dtmf_pressed": "2",
"final_action": "queue:support_queue"
}
],
"current_page": 1,
"last_page": 7,
"per_page": 50,
"total": 312
}Field response:
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
data[].id | integer | ID attempt | Số nguyên dương |
data[].uuid | string | UUID cuộc gọi (dùng để hangup hoặc tra cứu) | UUID v4 |
data[].campaign_id | integer | ID chiến dịch | — |
data[].lead_id | integer | ID lead | — |
data[].lead_phone_number | string | Số điện thoại lead (format E.164 84xxx) | — |
data[].attempt_number | integer | Lần thử thứ mấy | 1-N |
data[].started_at | datetime ISO 8601 | Thời điểm bắt đầu originate (UTC) | — |
data[].answered_at | datetime ISO 8601 | null | Thời điểm B-leg answer (UTC) | null nếu không answer |
data[].ended_at | datetime ISO 8601 | Thời điểm cuộc gọi kết thúc (UTC) | — |
data[].duration_sec | integer | Tổng thời lượng cuộc gọi (giây) | >= 0 |
data[].result | string | Kết quả cuộc gọi | connected / no_answer / busy / failed / abandoned |
data[].hangup_cause | string | Mã hangup từ Zorio PBX | NORMAL_CLEARING / NO_ANSWER / USER_BUSY / ... |
data[].dtmf_pressed | string | null | Phím DTMF khách hàng bấm (nếu có) | 0-9, *, #, null |
data[].final_action | string | null | Action cuối cùng đã thực thi | queue:<name> / playback:<url> / hangup / switch:<id> |
current_page | integer | Trang hiện tại | >= 1 |
last_page | integer | Số trang cuối | >= 1 |
per_page | integer | Số item mỗi trang | 1-200 |
total | integer | Tổng số attempt khớp filter | >= 0 |
Caller-ID Groups (read-only picker)
GET /api/autocall/caller-id-pools
Lấy danh sách pool đầu số để dùng làm caller_id_group_id khi tạo campaign.
Response 200:
{
"data": [
{
"id": 3,
"name": "Pool VNPT 028",
"label": "Pool VNPT 028",
"description": "5 số 028xxxx, rotation Round Robin"
}
]
}Field response:
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
data[].id | integer | ID pool — dùng làm caller_id_group_id khi POST /api/autocall/campaigns | Số nguyên dương |
data[].name | string | Tên pool (định danh kỹ thuật) | Tối đa 255 ký tự |
data[].label | string | Tên hiển thị friendly cho UI | Tối đa 255 ký tự |
data[].description | string | null | Mô tả ngắn về pool (cách rotation, danh sách số...) | — |
Hangup cuộc gọi đang chạy
Hangup cuộc gọi đang chạy cho campaign AutoCall — AutoCall và Telesales chia sẻ endpoint hangup chung.
POST /api/telesales/calls/{uuid}/hangupPath params:
| Field | Type | Required | Mô tả | Giá trị hợp lệ |
|---|---|---|---|---|
uuid | string | Có | UUID cuộc gọi (lấy từ webhook call.answered event hoặc field uuid trong attempt) | UUID v4 thuộc tài khoản hiện tại |
Request mẫu:
curl -X POST https://app.zorio.vn/api/telesales/calls/abc-def-123/hangup \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"Response 200:
{ "success": true }Field response:
| Field | Type | Mô tả | Giá trị hợp lệ |
|---|---|---|---|
success | boolean | Đã gửi lệnh hangup tới Zorio PBX thành công | true |
Error 404 (uuid không tồn tại hoặc không thuộc tài khoản):
{ "error": "Cuộc gọi không tồn tại" }Permission
Cần manage_telesales_campaigns hoặc role admin. Cuộc gọi sẽ kết thúc với hangup cause NORMAL_CLEARING.
