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.
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ường | Giá trị gửi |
|---|---|
text, select | chuỗi (select: đúng value của lựa chọn) |
number, decimal | số |
boolean | true / false |
date | "YYYY-MM-DD" |
multi_select, checkbox_group | mả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" } }
| Code | HTTP | Khi nào |
|---|---|---|
API_KEY_INVALID | 401 | Mã key sai, key đã thu hồi, hoặc người tạo không còn hiệu lực |
HMAC_REQUIRED | 401 | Gửi API key trần (x-api-key / Bearer) thay vì ký request |
HMAC_SIGNATURE_INVALID | 401 | Chữ ký sai |
HMAC_TIMESTAMP_EXPIRED | 401 | x-timestamp thiếu, sai định dạng hoặc lệch quá 5 phút |
HMAC_REPLAYED | 401 | Chữ ký đã dùng |
API_SCOPE_FORBIDDEN | 403 | Key không có scope cho endpoint |
API_KEY_LIMIT_EXCEEDED | 409 | Tạo key khi cửa hàng đã có key đang hoạt động (chỉ gặp trên giao diện) |
API_RATE_LIMITED | 429 | Vượt 60 request / phút / key hoặc 300 / phút / cửa hàng (params.scope, params.retryAfter) |
OPEN_API_DATE_RANGE_INVALID | 400 | updatedSince ≥ updatedUntil hoặc cách nhau quá 30 ngày |
OPEN_API_CURSOR_INVALID | 400 | cursor không phải giá trị server trả |
OPEN_API_FIELD_UNKNOWN | 400 | Mã trường không tồn tại (params.fields) |
OPEN_API_FIELD_VALUE_INVALID | 400 | Giá trị sai kiểu trường |
OPEN_API_MATCH_FIELD_INVALID | 400 | Trườ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_AMBIGUOUS | 409 | Tiêu chí match khớp nhiều hơn một bản ghi |
OPEN_API_CUSTOMER_NOT_FOUND | 404 | Khách không tồn tại hoặc ngoài phạm vi của key |
OPEN_API_ORDER_NOT_FOUND | 404 | Đơn không tồn tại hoặc ngoài phạm vi của key |
OPEN_API_ORDER_INVALID | 400 | Đơn mới thiếu khách hoặc dòng hàng |
OPEN_API_ITEM_NOT_FOUND | 400 | SKU / biến thể không có trong danh mục |
SCRM_CUSTOMER_FIELD_DUPLICATE | 409 | customerCode / SĐT / email đã thuộc khách khác |
CUSTOM_FIELD_VALUE_DUPLICATE | 409 | Trường "duy nhất" đã có giá trị này ở bản ghi khác |
ORDER_CODE_DUPLICATE | 409 | orderCode đã 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.