Skip to main content

Open API MiniAI SCRM

Tổng quan​

Open API v1 cho phép hệ thống của bạn (ERP, CRM khác, website, công cụ nội bộ…) đọc và ghi dữ liệu CRM của cửa hàng trên MiniAI SCRM:

NhómLàm được gìTrang
Khách hàngLiệt kê / tra cứu, tạo hoặc cập nhật (upsert), ghi trường tuỳ chỉnhKhách hàng
Lịch sử tương tácĐọc và thêm ghi chú nội bộ vào hồ sơ kháchLịch sử tương tác
Đơn hàngĐọc đơn kèm dòng hàng và địa chỉ giao, tạo hoặc cập nhật theo mã đơn, đổi trạng tháiĐơn hàng
Danh mụcSản phẩm + biến thể (SKU, giá, tồn), mã tỉnh / phường để tạo đơnDanh mục
Trường tuỳ chỉnh & bảng dữ liệuĐọc định nghĩa trường, đọc / ghi bản ghi bảng dữ liệuKhách hàng, Bảng dữ liệu
WebhookNhận sự kiện khách hàng, ghi chú, đơn hàng, bảng dữ liệu ngay khi thay đổiWebhook
MiniAI SCRM là nguồn sự thật

Open API dùng mô hình dữ liệu của MiniAI SCRM — trạng thái đơn, trường, mã khách hàng… đều là của hệ thống này. Hệ thống của bạn đọc danh mục (trạng thái, trường) rồi gửi đúng giá trị đó; không có bảng ánh xạ ngược.


Địa chỉ gọi API​

🔗Production
https://api.socialcrm.vn/api/open/v1
🔗Staging
https://staging1.api.socialcrm.vn/api/open/v1

Mọi request và response đều là JSON (Content-Type: application/json).


Tạo API key​

  1. Đăng nhập MiniAI SCRM bằng tài khoản có quyền Quản lý API key.
  2. Bấm vào tên của bạn ở góc dưới bên trái → Cài đặt, chọn API & Webhook (nhóm Workspace), tab API key, bấm Tạo API key.
  3. Đặt tên (VD: ERP Kho) và chọn quyền của key — chỉ chọn đúng những gì tích hợp cần (sửa lại được sau, không cần tạo key mới).
  4. Sao chép API key và Secret ngay — hai giá trị này chỉ hiển thị một lần. Khi gọi API bạn dùng Mã key (phần <prefix> trong miniai_live_<prefix>_<…>, cũng hiện ở cột Mã key) và Secret để ký request — xem Xác thực.
Mỗi cửa hàng một API key

Mỗi cửa hàng chỉ có 1 API key đang hoạt động.

  • Đổi quyền / đổi tên ⇒ bấm Sửa quyền ở tab API key. Key và secret giữ nguyên, quyền mới có hiệu lực ngay — tích hợp không phải đổi gì.
  • Lộ key ⇒ Thu hồi rồi tạo key mới; tích hợp phải cập nhật key + secret mới.
Bảo mật key
  • Key đứng tên người tạo: dữ liệu ghi qua key được ghi nhận là của người đó, và key chỉ thấy những khách / đơn mà người đó được thấy.
  • Người tạo bị xoá, rời cửa hàng hoặc bị bớt quyền ⇒ key mất quyền tương ứng ngay.
  • Lộ key ⇒ Thu hồi ở tab API key rồi tạo key mới. Không nhúng key vào ứng dụng chạy trên trình duyệt / điện thoại.

Quyền (scope) của key​

Key chỉ được cấp các quyền dưới đây; quyền thực tế = quyền chọn cho key ∩ quyền hiện tại của người tạo.

ScopeMở endpoint
customers_listGET /customers (liệt kê, tra cứu), GET /customer-properties
customers_detailGET /customers/:id, GET /interactions, GET /customers/:id/objects/:objectKey/records
customers_create + customers_updatePOST /customers (upsert cần cả hai)
customers_updatePATCH /customers/:id/fields, POST /customers/:id/interactions, PUT /customers/:id/objects/:objectKey/records
orders_listGET /orders (liệt kê, tra theo orderCode), GET /order-statuses
orders_detailGET /orders/:id
orders_create + orders_updatePUT /orders (upsert cần cả hai)
products_listGET /products
custom_field_listGET /custom-fields
custom_object_record_create / custom_object_record_updateGET /objects, GET /objects/:objectKey/records (một trong hai); PUT /objects/:objectKey/records (cần cả hai)

GET /me và danh mục địa chỉ (GET /address/*) không cần scope. Kiểm tra key và quyền thực tế bằng GET /me:

{
"data": {
"shop": { "id": "5b3c…", "name": "Cửa hàng ABC" },
"key": { "name": "ERP Kho", "scopes": ["customers_list", "customers_detail"] }
}
}

Xác thực​

Mọi request tới Open API phải được ký bằng HMAC. Secret không bao giờ đi trên đường truyền, nội dung request không bị sửa được trên đường đi, và mỗi chữ ký chỉ dùng được một lần.

Không nhận API key trần

Gửi API key trong header x-api-key hoặc Authorization: Bearer … ⇒ bị từ chối với 401 HMAC_REQUIRED. Hãy ký request theo hướng dẫn dưới đây.

Mỗi request gửi kèm 3 header:

HeaderGiá trị
x-key-idMã key — phần <prefix> trong miniai_live_<prefix>_<…> (cột Mã key ở tab API key)
x-timestampUnix time giây; lệch giờ máy chủ tối đa 5 phút
x-signaturehex(HMAC-SHA256(secret, chuỗi_ký))

Chuỗi ký gồm 4 dòng nối bằng \n:

<x-timestamp>
<METHOD viết hoa>
<đường dẫn + query, đúng như gửi — VD /api/open/v1/customers?limit=50>
<hex(SHA256(body thô))> ← body rỗng thì hash chuỗi rỗng
Ký đúng byte gửi đi

Đường dẫn ký bắt đầu từ /api/open/v1 và giữ nguyên query string. Body phải hash đúng chuỗi JSON bạn gửi — đừng JSON.stringify lại lần nữa sau khi ký. Cùng một chữ ký gửi lại lần hai sẽ bị từ chối (HMAC_REPLAYED).

Vì x-timestamp tính theo giây, hai request giống hệt nhau (cùng method, đường dẫn, body) trong cùng một giây có cùng chữ ký ⇒ request sau bị HMAC_REPLAYED. Khi retry hoặc gọi lặp cùng một URL, ký lại ở giây sau (hoặc chờ ≥ 1 giây).

import crypto from "node:crypto";

const BASE = "https://api.socialcrm.vn";
const KEY_ID = process.env.MI9_KEY_ID; // <prefix>
const SECRET = process.env.MI9_SECRET;

async function call(method, path, body) {
const raw = body ? JSON.stringify(body) : "";
const ts = Math.floor(Date.now() / 1000).toString();
const bodyHash = crypto.createHash("sha256").update(raw).digest("hex");
const signature = crypto
.createHmac("sha256", SECRET)
.update([ts, method.toUpperCase(), path, bodyHash].join("\n"))
.digest("hex");

const res = await fetch(BASE + path, {
method,
headers: {
"Content-Type": "application/json",
"x-key-id": KEY_ID,
"x-timestamp": ts,
"x-signature": signature,
},
body: raw || undefined,
});
return res.json();
}

await call("GET", "/api/open/v1/me");

Quy ước chung​

Giới hạn​

Giới hạnGiá trị
Bản ghi mỗi lần đọc (limit)tối đa 50 (mặc định 50)
Khoảng thời gian mỗi lần liệt kê khách hàng / ghi chú / đơntối đa 30 ngày (updatedSince → updatedUntil)
Dòng mỗi lần ghi hàng loạttối đa 50
Request theo key60 / phút
Request theo cửa hàng (cộng mọi key)300 / phút
API key đang hoạt động mỗi cửa hàng1

Mọi response có header hạn mức (cửa sổ cố định theo phút):

HeaderÝ nghĩa
X-RateLimit-LimitHạn mức / phút của key
X-RateLimit-RemainingSố request còn lại trong phút này (đã tính cả trần của cửa hàng)
X-RateLimit-ResetSố giây đến khi sang phút mới
Retry-AfterChỉ khi bị 429: số giây nên chờ

Vượt hạn mức ⇒ 429 API_RATE_LIMITED, params.scope = key hoặc shop. Chờ Retry-After giây rồi gửi lại.

Phân trang keyset (đồng bộ định kỳ)​

Các endpoint liệt kê để đồng bộ (GET /customers, GET /interactions, GET /orders, GET /products) dùng keyset, sắp theo thời gian cập nhật:

Tham sốÝ nghĩa
updatedSinceISO 8601 — lấy bản ghi cập nhật từ thời điểm này
updatedUntilISO 8601 — lấy bản ghi cập nhật trước thời điểm này (không gồm). Không áp cho GET /products
limit1–50, mặc định 50
cursorGiá trị nextCursor của trang trước — giữ nguyên, không tự giải mã
excludeSourceBỏ bản ghi mà lần sửa cuối đến từ nguồn này, VD api:ERP Kho — chống vòng lặp khi bạn vừa ghi vào rồi lại đọc ra

Gọi tiếp với cursor = nextCursor (giữ nguyên updatedSince / updatedUntil) đến khi nextCursor là null.

Cửa sổ 30 ngày (khách hàng, ghi chú, đơn hàng): updatedUntil − updatedSince tối đa 30 ngày, nếu không ⇒ 400 OPEN_API_DATE_RANGE_INVALID.

GửiCửa sổ dùng
Không gửi cả hai30 ngày gần nhất
Chỉ updatedSinceupdatedSince → updatedSince + 30 ngày
Chỉ updatedUntilupdatedUntil − 30 ngày → updatedUntil
Cả haiđúng như gửi

Response trả lại cửa sổ đã dùng trong window:

{
"data": [ /* … */ ],
"nextCursor": "eyJ0IjoiMjAyNi0xMC0wOFQwMzoxMjo0NS4xMjNaIiwiaWQiOiI…",
"window": { "updatedSince": "2026-09-08T00:00:00.000Z", "updatedUntil": "2026-10-08T00:00:00.000Z" }
}
Đồng bộ dữ liệu cũ hơn 30 ngày

Đi lần lượt từng cửa sổ: updatedSince = T, đọc hết trang (theo nextCursor), rồi updatedSince = window.updatedUntil của lần trước, lặp đến hiện tại. Đồng bộ định kỳ: lần sau đặt updatedSince = window.updatedUntil (hoặc thời điểm bắt đầu) của lần trước.

Nguồn ghi (source)​

Mọi thay đổi qua key được gắn nguồn api:<tên key> (cắt 50 ký tự) — hiện trên giao diện (chip "API · tên key") và trả về ở lastModifiedSource / source.

Trường tuỳ chỉnh​

Trường tuỳ chỉnh vào / ra theo mã trường (fieldKey): "customFields": { "ma_erp": "KH-001", "hang_thanh_vien": "vang" }. Lấy danh sách mã, kiểu và lựa chọn hợp lệ ở GET /custom-fields. Gửi null hoặc "" ⇒ xoá giá trị.

Kiểu trườngGiá trị gửi
text, selectchuỗi (select: đúng value của lựa chọn)
number, decimalsố
booleantrue / false
date"YYYY-MM-DD"
multi_select, checkbox_groupmảng chuỗi

Mã tự đặt​

customerCode (khách hàng) và orderCode (đơn hàng) bạn tự đặt được — phải duy nhất trong cửa hàng. Không gửi thì hệ thống tự sinh (Mi9XXXXXX / ORD-YYYYMMDD-XXXXXX) và trả về trong response để bạn lưu lại.

Ghi hàng loạt​

Endpoint ghi nhiều dòng (PUT /orders, PUT /objects/:objectKey/records) nhận tối đa 50 dòng; dòng lỗi không làm hỏng dòng khác. Response luôn 200:

{
"results": [
{ "index": 0, "status": "created", "id": "9b1e…", "orderCode": "DH-1001" },
{ "index": 1, "status": "error", "error": { "code": "OPEN_API_ITEM_NOT_FOUND", "message": "Không tìm thấy sản phẩm SKU-X.", "params": { "sku": "SKU-X" } } }
]
}

Lỗi​

Lỗi trả HTTP status kèm code cố định (dùng code để xử lý, message chỉ để đọc):

{ "statusCode": 409, "code": "ORDER_CODE_DUPLICATE", "message": "Mã đơn DH-1001 đã tồn tại", "params": { "orderCode": "DH-1001" } }
CodeHTTPKhi nào
API_KEY_INVALID401Mã key sai, key đã thu hồi, hoặc người tạo không còn hiệu lực
HMAC_REQUIRED401Gửi API key trần (x-api-key / Bearer) thay vì ký request
HMAC_SIGNATURE_INVALID401Chữ ký sai
HMAC_TIMESTAMP_EXPIRED401x-timestamp thiếu, sai định dạng hoặc lệch quá 5 phút
HMAC_REPLAYED401Chữ ký đã dùng
API_SCOPE_FORBIDDEN403Key không có scope cho endpoint
API_KEY_LIMIT_EXCEEDED409Tạo key khi cửa hàng đã có key đang hoạt động (chỉ gặp trên giao diện)
API_RATE_LIMITED429Vượt 60 request / phút / key hoặc 300 / phút / cửa hàng (params.scope, params.retryAfter)
OPEN_API_DATE_RANGE_INVALID400updatedSince ≥ updatedUntil hoặc cách nhau quá 30 ngày
OPEN_API_CURSOR_INVALID400cursor không phải giá trị server trả
OPEN_API_FIELD_UNKNOWN400Mã trường không tồn tại (params.fields)
OPEN_API_FIELD_VALUE_INVALID400Giá trị sai kiểu trường
OPEN_API_MATCH_FIELD_INVALID400Trường dùng để match không tồn tại / không phải trường văn bản "duy nhất"
OPEN_API_MATCH_AMBIGUOUS409Tiêu chí match khớp nhiều hơn một bản ghi
OPEN_API_CUSTOMER_NOT_FOUND404Khách không tồn tại hoặc ngoài phạm vi của key
OPEN_API_ORDER_NOT_FOUND404Đơn không tồn tại hoặc ngoài phạm vi của key
OPEN_API_ORDER_INVALID400Đơn mới thiếu khách hoặc dòng hàng
OPEN_API_ITEM_NOT_FOUND400SKU / biến thể không có trong danh mục
SCRM_CUSTOMER_FIELD_DUPLICATE409customerCode / SĐT / email đã thuộc khách khác
CUSTOM_FIELD_VALUE_DUPLICATE409Trường "duy nhất" đã có giá trị này ở bản ghi khác
ORDER_CODE_DUPLICATE409orderCode đã thuộc đơn khác (kể cả đơn đã xoá)

Lỗi kiểm tra dữ liệu đầu vào (thiếu trường bắt buộc, sai kiểu) trả 400 với message là danh sách lỗi, không có code.