Skip to content

Leads + Variables ​

Quản lý lead (import bulk, list, blacklist/DNC) và biến TTS do khách hàng tự định nghĩa dùng trong template template_text.

Push lead vào chiến dịch (bulk) ​

http
POST /api/autocall/campaigns/{id}/leads/import

Path params:

FieldTypeRequiredMô tảGiá trị hợp lệ
idintegerCóID chiến dịch nhận leadSố nguyên dương tồn tại trong tài khoản

Body:

json
{
  "leads": [
    {
      "phone": "0900000002",
      "payload": {
        "name": "Alice Example",
        "amount": "500000",
        "callback_date": "2026-06-30",
        "loan_id": "LN-12345"
      }
    },
    {
      "phone": "0987654321",
      "payload": {
        "name": "Bob Example",
        "amount": "1200000",
        "callback_date": "2026-07-05"
      }
    }
  ]
}

Body fields:

FieldTypeRequiredMô tảGiá trị hợp lệ
leadsarrayCóMảng lead cần push vào chiến dịchTối đa 5000 item/request
leads[].phonestringCóSố điện thoại lead, format Việt Nam0xxx / 84xxx / +84xxx (auto-normalize). Legacy alias phone_number được chấp nhận
leads[].payloadobjectCó (nếu template có biến)JSON object chứa biến TTS theo variable_code. Legacy alias variables được chấp nhậnKey snake_case khớp variable_code đã định nghĩa

Field phone: bắt buộc, format Việt Nam. DB nội bộ lưu format E.164 84xxx (cho dedup + DNC matching) nhưng API GET trả về format gốc 0xxx để khớp với input client. Legacy alias phone_number vẫn được chấp nhận trong body request cho backward compat — KHUYẾN NGHỊ dùng phone cho integration mới.

Ví dụ

Client gửi "phone": "0987654321" -> response GET trả "phone_number": "0987654321". Hệ thống lưu format E.164 84987654321 để dedup với DNC. Search ?phone=0987 được hệ thống tự normalize.

Field payload: JSON object chứa biến TTS theo variable_code đã định nghĩa trong pool (xem Variables). Key phải match variable_code (lowercase, snake_case). Legacy alias variables được chấp nhận.

Behaviour:

  • Lead trùng phone trong cùng campaign -> skip -> đếm vào skipped_duplicate
  • Lead nằm trong DNC -> skip -> đếm vào skipped_dnc
  • Phone format không hợp lệ -> skip -> đếm vào skipped_invalid, lý do invalid_phone_format
  • Payload thiếu biến required trong script.template_text placeholder -> skip -> đếm vào skipped_invalid, lý do missing_variables
  • Lead inserted với status=pending, Hệ thống tạo tác vụ render audio chạy bất đồng bộ.
  • Nếu campaign đang active -> engine pick lead ngay khi audio sẵn sàng

Validate variables vs template_text

Hệ thống phân tích các placeholder từ script.template_text của campaign -> mọi key này PHẢI có trong payload của lead. Thiếu key nào -> reject lead, log vào errors[] (max 20 sample). Đảm bảo audio render thành công cho mọi lead.

Response 200 (case có lead thiếu biến):

json
{
  "data": {
    "inserted": 1,
    "skipped_invalid": 2,
    "skipped_duplicate": 0,
    "skipped_dnc": 0,
    "errors": [
      {
        "index": 0,
        "phone": "0900000002",
        "reason": "missing_variables",
        "missing": ["tong_can_thanh_toan", "ngay_den_han"],
        "hint": "Script template_text yêu cầu các biến: {{tong_can_thanh_toan}}, {{ngay_den_han}} — payload đang thiếu các biến này."
      },
      { "index": 487, "phone": "0912abc", "reason": "invalid_phone_format" }
    ]
  }
}

Field response:

FieldTypeMô tảGiá trị hợp lệ
data.insertedintegerSố lead insert thành công>= 0
data.skipped_invalidintegerSố lead bị skip do invalid (sai phone format / thiếu biến)>= 0
data.skipped_duplicateintegerSố lead bị skip do trùng phone trong cùng chiến dịch>= 0
data.skipped_dncintegerSố lead bị skip do nằm trong DNC>= 0
data.errors[]arrayDanh sách lỗi mẫu (tối đa 20 item)—
data.errors[].indexintegerVị trí lead trong array leads của request0-indexed
data.errors[].phonestringSố điện thoại lead lỗi—
data.errors[].reasonstringLý do skipmissing_variables / invalid_phone_format / duplicate / dnc_blocked
data.errors[].missingstring[]Danh sách biến thiếu (chỉ với reason=missing_variables)—
data.errors[].hintstringGợi ý fix lỗi—

Bulk limit

Lead push bulk tối đa 5000 lead/request (chia thành nhiều request nếu vượt giới hạn). Rate limit lead import: 5 batch/phút/token.

List leads ​

GET /api/autocall/campaigns/{id}/leads?status=pending&page=1 ​

Path params:

FieldTypeRequiredMô tảGiá trị hợp lệ
idintegerCóID chiến dịchSố nguyên dương

Query params:

FieldTypeRequiredMô tảGiá trị hợp lệ
statusstring | string[]KhôngLọc theo trạng thái lead (single hoặc dùng status[]=a&status[]=b)pending / dialing / connected / completed / failed / abandoned / blacklisted
phonestringKhôngTìm gần đúng theo số điện thoại (auto-normalize 84xxx)Chuỗi số
pageintegerKhôngTrang phân trang>= 1, default 1
per_pageintegerKhôngSố lead mỗi trang1-200, default 50

Response 200:

json
{
  "data": [
    {
      "id": 78912,
      "campaign_id": 36,
      "phone_number": "84912345678",
      "payload": {"name": "Alice Example", "amount": "500000"},
      "status": "completed",
      "attempt_count": 1,
      "last_attempt_at": "2026-06-24T08:32:00Z",
      "last_hangup_cause": "NORMAL_CLEARING",
      "next_retry_at": null,
      "final_audio_path": "/audio/render/lead_78912.wav",
      "created_at": "2026-06-24T07:35:00Z"
    }
  ],
  "current_page": 1,
  "last_page": 10,
  "per_page": 50,
  "total": 487
}

Field response:

FieldTypeMô tảGiá trị hợp lệ
data[].idintegerID leadSố nguyên dương
data[].campaign_idintegerID chiến dịch—
data[].phone_numberstringSố điện thoại format E.164 (84xxx)—
data[].payloadobjectBiến TTS đã nhận khi importKey snake_case
data[].statusstringTrạng thái lifecycle của leadpending / dialing / connected / completed / failed / abandoned / blacklisted
data[].attempt_countintegerSố lần đã gọi>= 0
data[].last_attempt_atdatetime ISO 8601 | nullThời điểm attempt gần nhất (UTC)—
data[].last_hangup_causestring | nullHangup cause của attempt cuốiNORMAL_CLEARING / NO_ANSWER / ...
data[].next_retry_atdatetime ISO 8601 | nullThời điểm lên lịch retry tiếp theo (UTC)null nếu không retry tiếp
data[].final_audio_pathstring | nullĐường dẫn file wav render xong (server local)—
data[].created_atdatetime ISO 8601Thời điểm import lead (UTC)—
current_pageintegerTrang hiện tại>= 1
last_pageintegerSố trang cuối>= 1
per_pageintegerSố item mỗi trang1-200
totalintegerTổng số lead khớp filter>= 0

GET /api/autocall/leads?campaign_id=36&phone=0912&status[]=completed&status[]=failed ​

Liệt kê lead cross-campaign. Response shape giống endpoint trên (data[] + pagination).

Query params:

FieldTypeRequiredMô tảGiá trị hợp lệ
campaign_idintegerKhôngLọc theo chiến dịchID chiến dịch tồn tại
phonestringKhôngTìm gần đúng theo số điện thoại—
status[]string[]KhôngLọc nhiều trạng tháipending / dialing / connected / completed / failed / abandoned / blacklisted
fromdatetime ISO 8601KhôngLọc theo created_at >= fromYYYY-MM-DD hoặc ISO 8601
todatetime ISO 8601KhôngLọc theo created_at <= toYYYY-MM-DD hoặc ISO 8601
per_pageintegerKhôngSố lead mỗi trang1-200, default 50

Bulk action ​

Blacklist ​

http
POST /api/autocall/leads/blacklist

Body:

json
{
  "phones": ["0900000002", "0987654321"],
  "lead_ids": [78912, 78913],
  "reason": "Khách hàng yêu cầu không gọi",
  "source": "customer_request"
}

Body fields:

FieldTypeRequiredMô tảGiá trị hợp lệ
phonesstring[]Không (1 trong 2)Danh sách số điện thoại cần blacklistFormat Việt Nam (0xxx / 84xxx)
lead_idsinteger[]Không (1 trong 2)Danh sách lead ID — phone tương ứng được auto-resolve và merge vào phonesID lead tồn tại
reasonstringKhôngLý do blacklist (lưu vào DNC log)Tối đa 500 ký tự
sourcestringKhôngNguồn gốc blacklistmanual / customer_request / duplicate / complaint / other (default manual)

Response 200:

json
{ "data": { "blacklisted": 2 } }

Field response:

FieldTypeMô tảGiá trị hợp lệ
data.blacklistedintegerSố phone đã insert vào DNC (đã dedup)>= 0

Unblacklist ​

http
POST /api/autocall/leads/unblacklist
{ "phones": ["0900000002"] }

Body fields:

FieldTypeRequiredMô tảGiá trị hợp lệ
phonesstring[]CóDanh sách phone cần xoá khỏi DNCFormat Việt Nam

Response 200: {"data": {"unblacklisted": 1}}

Field response:

FieldTypeMô tảGiá trị hợp lệ
data.unblacklistedintegerSố phone đã xoá khỏi DNC>= 0

Bulk re-render audio ​

http
POST /api/autocall/leads/bulk-rerender
{ "lead_ids": [78912, 78913] }

Tạo lại tác vụ render audio cho các lead (vd khi script đổi -> cần render lại).

Body fields:

FieldTypeRequiredMô tảGiá trị hợp lệ
lead_idsinteger[]CóDanh sách lead ID cần render lại audioID lead tồn tại trong tài khoản

Response 200: {"data": {"dispatched": 2}}

Field response:

FieldTypeMô tảGiá trị hợp lệ
data.dispatchedintegerSố job render đã dispatch vào queue>= 0

Do-Not-Call list ​

GET /api/autocall/dnc?phone=0912&source=manual ​

Query params:

FieldTypeRequiredMô tảGiá trị hợp lệ
phonestringKhôngTìm gần đúng theo số điện thoại—
sourcestringKhôngLọc theo nguồn blacklistmanual / customer_request / duplicate / complaint / other
pageintegerKhôngTrang phân trang>= 1, default 1
per_pageintegerKhôngSố item mỗi trang1-200, default 50

Response 200:

json
{
  "data": [
    {
      "id": 543,
      "phone_number": "84912345678",
      "source": "customer_request",
      "reason": "Khách yêu cầu",
      "blocked_by": 7,
      "created_at": "2026-06-24T07:40:00Z"
    }
  ],
  "current_page": 1,
  "last_page": 12,
  "per_page": 50,
  "total": 567
}

Field response:

FieldTypeMô tảGiá trị hợp lệ
data[].idintegerID record DNCSố nguyên dương
data[].phone_numberstringSố điện thoại format E.164 (84xxx)—
data[].sourcestringNguồn blacklistmanual / customer_request / duplicate / complaint / other
data[].reasonstring | nullLý do blacklist—
data[].blocked_byinteger | nullUser ID người thực hiện chặn—
data[].created_atdatetime ISO 8601Thời điểm blacklist (UTC)—
current_pageintegerTrang hiện tại>= 1
last_pageintegerSố trang cuối>= 1
per_pageintegerSố item mỗi trang1-200
totalintegerTổng số record khớp filter>= 0

DNC global per tài khoản

Số trong DNC sẽ bị skip khỏi MỌI campaign của khách hàng đó.

Variables (biến TTS do khách hàng tự định nghĩa) ​

Biến do khách hàng định nghĩa dùng trong template_text. Khi render, hệ thống resolve theo data_type -> format đúng (số tiền -> đọc thành chữ, ngày -> "ngày 15 tháng 7"...).

List variables ​

http
GET /api/autocall/variables

Response 200:

json
{
  "data": [
    {
      "id": 1,
      "code": "name",
      "label": "Tên khách hàng",
      "data_type": "name",
      "description": "Tên gọi của khách hàng"
    },
    {
      "id": 2,
      "code": "salutation_name",
      "label": "Xưng hô + Tên",
      "data_type": "salutation_name",
      "description": "VD: anh An, chị Bích"
    },
    {
      "id": 3,
      "code": "amount",
      "label": "Số tiền",
      "data_type": "money",
      "description": "Số tiền VND, sẽ đọc thành chữ"
    }
  ]
}

Field response:

FieldTypeMô tảGiá trị hợp lệ
data[].idintegerID variableSố nguyên dương
data[].codestringMã biến (dùng làm key trong payload khi import lead, dùng làm trong template_text)snake_case, max 64 ký tự, unique per tài khoản, immutable
data[].labelstringTên hiển thị tiếng ViệtTối đa 255 ký tự
data[].data_typestringLoại dữ liệu để TTS đọc đúng formatname / salutation_name / salutation_fullname / fullname / date / time / number / money
data[].descriptionstring | nullMô tả cho người dùngTối đa 2000 ký tự

Data types hỗ trợ:

data_typeMô tảVí dụ inputOutput đọc
nameTên rút gọn"Nam""Nam"
salutation_nameXưng hô + Tên"anh Nam""anh Nam"
salutation_fullnameXưng hô + Họ tên"anh Sample User"đầy đủ
fullnameHọ tên"Sample User"đầy đủ
dateNgày Y-m-d"2026-06-30""ngày ba mươi tháng sáu năm hai nghìn không trăm hai mươi sáu"
timeGiờ H:i"14:30""mười bốn giờ ba mươi phút"
numberSố nguyên"1234""một nghìn hai trăm ba mươi tư"
moneySố tiền VND"500000""năm trăm nghìn đồng"

CRUD variables (chỉ quản trị viên) ​

http
POST   /api/autocall/variables       (admin tier)
PUT    /api/autocall/variables/{id}
DELETE /api/autocall/variables/{id}

Body create/update:

json
{
  "code": "loan_id",
  "label": "Mã hợp đồng",
  "data_type": "name",
  "description": "Format LN-xxxxx",
  "example_value": "LN-12345",
  "sort_order": 100,
  "is_active": true
}
FieldRequiredTypeDefaultMô tả
codeCó (create)string, max 64, snake_case—Variable key, unique per tài khoản, immutable sau khi tạo
labelCóstring, max 255—Tên hiển thị
data_typeCóenum (8 type, xem bảng phía trên)—Loại dữ liệu để TTS đọc đúng format
descriptionKhôngstring, max 2000nullMô tả
example_valueKhôngstringnullGiá trị mẫu dùng cho TTS preview
sort_orderKhônginteger999Thứ tự hiển thị trong UI picker
is_activeKhôngbooleantrueTắt variable (KHÔNG xoá) — script vẫn render nhưng UI ẩn

is_builtin: true (read-only) đánh dấu các variable hệ thống cung cấp sẵn — KHÔNG cho phép xoá hoặc đổi code.

POST /api/autocall/variables response 201:

json
{
  "data": {
    "id": 15,
    "code": "loan_id",
    "label": "Mã hợp đồng",
    "data_type": "name",
    "description": "Format LN-xxxxx",
    "example_value": "LN-12345",
    "sort_order": 100,
    "is_active": true,
    "is_builtin": false,
    "tenant_id": 1,
    "created_at": "2026-06-25T03:00:00Z",
    "updated_at": "2026-06-25T03:00:00Z"
  }
}

PUT /api/autocall/variables/{id} response 200: shape giống POST response (object đã update). Chỉ truyền field cần đổi (partial update). Field code immutable — hệ thống bỏ qua nếu gửi.

DELETE /api/autocall/variables/{id} response 200:

json
{ "data": { "deleted": true } }

Response 422 (variable hệ thống):

json
{ "message": "Không thể xoá biến hệ thống (is_builtin=true)" }

Response 422 (validation create — vd code trùng):

json
{
  "message": "The given data was invalid.",
  "errors": {
    "code": ["The code has already been taken."]
  }
}

Cấp phép theo điều khoản sử dụng của Zorio.