Luồng tổng thể (đọc trước khi tích hợp)#
- Tạo page (
POST /api/v2/landingpage/create) hoặc Clone page (POST /api/v2/landingpage/clone/:id) → nhậnpage_id. - Partner tự redirect user sang editor — API chỉ trả về
page_id, không trả về URL editor. Partner tự ghép URLhttps://builder.<domain>/{page_id}?token=...và điều hướng user sang đó. - Page vừa tạo/clone chưa có HTML (trừ khi clone từ page nguồn đã publish). Sau khi vào editor, user bấm Xuất bản (Publish) — chỉ sau bước này page mới có HTML và tự động đồng bộ sang domain riêng của partner.
Editor URL — chỉ còn 1 domain duy nhất#
Partner không cần lưu / không cần quan tâm page thuộc builder cũ hay mới. Cứ luôn điều hướng về:
https://builder.<DOMAIN_PARTNER>/{page_id}?token=<TOKEN>
Builder tự nhận diện loại page và tự redirect sang builder cũ (/v1/{page_id}, nginx proxy ngược về builder PHP) nếu page đó không thuộc builder React. Toàn bộ việc phân luồng do builder xử lý.
Ví dụ với unica.vn: dù page cũ hay mới, partner đều mở https://builder.unica.vn/{page_id}?token=....
Lưu ý migrate: nếu hệ thống partner đang hardcode
builder1.<domain>, bỏ đi — domain đó không dùng cho tích hợp mới. Mọi thứ đi quabuilder.<domain>.
1. Tạo Page#
Thay thế cho luồng
Clone pagecũ (https://salekit.page/api/page/clone). Luồng cũ thực chất luôn clone từ 1 page mẫu cố định (copy_id), bản chất là tạo page mới — nên chuyển sang API tạo page của builder mới, không dùng clone.
Sử dụng mã hóa Json Web Token chuỗi Token:
DATA: Mảng giá trị
| Field | Type | Desc |
|---|---|---|
| shop_id | Int hoặc String | ID Shop trên hệ thống DOMAIN_PARTNER |
| creator_id | Int hoặc String | ID User trên hệ thống DOMAIN_PARTNER |
TOKEN = jwt(DATA, KEY)
Key tích hợp: KEY = XTBHTCTBWCPPGX0U
Url: https://api.salekit.com:3036/api/v2/landingpage/create
Method: POST
Header
| Key | Value |
|---|---|
| Content-Type | application/json |
| Token | TOKEN |
Body
| Field | Value | Desc |
|---|---|---|
| domain_site | DOMAIN_PARTNER | Tên miền tích hợp |
| pageType | INT (default 0) | Loại page: 2 = Sale page (tương ứng Landingpage bên hệ cũ) |
Response
| Field | Type | Desc |
|---|---|---|
| error | Boolean | true/false |
| data | String | Id page vừa tạo trả về từ hệ thống Builder |
Ví dụ response thành công (HTTP 201):
{
"error": false,
"data": "68f1c0a9d4b2e30012ab34cd"
}
API này CHỈ trả về
page_id— không trả về object page, không trả về URL editor, không trả về HTML. Sau khi nhậndata, partner phải tự redirect user sang editor builder mới:https://builder.<DOMAIN_PARTNER>/{data}?token=<TOKEN>Ví dụ với
unica.vnvàdata = 68f1c0a9d4b2e30012ab34cd:https://builder.unica.vn/68f1c0a9d4b2e30012ab34cd?token=...
Response lỗi
| HTTP | Body | Nguyên nhân |
|---|---|---|
| 401 | { "error": true, "msg": "Token không được cung cấp" } |
Thiếu header Token |
| 401 | { "error": true, "msg": "Token không hợp lệ" } |
JWT ký sai KEY hoặc sai domain_site |
| 401 | { "error": true, "msg": "Token đã hết hạn" } |
JWT hết hạn (exp) |
| 400 | { "error": true, "msg": "domain_site là bắt buộc" } |
Body thiếu domain_site |
| 500 | { "error": true, "msg": "..." } |
Lỗi hệ thống Builder |
Lưu ý quan trọng: Page vừa tạo qua API này chưa publish và chưa có HTML — API tạo page chỉ khởi tạo cấu trúc block rỗng theo
pageType, không build HTML. Muốn có HTML thật (để hiển thị/clone/lấy nội dung), partner bắt buộc phải đưa user vào editor builder mới (https://builder.<domain>/{page_id}?token=...) để bấm Xuất bản (Publish). Chỉ sau khi publish, hệ thống mới:
- Ghi HTML vào page (
react_publish.html).- Tự động đồng bộ HTML sang domain riêng của partner (webhook
apiv1/landingpage/update) — không cần partner tự gọi thêm API nào để đồng bộ, việc này tự chạy ngay khi publish.
2. Clone Page#
Dùng khi partner muốn clone 1 page đã tạo qua builder mới (page_id lấy từ response mục tạo page ở trên) — không dùng cho clone từ page mẫu builder cũ.
Sử dụng mã hóa Json Web Token chuỗi Token:
DATA: Mảng giá trị
| Field | Type | Desc |
|---|---|---|
| shop_id | Int hoặc String | ID Shop trên hệ thống DOMAIN_PARTNER |
| creator_id | Int hoặc String | ID User trên hệ thống DOMAIN_PARTNER |
| copy_id | String | page_id nguồn cần clone (page đã tạo qua builder mới) |
| parent_id | String (optional) | Gắn page mới là con của parent_id — dùng cho A/B test variant |
| name | String (optional) | Tên page mới |
TOKEN = jwt(DATA, KEY)
Key tích hợp: KEY = XTBHTCTBWCPPGX0U
Url: https://api.salekit.com:3036/api/v2/landingpage/clone/:id
Method: POST
:idtrên URL chính làpage_idcủa page muốn clone (page nguồn) — cùng ý nghĩa vớicopy_idtrong DATA. Chỉ cần truyền 1 trong 2: nếu DATA cócopy_idthìcopy_idđược ưu tiên dùng,:idtrên URL sẽ bị bỏ qua; nếu DATA không cócopy_idthì hệ thống mới lấy:idtrên URL làm page nguồn.
Header
| Key | Value |
|---|---|
| Content-Type | application/json |
| Token | TOKEN |
Body
| Field | Value | Desc |
|---|---|---|
| domain_site | DOMAIN_PARTNER | Tên miền tích hợp |
Response
| Field | Type | Desc |
|---|---|---|
| error | Boolean | true/false |
| data | String | Id page mới trả về từ hệ thống Builder |
| html | String | HTML đã publish của page gốc (copy_id), base64-encode |
Redirect sang editor: giống API tạo page, response clone chỉ trả về
page_idmới — partner tự ghép URL và điều hướng user sanghttps://builder.<DOMAIN_PARTNER>/{data}?token=<TOKEN>.Lưu ý:
htmlchỉ có giá trị khi page gốc (copy_id) đã từng publish. Nếu partner clone 1 page vừa tạo qua APIPOST /api/v2/landingpage/createở trên (page chưa publish lần nào), fieldhtmlsẽ luôn trả về rỗng""— không dùng field này để lấy nội dung page trong luồng tạo mới → clone.
3. Export Page#
Lấy toàn bộ nội dung page của builder mới ra dạng JSON, để lưu thành file
.lphoặc chuyển sang shop/hệ thống khác. Export không trả HTML — chỉ trả cấu trúc block.
Sử dụng mã hóa Json Web Token chuỗi Token:
DATA: Mảng giá trị
| Field | Type | Desc |
|---|---|---|
| shop_id | Int hoặc String | ID Shop trên hệ thống DOMAIN_PARTNER |
| creator_id | Int hoặc String | ID User trên hệ thống DOMAIN_PARTNER |
TOKEN = jwt(DATA, KEY)
Key tích hợp: KEY = XTBHTCTBWCPPGX0U
API export không đọc nội dung DATA — chỉ cần JWT ký đúng
KEYcủadomain_site. Page được xác định hoàn toàn bằng:idtrên URL.
Url: https://api.salekit.com:3036/api/v2/landingpage/export/:id
Method: POST
:idlàpage_idcủa page cần export — phải là chuỗi 24 ký tự hex (_idMongoDB). Truyền sai định dạng sẽ trả lỗi 400.
Header
| Key | Value |
|---|---|
| Content-Type | application/json |
| Token | TOKEN |
Body
| Field | Value | Desc |
|---|---|---|
| domain_site | DOMAIN_PARTNER | Tên miền tích hợp (bắt buộc — dùng để tra KEY xác thực token) |
Response
| Field | Type | Desc |
|---|---|---|
| error | Boolean | true/false |
| data | Object | Nội dung page đã export |
Các field bên trong data:
| Field | Type | Desc |
|---|---|---|
| name | String | Tên page |
| shop_id | Int hoặc String | Shop sở hữu page gốc |
| creator_id | Int hoặc String | User tạo page gốc |
| type | Int | Loại bản ghi nội bộ |
| blocks | Object | Toàn bộ block của page — dữ liệu chính khi import |
| hierarchy | Object | Cây phân cấp block (block nào nằm trong block nào) |
| page_settings | Object | Cấu hình page: config, customCode, seo, tracking |
| page_type | Int | Loại page (0, 2, 8, 9, 10) |
| builder_type | String | Luôn là "react" |
| body | Object (optional) | Chỉ xuất hiện khi page có dữ liệu builder cũ |
| export | Object (optional) | Chỉ xuất hiện khi page có dữ liệu builder cũ |
Ví dụ response thành công (HTTP 200):
{
"error": false,
"data": {
"name": "Landing page khóa học",
"shop_id": 1234,
"creator_id": 5678,
"type": 0,
"blocks": {
"page": { "type": "page", "label": "Page", "cname": "page", "configs": {}, "bpConfigs": {} },
"SECTION1": { "type": "section", "label": "Section", "cname": "section", "configs": {}, "bpConfigs": {} }
},
"hierarchy": { "page": ["SECTION1"], "SECTION1": [] },
"page_settings": { "config": { "pageType": 2 }, "customCode": {}, "seo": {}, "tracking": {} },
"page_type": 2,
"builder_type": "react"
}
}
Response lỗi
| HTTP | Body | Nguyên nhân |
|---|---|---|
| 401 | { "error": true, "msg": "Token không được cung cấp" } |
Thiếu header Token |
| 401 | { "error": true, "msg": "Token không hợp lệ" } |
JWT ký sai KEY hoặc sai domain_site |
| 401 | { "error": true, "msg": "Token đã hết hạn" } |
JWT hết hạn (exp) |
| 400 | { "error": true, "msg": "domain_site là bắt buộc" } |
Body thiếu domain_site |
| 400 | { "error": true, "msg": "page_id không hợp lệ" } |
:id không phải 24 ký tự hex |
| 404 | { "error": true, "msg": "Page không tồn tại" } |
Không tìm thấy page với :id đó |
| 500 | { "error": true, "msg": "Export failed" } |
Lỗi hệ thống Builder |
4. Import Page#
Không có API import riêng. Import chính là API tạo page ở mục 1 (
POST /api/v2/landingpage/create), nhưng body được gắn thêmblocks/hierarchy/page_settingslấy từ response export. Nếu body không có các field này, hệ thống tạo page rỗng theopageTypenhư bình thường.
Sử dụng mã hóa Json Web Token chuỗi Token:
DATA: Mảng giá trị
| Field | Type | Desc |
|---|---|---|
| shop_id | Int hoặc String | ID Shop đích trên hệ thống DOMAIN_PARTNER |
| creator_id | Int hoặc String | ID User đích trên hệ thống DOMAIN_PARTNER |
| name | String (optional) | Tên page mới. Nếu có, ưu tiên hơn name trong Body |
TOKEN = jwt(DATA, KEY)
Key tích hợp: KEY = XTBHTCTBWCPPGX0U
Url: https://api.salekit.com:3036/api/v2/landingpage/create
Method: POST
Header
| Key | Value |
|---|---|
| Content-Type | application/json |
| Token | TOKEN |
Body
| Field | Value | Desc |
|---|---|---|
| domain_site | DOMAIN_PARTNER | Tên miền tích hợp (bắt buộc) |
| page_type | INT | Lấy từ data.page_type của response export. Chấp nhận cả tên pageType |
| blocks | Object | data.blocks từ response export |
| hierarchy | Object | data.hierarchy từ response export |
| page_settings | Object | data.page_settings từ response export |
| name | String (optional) | Tên page mới — chỉ dùng khi JWT không có name |
| body | Object (optional) | data.body từ export, nếu có |
| export | Object (optional) | data.export từ export, nếu có |
Response
Giống hệt mục 1 — chỉ trả page_id mới:
{
"error": false,
"data": "68f1c0a9d4b2e30012ab34cd"
}
Sau đó partner tự redirect user sang https://builder.<DOMAIN_PARTNER>/{data}?token=<TOKEN>. Page vừa import chưa có HTML — user phải vào editor bấm Xuất bản (Publish).
Response lỗi: giống bảng lỗi ở mục 1 (401 token, 400 thiếu domain_site, 500 lỗi hệ thống).
4.1. Ba điểm bắt buộc lưu ý khi import#
a) page_type không tự đi theo blocks
Response export trả page_type ở cấp data, nhưng API import đọc loại page từ Body (page_type / pageType), không đọc từ page_settings.config.pageType. Partner phải tự map:
body.page_type = exported.page_type;
Nếu bỏ qua bước này, hệ thống fallback về sales_page trong JWT, và nếu cũng không có thì page được tạo với page_type = 0 — sai loại page. Giá trị hợp lệ: 0, 2, 8, 9, 10; giá trị ngoài danh sách bị quy về 0.
Ngoài ra page_settings.config.pageType luôn bị hệ thống ghi đè bằng page_type đã resolve từ Body — không cần chỉnh tay field này.
b) blocks phải có nhiều hơn 1 phần tử
Hệ thống chỉ chấp nhận blocks từ Body khi object đó có từ 2 key trở lên. Page chỉ có đúng 1 block gốc (page) sẽ bị âm thầm thay bằng cấu trúc mặc định của page_type, không báo lỗi — response vẫn trả 201 kèm page_id bình thường.
Partner nên kiểm tra trước khi gọi:
if (!exported.blocks || Object.keys(exported.blocks).length <= 1) {
// page rỗng — import sẽ không giữ nội dung, cảnh báo user
}
c) hierarchy chỉ được nhận kèm blocks
hierarchy chỉ có tác dụng khi blocks hợp lệ theo điều kiện (b). Gửi hierarchy một mình sẽ bị bỏ qua. Luôn gửi cả cặp.
4.2. Giới hạn dữ liệu của import/export (hiện tại)#
Cặp API này chỉ mang theo bố cục và cấu hình trang. Những thứ sau không nằm trong data của export và không được khôi phục khi import:
| Dữ liệu | Trạng thái |
|---|---|
blocks, hierarchy |
✅ Được mang theo |
page_settings (config, customCode, seo, tracking) |
✅ Được mang theo |
page_type |
⚠️ Được export, nhưng partner phải tự map sang Body khi import (xem 4.1a) |
| Popup | ❌ Không export, không import — page đích sẽ không có popup nào |
Tiện ích dùng chung (global_components) |
❌ Không export, không import |
| HTML đã publish | ❌ Không export — page đích phải publish lại từ editor |
| Ảnh thumbnail page | ❌ Không export |
| Trạng thái publish, domain, slug, lượt xem | ❌ Không export — page đích luôn ở trạng thái chưa publish |
Nếu page nguồn dùng popup hoặc tiện ích dùng chung, hãy báo trước cho user rằng các phần này cần dựng lại thủ công sau khi import. Đây là giới hạn hiện tại của hệ thống, không phải lỗi tích hợp.
4.3. Luồng tham khảo: export → file .lp → import#
Đây là luồng đang chạy thật trên hệ thống partner:
┌─ EXPORT ────────────────────────────────────────────────┐
│ 1. POST /api/v2/landingpage/export/{page_id} │
│ → data │
│ 2. Bỏ các field không cần: shop_id, creator_id │
│ 3. Gắn cờ version = 1 (đánh dấu page builder mới) │
│ 4. jwt(data) → ghi ra file <tên page>.lp │
└──────────────────────────────────────────────────────────┘
↓
┌─ IMPORT ────────────────────────────────────────────────┐
│ 1. User upload file .lp │
│ 2. decode jwt(file) → file_content │
│ 3. Rẽ nhánh theo file_content.version: │
│ version == 1 → POST /api/v2/landingpage/create │
│ còn lại → API builder cũ │
│ body = { domain_site, ...file_content } │
│ 4. Nhận page_id mới → lưu vào DB partner (version = 1) │
│ 5. Redirect: builder.<domain>/{page_id}?token=... │
└──────────────────────────────────────────────────────────┘
Cờ version do partner tự gắn vào file .lp, không phải field của API. Nó giúp phân biệt file export từ builder mới (version = 1) và builder cũ (version = 0) để gọi đúng endpoint lúc import.
Ví dụ gọi import bằng Node.js:
const exported = fileContent; // đã decode từ file .lp
const token = jwt.sign({ shop_id, creator_id, name }, KEY);
const body = {
domain_site: "unica.vn",
page_type: exported.page_type, // bắt buộc — xem 4.1a
blocks: exported.blocks,
hierarchy: exported.hierarchy,
page_settings: exported.page_settings,
};
const res = await fetch("https://api.salekit.com:3036/api/v2/landingpage/create", {
method: "POST",
headers: { "Content-Type": "application/json", Token: token },
body: JSON.stringify(body),
});
const { error, data: newPageId } = await res.json();