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óm | Làm được gì | Trang |
|---|---|---|
| Khách hàng | Liệt kê / tra cứu, tạo hoặc cập nhật (upsert), ghi trường tuỳ chỉnh | Khách hàng |
| Lịch sử tương tác | Đọc và thêm ghi chú nội bộ vào hồ sơ khách | Lị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ục | Sản phẩm + biến thể (SKU, giá, tồn), mã tỉnh / phường để tạo đơn | Danh 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ệu | Khách hàng, Bảng dữ liệu |
| Webhook | Nhận sự kiện khách hàng, ghi chú, đơn hàng, bảng dữ liệu ngay khi thay đổi | Webhook |
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
https://api.socialcrm.vn/api/open/v1
https://staging1.api.socialcrm.vn/api/open/v1
Mọi request và response đều là JSON (Content-Type: application/json).
Tạo API key
- Đăng nhập MiniAI SCRM bằng tài khoản có quyền Quản lý API key.
- 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.
- Đặ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). - 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>trongminiai_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 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.
- 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.
| Scope | Mở endpoint |
|---|---|
customers_list | GET /customers (liệt kê, tra cứu), GET /customer-properties |
customers_detail | GET /customers/:id, GET /interactions, GET /customers/:id/objects/:objectKey/records |
customers_create + customers_update | POST /customers (upsert cần cả hai) |
customers_update | PATCH /customers/:id/fields, POST /customers/:id/interactions, PUT /customers/:id/objects/:objectKey/records |
orders_list | GET /orders (liệt kê, tra theo orderCode), GET /order-statuses |
orders_detail | GET /orders/:id |
orders_create + orders_update | PUT /orders (upsert cần cả hai) |
products_list | GET /products |
custom_field_list | GET /custom-fields |
custom_object_record_create / custom_object_record_update | GET /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.
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:
| Header | Giá trị |
|---|---|
x-key-id | Mã key — phần <prefix> trong miniai_live_<prefix>_<…> (cột Mã key ở tab API key) |
x-timestamp | Unix time giây; lệch giờ máy chủ tối đa 5 phút |
x-signature | hex(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
Đườ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).
- Node.js
- Python
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");
import hashlib, hmac, json, os, time, requests
BASE = "https://api.socialcrm.vn"
KEY_ID = os.environ["MI9_KEY_ID"]
SECRET = os.environ["MI9_SECRET"].encode()
def call(method, path, body=None):
raw = json.dumps(body, separators=(",", ":")) if body is not None else ""
ts = str(int(time.time()))
body_hash = hashlib.sha256(raw.encode()).hexdigest()
canonical = "\n".join([ts, method.upper(), path, body_hash])
signature = hmac.new(SECRET, canonical.encode(), hashlib.sha256).hexdigest()
return requests.request(
method, BASE + path, data=raw or None,
headers={
"Content-Type": "application/json",
"x-key-id": KEY_ID,
"x-timestamp": ts,
"x-signature": signature,
},
).json()
call("GET", "/api/open/v1/me")
Quy ước chung
Giới hạn
| Giới hạn | Giá 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ú / đơn | tối đa 30 ngày (updatedSince → updatedUntil) |
| Dòng mỗi lần ghi hàng loạt | tối đa 50 |
| Request theo key | 60 / 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àng | 1 |
Mọi response có header hạn mức (cửa sổ cố định theo phút):
| Header | Ý nghĩa |
|---|---|
X-RateLimit-Limit | Hạn mức / phút của key |
X-RateLimit-Remaining | Số request còn lại trong phút này (đã tính cả trần của cửa hàng) |
X-RateLimit-Reset | Số giây đến khi sang phút mới |
Retry-After | Chỉ 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 |
|---|---|
updatedSince | ISO 8601 — lấy bản ghi cập nhật từ thời điểm này |
updatedUntil | ISO 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 |
limit | 1–50, mặc định 50 |
cursor | Giá trị nextCursor của trang trước — giữ nguyên, không tự giải mã |
excludeSource | Bỏ 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ửi | Cửa sổ dùng |
|---|---|
| Không gửi cả hai | 30 ngày gần nhất |
Chỉ updatedSince | updatedSince → updatedSince + 30 ngày |
Chỉ updatedUntil | updatedUntil − 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" }
}
Đ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.