Voice APIBáo cáo chi tiết Autocall

Báo cáo chi tiết Autocall

Nhữ Hào Nam·8/7/2026

API Voice — Chi tiết DTMF

Task: ALO-voice-detail-dtmf  |  Platform: CPaaS 2.0

Authentication chung: Header Authorization (API key) bắt buộc. Header X-Tenant-ID optional — nếu có Kong sẽ inject; nếu không có, backend tự resolve tenantId qua VsaDAO.getTenantByApiKey(). Không dùng JWT (x-access-token).

Base URL: Dev: https://xapi-dev.alohub.vn  |  Prod: https://xapi.alohub.vn


POST /v1/voice/detail-dtmf

Lấy danh sách chi tiết kết quả gọi ra của campaign auto-call, kèm thông tin DTMF khách hàng bấm phím. Hỗ trợ filter đa chiều (campaignId, số điện thoại, dtmf, trạng thái, transactionId) và phân trang server-side.

Authentication: Header Authorization (API key), scope: voice

Lưu ý method: Endpoint dùng POST (không phải GET) vì filter được truyền qua request body. Date range (callStartTime + callEndtime) là bắt buộc — thiếu sẽ nhận 400.

contactStatus logic: contactStatus=13 AND CONNECTED_TIME IS NOT NULL = thành công. Bất kỳ giá trị khác 0 và khác 13 = lọc cuộc gọi thất bại. contactStatus=0 hoặc không truyền = lấy tất cả.

Request Body

{
  "callStartTime": "2026-04-01T00:00:00.000Z",
  "callEndtime":   "2026-04-30T23:59:59.000Z",
  "campaignId":    1042,
  "phoneNumber":   "0901234567",
  "dtmf":          "1",
  "contactStatus": 13,
  "transactionId": "TXN-20260401-001",
  "page":          1,
  "limit":         20
}

Field

Kiểu

Bắt buộc

Mô tả

callStartTime

string (ISO 8601)

Thời điểm bắt đầu khoảng lọc. Format: yyyy-MM-ddTHH:mm:ss.SSSZ. Thiếu → 400.

callEndtime

string (ISO 8601)

Thời điểm kết thúc khoảng lọc. Lưu ý typo: tên field là callEndtime (chữ t thường). Thiếu → 400.

campaignId

number (integer)

Không

Filter theo ID campaign. Bỏ qua = lấy tất cả campaign của tenant.

phoneNumber

string

Không

Filter theo số điện thoại — partial match (LIKE %value%, case-insensitive).

dtmf

string

Không

Filter theo phím DTMF khách bấm — partial match (LIKE %value%, case-insensitive). VD: "1" để lọc các cuộc gọi khách bấm phím 1.

contactStatus

number (long)

Không

13 = chỉ lấy cuộc gọi thành công (CONTACT_STATUS=13 AND CONNECTED_TIME IS NOT NULL). Bất kỳ giá trị ≠ 0 và ≠ 13 = lọc cuộc gọi thất bại. 0 hoặc không truyền = tất cả.

transactionId

string

Không

Filter theo EXTERNAL_ID của campaign customer — dùng để tra theo mã giao dịch bên ngoài.

page

number

Không

Số trang, bắt đầu từ 1. Default: 1.

limit

number

Không

Số bản ghi/trang. Default: 20. Khuyến nghị max 100.

Gotcha: Field backend là callEndtime (typo — chữ t thường, không phải callEndTime). FE phải gửi đúng tên, nếu sai BE sẽ không apply filter endTime và trả về toàn bộ dữ liệu.

Code mẫu

# Không filter — toàn bộ trong khoảng thời gian
curl -X POST "https://xapi.alohub.vn/v1/voice/detail-dtmf" \
  -H "Authorization: sk_live_xxx" \
  -H "X-Tenant-ID: 082018" \
  -H "Content-Type: application/json" \
  -d '{
    "callStartTime": "2026-04-01T00:00:00.000Z",
    "callEndtime":   "2026-04-30T23:59:59.000Z",
    "page": 1,
    "limit": 20
  }'

# Filter theo campaignId + cuộc gọi thành công + phím DTMF=1
curl -X POST "https://xapi.alohub.vn/v1/voice/detail-dtmf" \
  -H "Authorization: sk_live_xxx" \
  -H "X-Tenant-ID: 082018" \
  -H "Content-Type: application/json" \
  -d '{
    "callStartTime": "2026-04-01T00:00:00.000Z",
    "callEndtime":   "2026-04-30T23:59:59.000Z",
    "campaignId": 1042,
    "contactStatus": 13,
    "dtmf": "1",
    "page": 1,
    "limit": 20
  }'

# Filter theo transactionId
curl -X POST "https://xapi.alohub.vn/v1/voice/detail-dtmf" \
  -H "Authorization: sk_live_xxx" \
  -H "X-Tenant-ID: 082018" \
  -H "Content-Type: application/json" \
  -d '{
    "callStartTime": "2026-04-01T00:00:00.000Z",
    "callEndtime":   "2026-04-30T23:59:59.000Z",
    "transactionId": "TXN-20260401-001",
    "page": 1, "limit": 20
  }'
const axios = require('axios');

// Helper: build filter payload
function buildFilter(opts = {}) {
  return {
    callStartTime: opts.callStartTime,  // ISO 8601
    callEndtime:   opts.callEndtime,    // ⚠️ typo: lowercase t
    campaignId:    opts.campaignId,
    phoneNumber:   opts.phoneNumber,
    dtmf:          opts.dtmf,
    contactStatus: opts.contactStatus,  // 13=success, !=0&&!=13=fail, omit=all
    transactionId: opts.transactionId,
    page:          opts.page  ?? 1,
    limit:         opts.limit ?? 20,
  };
}

const response = await axios.post(
  '{{host}}/api/v1/voice/detail-dtmf',
  buildFilter({
    callStartTime: '2026-04-01T00:00:00.000Z',
    callEndtime:   '2026-04-30T23:59:59.000Z',
    campaignId: 1042,
    contactStatus: 13,
    dtmf: '1',
    page: 1, limit: 20,
  }),
  { headers: { 'Authorization': '{{api-key}}', 'X-Tenant-ID': '{{tenant-id}}' } }
);
console.log(response.data);

// Tính tổng trang
const { totalRecord, data } = response.data;
const totalPages = Math.ceil(totalRecord / 20);
import requests

def get_voice_detail_dtmf(
    call_start: str,
    call_end: str,
    campaign_id: int = None,
    phone_number: str = None,
    dtmf: str = None,
    contact_status: int = None,
    transaction_id: str = None,
    page: int = 1,
    limit: int = 20,
):
    payload = {
        "callStartTime": call_start,
        "callEndtime":   call_end,   # ⚠️ typo: lowercase t
        "page": page,
        "limit": limit,
    }
    if campaign_id:     payload["campaignId"]    = campaign_id
    if phone_number:    payload["phoneNumber"]   = phone_number
    if dtmf:            payload["dtmf"]          = dtmf
    if contact_status:  payload["contactStatus"] = contact_status
    if transaction_id:  payload["transactionId"] = transaction_id

    resp = requests.post(
        "{{host}}/api/v1/voice/detail-dtmf",
        json=payload,
        headers={
            "Authorization": "{{api-key}}",
            "X-Tenant-ID":   "{{tenant-id}}",
        }
    )
    return resp.json()

# Chỉ cuộc gọi thành công, bấm phím 1
result = get_voice_detail_dtmf(
    "2026-04-01T00:00:00.000Z",
    "2026-04-30T23:59:59.000Z",
    campaign_id=1042,
    dtmf="1",
    contact_status=13,
)
print(result)

Response 200

{
  "success": "1",
  "error_code": "SUCCESS",
  "error_message": "SUCCESS",
  "totalRecord": 150,
  "data": [
    {
      "phoneNumber":   "0901234567",
      "timeStart":     "01/04/2026 09:30:00",
      "connectTime":   "01/04/2026 09:30:08",
      "duration":      45,
      "contactStatus": "Thanh cong",
      "dtmf":          "1",
      "callId":        "20260401093000-ABCDEFGH-001",
      "campaignCode":  "CP-001",
      "campaignName":  "Chiến dịch tháng 4",
      "campaignType":  "CAMPAIGN_CALL_AUTO",
      "sipCode":       200,
      "url":           "20260401093000-ABCDEFGH-001.mp3"
    }
  ]
}

Response Fields

Field

Kiểu

Mô tả

success

string

"1" = thành công, "0" = lỗi

error_code

string

SUCCESS khi thành công

totalRecord

number

Tổng số bản ghi match filter (dùng để tính phân trang)

data[].phoneNumber

string

Số điện thoại khách hàng

data[].timeStart

string

Thời điểm bắt đầu cuộc gọi — format dd/MM/yyyy HH:mm:ss (UTC+7, không phải ISO 8601)

data[].connectTime

string

Thời điểm agent/hệ thống kết nối — format dd/MM/yyyy HH:mm:ss. Rỗng nếu không kết nối được.

data[].duration

number

Thời lượng đàm thoại (giây) — tính từ CONNECTED_TIME đến END_TIME. 0 nếu không kết nối.

data[].contactStatus

string

"Thanh cong" nếu CONTACT_STATUS=13 và có CONNECTED_TIME. "Khong thanh cong" trong các trường hợp còn lại.

data[].dtmf

string

Phím DTMF khách bấm trong cuộc gọi. Rỗng nếu không bấm phím.

data[].callId

string

Mã định danh cuộc gọi — format YYYYMMDDHHmmss-XXXXXXXX-NNN. Dùng để tra chi tiết.

data[].campaignCode

string

Mã code của campaign (do người tạo đặt, có thể rỗng).

data[].campaignName

string

Tên campaign.

data[].campaignType

string

Loại campaign — VD: CAMPAIGN_CALL_AUTO. Chỉ các campaign type trong cấu hình list_campaign_type_record_able mới có file ghi âm.

data[].sipCode

number

SIP response code của cuộc gọi (200=answered, 486=busy, 408=timeout, ...).

data[].url

string

Tên file ghi âm (format: {callId}.mp3). Rỗng nếu cuộc gọi không thành công hoặc campaign type không hỗ trợ ghi âm.

Lưu ý thời gian: Các field timeStartconnectTime là string format dd/MM/yyyy HH:mm:ss theo UTC+7 — không phải ISO 8601. FE cần parse thủ công.

Lưu ý url ghi âm: Field url chỉ chứa tên file (không phải full URL). FE cần ghép với base URL của CDN/storage để phát audio. Rỗng nếu: cuộc gọi thất bại, hoặc campaignType không nằm trong whitelist ghi âm.

Phân trang: Công thức offset: page=1 → offset=0; page=N → offset = limit*(N-1). Tổng trang = Math.ceil(totalRecord / limit). Kết quả sort theo CALL_ID DESC (cuộc gọi mới nhất lên đầu).

contactStatus — Logic filter

Giá trị truyền vào

Behavior BE

Dữ liệu trả về

0 hoặc không truyền

Không apply filter contactStatus

Tất cả cuộc gọi

13

CONTACT_STATUS=13 AND CONNECTED_TIME IS NOT NULL

"Thanh cong" — đã kết nối và hoàn thành

Bất kỳ giá trị khác (VD: 1, 2, ...)

CONTACT_STATUS≠13 OR (CONTACT_STATUS=13 AND CONNECTED_TIME IS NULL)

"Khong thanh cong" — không kết nối được

Error Codes

HTTP

error_code

Mô tả

FE xử lý

401

UNAUTHORIZED

Thiếu header Authorization hoặc không resolve được tenantId

Redirect nhập lại key

403

INSUFFICIENT_SCOPE

Key không có scope voice

Thông báo liên hệ admin

400

INVALID_INPUT

callStartTime hoặc callEndtime bị thiếu

Hiện lỗi, yêu cầu chọn khoảng thời gian

429

RATE_LIMIT_EXCEEDED

Vượt giới hạn request

Retry sau Retry-After giây

400

FAIL

Lỗi xử lý khác (parse date, DB error, ...)

Toast lỗi chung, log error_message để debug

500

FAIL

Lỗi hệ thống

Toast lỗi chung

Lưu ý error 400 vs 500: BE trả 400 cho cả lỗi INVALID_INPUT lẫn exception không xác định (catch-all trong controller). FE nên kiểm tra error_code để phân biệt: INVALID_INPUT = lỗi input người dùng; FAIL = lỗi hệ thống.

Rate Limit Headers

Header

Mô tả

X-RateLimit-Limit-Tenant

Giới hạn tenant/10s

X-RateLimit-Remaining-Tenant

Còn lại tenant/10s

X-RateLimit-Limit-Route

Giới hạn route/10s

X-RateLimit-Remaining-Route

Còn lại route/10s

Retry-After

Giây cần chờ khi bị 429


Bài viết này có hữu ích không?
Cập nhật: 8/7/2026
để chuyển bài