ODS
API ReferenceBắt đầu

Quy ước chung

Quy chuẩn kỹ thuật về Base URL, phương thức HTTP, quy ước casing, phân trang, định dạng ngày tháng và xử lý lỗi.

Trang này tổng hợp các quy chuẩn kỹ thuật bắt buộc khi làm việc với API của ODS AI Contact Center, giúp kỹ sư tích hợp xây dựng ứng dụng đồng bộ, chính xác và tránh các lỗi phổ biến.


1. Base URL & Môi trường

Hệ thống phân tách domain chính thức theo từng cụm dịch vụ:

Cụm dịch vụBase URL chính thứcPhạm vi áp dụng
Tổng đài PBXhttps://api.ods.vnMáy nhánh, Click-to-Call V1/V2, Lịch sử cuộc gọi, Danh bạ, Luật khách quen.
Dịch vụ Autocallhttps://apiai.cloudfone.vnChiến dịch gọi tự động, File âm thanh TTS, Nhóm danh bạ Autocall.

Khả năng tương thích ngược: Hệ thống ODS vẫn duy trì hỗ trợ các domain cũ (https://api.cloudfone.vn, https://api.matbao.one) cho các đối tác đã kết nối trước đây. Tuy nhiên, mọi dự án mới và phiên bản nâng cấp bắt buộc sử dụng https://api.ods.vn.


2. Giao thức, Phương thức & Headers

  • Phương thức HTTP: 100% endpoint của ODS đều sử dụng POST, kể cả các tác vụ tra cứu danh sách (GetCallHistory, GetAudioList) hoặc tác vụ xóa (DeleteExtension, DeleteCampaign, DeleteAudio). Không sử dụng GET, PUT hay DELETE.
  • Định dạng dữ liệu: Toàn bộ tham số gửi lên được đóng gói trong JSON Body.
  • Request Headers bắt buộc:
POST /api/CloudFone/AutoCall HTTP/1.1
Host: api.ods.vn
Content-Type: application/json; charset=utf-8

3. Quy ước đặt tên trường (Casing) & Kiểu dữ liệu

Casing không đồng nhất giữa các nhóm API

Do hệ thống được phát triển qua nhiều giai đoạn, quy ước đặt tên thuộc tính JSON có sự khác biệt giữa các nhóm endpoint:

  • Request Parameters: Hầu hết sử dụng PascalCase (ví dụ: ServiceName, ServiceCode, AuthUser, AuthKey, PhoneNumber, DateStart, DateEnd, PageIndex, PageSize). Tuy nhiên, một số endpoint sử dụng chữ thường (ví dụ: password, name trong API AddExtension).
  • Response Properties:
    • Nhóm khởi tạo/CRUD: Thường trả về result, message, keySearch, idPhoneBook.
    • Mảng dữ liệu lịch sử cuộc gọi (GetCallHistory): Thuộc tính bản ghi bên trong dùng camelCase hoặc không dấu tiếng Việt (ví dụ: stt, key, ngayGoi, soGoiDen, dauSo, soNhan, trangThai, tongThoiGianGoi, thoiGianThucGoi, linkFile, typecall).

Bộ giải mã JSON trên hệ thống ODS có phân biệt chữ hoa/chữ thường. Lập trình viên bắt buộc đối chiếu bảng tham số chi tiết của từng endpoint cụ thể, không tự động chuyển đổi định dạng camelCase hay snake_case.


4. Cấu trúc phản hồi (Response Envelope)

Hệ thống không dùng chung một schema cho toàn bộ các API mà phân thành 2 dạng envelope chính có bằng chứng trong tài liệu nguồn:

Dạng 1: Phản hồi thực thi tác vụ (Action Response)

Áp dụng cho Click-to-Call, thêm/xóa máy nhánh, khởi tạo chiến dịch:

{
  "result": "success",
  "message": "GỌI TỰ ĐỘNG THÀNH CÔNG",
  "keySearch": "1b38fbda-4b7e-41a2-9bc3-7a28e9324a1b"
}

Hoặc trả về ID bản ghi mới tạo:

{
  "result": "success",
  "message": "Thành công!",
  "idPhoneBook": 12
}

Dạng 2: Phản hồi danh sách phân trang (List Response)

Áp dụng cho tra cứu lịch sử cuộc gọi (GetCallHistory, GetCallLogs) và danh sách nhóm liên hệ:

{
  "result": "success",
  "message": "",
  "total": 415,
  "data": [
    {
      "stt": 1,
      "key": "1610039549.118173-9100(Nhóm)",
      "ngayGoi": "2026-10-15T08:30:00",
      "soGoiDen": "0900000000",
      "dauSo": "02873007788",
      "soNhan": "101",
      "trangThai": "ANSWERED",
      "tongThoiGianGoi": "02:15",
      "thoiGianThucGoi": "135",
      "linkFile": "https://api.ods.vn/recordings/call-123.mp3"
    }
  ]
}
  • result: Trạng thái xử lý ("success" hoặc "failed").
  • total: Tổng số bản ghi thỏa điều kiện lọc trong database (dùng để tính toán tổng số trang phía client).
  • data: Mảng danh sách bản ghi thuộc trang hiện tại.

5. Quy tắc phân trang (Pagination)

Áp dụng cho các endpoint tra cứu dữ liệu (GetCallHistory, GetCallLogs, danh sách danh bạ):

Tham sốKiểuBắt buộcMặc địnhMô tả & Lưu ý
PageIndexintegerCó1Số thứ tự trang cần lấy. Mặc định tính từ 1 (1-indexed).
PageSizeintegerCó—Số lượng bản ghi trên một trang. Tối đa là 200 theo quy định của tổng đài.

Điểm kỹ thuật chưa chốt: Tài liệu nguồn không định nghĩa hành vi khi PageSize > 200 (hệ thống có thể tự giới hạn về 200 hoặc trả về lỗi). Khuyến nghị luôn đặt PageSize trong khoảng 10 đến 200.


6. Định dạng ngày tháng & Bẫy biên thời gian (DateEnd)

Trong các API lọc theo thời gian (DateStart, DateEnd):

  • Định dạng nhận diện: Chuỗi YYYY-MM-DD hoặc YYYY-MM-DD HH:mm:ss (ví dụ: "2026-10-15 08:30:00").
  • Múi giờ (Timezone): Mặc định tính theo giờ Việt Nam ICT (UTC+7). Tránh gửi chuỗi ISO có hậu tố Z (UTC) nếu chưa chuyển đổi giờ địa phương.

Cảnh báo bẫy biên thời gian (DateEnd):
Theo tài liệu nguồn ODS: Nếu chỉ truyền ngày dạng YYYY-MM-DD (ví dụ "2026-10-15"), hệ thống sẽ tự động gán mốc thời gian là 00:00:00.

Do đó, nếu bạn truyền:

  • DateStart: "2026-10-15" ➔ hiểu là 2026-10-15 00:00:00
  • DateEnd: "2026-10-15" ➔ hiểu là 2026-10-15 00:00:00

Kết quả: Hệ thống sẽ không trả về bất kỳ cuộc gọi nào phát sinh trong ngày 15/10!
Giải pháp chuẩn: Khi lọc trong ngày hoặc khoảng ngày, hãy luôn truyền đủ giờ phút giây:
DateStart: "2026-10-15 00:00:00" và DateEnd: "2026-10-15 23:59:59".


7. Phân biệt KeySearch với Idempotency Key

Trong các API khởi tạo cuộc gọi (Click-to-Call V1, Click-to-Call V2), tham số KeySearch có vai trò quan trọng nhưng thường bị hiểu nhầm:

Đặc tínhKeySearch của ODSIdempotency Key (Khóa chống trùng lặp)
Bản chấtMã đối chiếu tham chiếu (Correlation ID) do CRM tự đặt hoặc tổng đài tự sinh ngẫu nhiên.Khóa giao dịch kỹ thuật ngăn chặn thực thi trùng lặp khi request bị gửi lại nhiều lần (Retry).
Mục đíchDùng để tìm kiếm bản ghi cuộc gọi trong GetCallLogs hoặc đối chiếu khi nhận Webhook Popup.Đảm bảo 1 thao tác duy nhất chỉ được trừ tiền hoặc tạo 1 cuộc gọi dù mạng chập chờn.
Hành vi tổng đàiKHÔNG có cơ chế chống trùng lặp: Nếu CRM gửi 2 request với cùng một chuỗi KeySearch, tổng đài vẫn tạo ra 2 cuộc gọi riêng biệt.Nếu gửi lại cùng key, hệ thống trả về kết quả cũ mà không thực hiện gọi lại.

Khuyến nghị triển khai: Lập trình viên phải tự quản lý cơ chế chống bấm đúp (Debounce / Throttle) tại giao diện CRM/ERP để tránh tình trạng người dùng bấm liên tục tạo ra nhiều cuộc gọi đồng thời tới cùng một khách hàng.


8. Đối chiếu lỗi HTTP với lỗi nghiệp vụ

Khi tích hợp với ODS, lập trình viên cần xử lý 2 tầng lỗi riêng biệt:

Tầng 1: Lỗi giao thức HTTP (Network & Transport)

  • 400 Bad Request: Payload JSON bị lỗi cú pháp hoặc thiếu tham số bắt buộc.
  • 404 Not Found: Sai đường dẫn URL endpoint.
  • 500 / 502 / 504: Máy chủ ODS gặp sự cố hoặc gateway timeout.

Tầng 2: Lỗi nghiệp vụ (Business Logic Failure)

Đặc thù nghiệp vụ: Phần lớn các lỗi nghiệp vụ (sai mật khẩu máy nhánh, sai AuthKey, máy nhánh không online, số điện thoại không hợp lệ) vẫn trả về HTTP 200 OK.

Lập trình viên bắt buộc phải kiểm tra thuộc tính result bên trong thân JSON phản hồi:

Ví dụ phản hồi lỗi nghiệp vụ (HTTP 200 OK)
{
  "result": "failed",
  "message": "Sai thông tin AuthUser hoặc AuthKey!"
}
Mẫu xử lý kết quả đúng chuẩn (JavaScript)
// Node.js 22+ (Backend / Server-side)
async function createClickToCall({ ext, phoneNumber, phoneName = '' }) {
  const credentials = {
    ServiceName: process.env.ODS_SERVICE_NAME,
    AuthUser: process.env.ODS_AUTH_USER,
    AuthKey: process.env.ODS_AUTH_KEY,
  };

  const missing = Object.entries(credentials)
    .filter(([, value]) => !value)
    .map(([name]) => name);

  if (missing.length > 0) {
    throw new Error(`Thiếu cấu hình bắt buộc: ${missing.join(', ')}`);
  }
  if (!ext || !phoneNumber) {
    throw new Error('ext và phoneNumber là bắt buộc');
  }

  const response = await fetch('https://api.ods.vn/api/CloudFone/AutoCall', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      ...credentials,
      Ext: ext,
      PhoneNumber: phoneNumber,
      PhoneName: phoneName,
      Prefix: '0',
      KeySearch: crypto.randomUUID(),
    }),
  });

  if (!response.ok) {
    throw new Error(`Lỗi HTTP: ${response.status} ${response.statusText}`);
  }

  const data = await response.json();
  if (data.result !== 'success') {
    throw new Error(`Tổng đài từ chối: ${data.message ?? 'Không rõ nguyên nhân'}`);
  }

  return data;
}

// Không tự động retry hàm tạo cuộc gọi khi chưa xác định kết quả request trước.

Bài viết tiếp theo