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ệ
idintegerID chiến dịch nhận leadSố nguyên dương tồn tại trong tài khoản

Body:

json
{
  "leads": [
    {
      "phone": "0912345678",
      "payload": {
        "name": "Nguyễn Văn An",
        "amount": "500000",
        "callback_date": "2026-06-30",
        "loan_id": "LN-12345"
      }
    },
    {
      "phone": "0987654321",
      "payload": {
        "name": "Trần Thị Bích",
        "amount": "1200000",
        "callback_date": "2026-07-05"
      }
    }
  ]
}

Body fields:

FieldTypeRequiredMô tảGiá trị hợp lệ
leadsarrayMảng lead cần push vào chiến dịchTối đa 5000 item/request
leads[].phonestringSố đ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, audio render job dispatch async
  • Nếu campaign đang active -> engine pick lead ngay khi audio sẵn sàng

Validate variables vs template_text

Hệ thống extract 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": "0912345678",
        "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 (chunk client-side nếu nhiều 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ệ
idintegerID 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": "Nguyễn Văn An", "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": ["0912345678", "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": ["0912345678"] }

Body fields:

FieldTypeRequiredMô tảGiá trị hợp lệ
phonesstring[]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] }

Dispatch lại job 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[]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 Nguyễn Văn Nam"đầy đủ
fullnameHọ tên"Nguyễn Văn Nam"đầ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 (admin only)

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_caseVariable key, unique per tài khoản, immutable sau khi tạo
labelstring, max 255Tên hiển thị
data_typeenum (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.