Tổng quan#
Mô hình nhánh#
- Control —
variant_key = "control": chính là page gốc (ReactPage). Sửa/xuất bản qua API page thường (update,publish), không qua API biến thể. - Variant —
variant_key = "variant_<n>"(variant_1,variant_2…): mỗi biến thể là 1 documentpage_variants, unique theo(page_id, variant_key), có nội dung + bản xuất bản riêng. - Tối đa 4 biến thể (5 nhánh gồm control). Mỗi lần thêm/xoá, lưu lượng được chia lại đều; phần dư dồn vào control (vd 3 nhánh →
34/33/33). - Số
<n>cấp từvariant_seqvà không tái sử dụng: xoávariant_2rồi thêm mới sẽ ravariant_3. - Chỉ
enabled,allocation,main_variantđược đồng bộ sang Partner MySQL (ab_test_pages) để phục vụ report.
Lưu ý:
:variantKeyphải khớp^(control|variant_[1-9]\d*)$. Các endpoint nội dung/xuất bản/lịch sử chỉ nhậnvariant_<n>— truyềncontroltrả400.
Xác thực & envelope#
Mọi endpoint dưới đây (trừ mục Serve) yêu cầu access token của Builder:
Authorization: Bearer <access_token> // hoặc cookie access_token
Content-Type: application/json
Server luôn kiểm tra page thuộc shop trong token; page không tồn tại hoặc không sở hữu → 404.
// Thành công
{ "success": true, "data": { ... } }
// Lỗi
{ "success": false, "message": "...", "currentRevision": 5 // chỉ có khi 409 }
Danh sách endpoint#
| Method & path | Nhóm | Mô tả |
|---|---|---|
| POST /ab-test/setup/:id | Create | Bật A/B, tạo variant_1 từ nội dung page |
| POST /ab-test/add-variant/:id | Create | Thêm 1 biến thể, chia lại lưu lượng |
| GET /ab-test/detail/:id | Read | Cấu hình A/B + danh sách nhánh |
| GET /ab-test/variant/:id/:variantKey | Read | Nội dung builder của 1 biến thể |
| GET /ab-test/variant-versions/:id/:variantKey | Read | Lịch sử phiên bản của biến thể |
| GET /ab-test/preview-variant/:id/:variantKey | Read | HTML đã xuất bản của 1 nhánh (kể cả control) |
| PUT /ab-test/variant/:id/:variantKey | Update | Lưu nội dung (khoá theo revision) |
| POST /ab-test/publish-variant/:id/:variantKey | Update | Xuất bản HTML của biến thể |
| POST /ab-test/restore-variant-version/:id/:variantKey/:versionId | Update | Khôi phục 1 phiên bản cũ |
| POST /ab-test/choose-main/:id | Update | Đặt bản chính (áp dụng khi tắt test) |
| POST /ab-test/remove-variant/:id/:variantKey | Delete | Xoá 1 biến thể + lịch sử của nó |
| POST /ab-test/toggle/:id | Delete | enabled:false = kết thúc test, xoá mọi biến thể |
:id là page_id (Mongo id của page, ví dụ 6abdc74503f66bacb700e230).
Create#
1. Bật A/B (tạo variant_1)#
POST /api/v2/landingpage/ab-test/setup/:id
Không có body. Tạo variant_1 copy từ nội dung hiện tại của page, chia 50/50, bản chính = control, đồng bộ sang Partner.
- Lần đầu: 201,
created: true. - Gọi lại khi đã có biến thể: 200, giữ nguyên các nhánh + allocation, chỉ đồng bộ lại (dùng để retry khi
sync_status = failed). - Gọi lại sau khi đã tắt test: tạo biến thể mới với số tiếp theo (vd
variant_3).
{
"success": true,
"data": {
"variants": [ { "page_id": "6abd...", "variant_key": "variant_1", "revision": 1, "published_html": null, ... } ],
"runtime": {
"page_id": "6abd...",
"enabled": true,
"allocation": { "control": 50, "variant_1": 50 },
"main_variant": "control",
"variant_seq": 1,
"sync_status": "synced",
"last_sync_error": null
},
"created": true
}
}
Lưu ý: page đang dùng cơ chế split-test cũ (có
parent_idhoặc có trang con đã xuất bản) bị từ chối với 400.
2. Thêm biến thể#
POST /api/v2/landingpage/ab-test/add-variant/:id
| Body | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| source_variant_key | string | Không | Nhánh để copy nội dung. Mặc định control; có thể là variant_<n> đang tồn tại. |
Biến thể mới chưa xuất bản. Khách được chia vào nhánh chưa xuất bản sẽ thấy nội dung control và không bị gán cookie.
// 201
{
"success": true,
"data": {
"variant": { "variant_key": "variant_2", "revision": 1, ... },
"runtime": { "allocation": { "control": 34, "variant_1": 33, "variant_2": 33 }, "variant_seq": 2, ... }
}
}
| Lỗi | Khi nào |
|---|---|
| 400 | A/B chưa bật · đã đủ 4 biến thể · source_variant_key sai định dạng |
| 404 | source_variant_key không tồn tại |
Read#
3. Cấu hình A/B#
GET /api/v2/landingpage/ab-test/detail/:id
Luôn trả 200 với page sở hữu. variants: [] nghĩa là chưa từng bật A/B hoặc test đã kết thúc.
{
"success": true,
"data": {
"page_id": "6abd...",
"enabled": true,
"allocation": { "control": 34, "variant_1": 33, "variant_2": 33 },
"main_variant": "control",
"sync_status": "synced", // pending | synced | failed
"last_sync_error": null,
"control": { "published_at": "2026-10-01T08:04:00.000Z", "has_published_html": true },
"variants": [
{ "variant_key": "variant_1", "revision": 4, "updated_at": "...", "published_at": "...", "has_published_html": true },
{ "variant_key": "variant_2", "revision": 1, "updated_at": "...", "published_at": null, "has_published_html": false }
]
}
}
variants sắp theo số tăng dần. Builder hiển thị nhãn theo vị trí: control = Bản A, phần tử đầu = Bản B…
4. Nội dung 1 biến thể#
GET /api/v2/landingpage/ab-test/variant/:id/:variantKey
{
"success": true,
"data": {
"page_id": "6abd...",
"variant_key": "variant_1",
"blocks": { ... },
"hierarchy": { ... },
"popups": [ ... ],
"revision": 4,
"updated_at": "...",
"publication": { "published_at": "..." }, // null nếu chưa xuất bản
"page": { ... } // thông tin dùng chung của page (domain, SEO, tracking)
}
}
Giữ lại revision để gửi kèm khi lưu (mục 7). Domain/SEO/tracking là của page, không riêng từng biến thể.
5. Lịch sử phiên bản#
GET /api/v2/landingpage/ab-test/variant-versions/:id/:variantKey
Mới nhất trước. Mỗi lần lưu thủ công/xuất bản tạo 1 snapshot (gộp trong cửa sổ 10 phút), giữ 7 ngày. Autosave không tạo snapshot.
{
"success": true,
"data": {
"versions": [
{ "_id": "66f0...a1", "blocks": { ... }, "hierarchy": { ... }, "popups": [ ... ],
"trigger_mode": "manual", "window_started_at": "...", "created_at": "..." }
]
}
}
6. Preview HTML#
GET /api/v2/landingpage/ab-test/preview-variant/:id/:variantKey
Trả HTML đã xuất bản của đúng 1 nhánh — :variantKey nhận cả control. Nhánh chưa xuất bản → 400.
{
"success": true,
"data": { "page_id": "6abd...", "variant_key": "variant_1", "html": "<!DOCTYPE html>...", "source_revision": 4, "published_at": "..." }
}
Update#
7. Lưu nội dung#
PUT /api/v2/landingpage/ab-test/variant/:id/:variantKey
| Body | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| revision | integer ≥ 1 | Có | Revision đang giữ ở client (optimistic lock) |
| blocks | object | Không | Chỉ field nào gửi lên mới bị ghi đè |
| hierarchy | object | Không | |
| popups | array | Không | |
| save_mode | string | Không | manual (mặc định) · publish · autosave (không tạo snapshot lịch sử) |
// 200
{ "success": true, "data": { "page_id": "6abd...", "variant_key": "variant_1", "revision": 5, "updated_at": "..." } }
// 409 — phiên khác đã lưu trước, nội dung KHÔNG bị ghi đè
{ "success": false, "message": "Nội dung đã bị thay đổi bởi phiên khác — vui lòng tải lại", "currentRevision": 6 }
Lưu ý: nội dung (blocks + hierarchy + popups) tối đa ~14 MB; vượt → 413.
8. Xuất bản biến thể#
POST /api/v2/landingpage/ab-test/publish-variant/:id/:variantKey
| Body | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
| html | string | Có | HTML đầy đủ do Builder render từ nội dung biến thể |
Server minify + chèn page token, lưu vào biến thể. Nếu A/B đang bật và page dùng tên miền riêng, đồng thời ghi file <name>.variant_<n>.html + .abtest.json sang server PHP.
{
"success": true,
"data": { "page_id": "6abd...", "variant_key": "variant_1", "published_html": "...", "published_at": "...", "revision": 5 }
}
Xuất bản control: dùng API publish page thường — khi A/B đang bật, API đó tự đồng bộ lại file của mọi biến thể đã xuất bản.
9. Khôi phục phiên bản#
POST /api/v2/landingpage/ab-test/restore-variant-version/:id/:variantKey/:versionId
Không có body. Ghi nội dung của phiên bản :versionId (lấy từ mục 5) đè lên biến thể, tăng revision. Chỉ ghi nháp — muốn khách thấy phải xuất bản lại.
{ "success": true, "data": { "page_id": "6abd...", "variant_key": "variant_1", "revision": 6, "updated_at": "..." } }
10. Đặt bản chính#
POST /api/v2/landingpage/ab-test/choose-main/:id
{ "variant_key": "variant_2" } // hoặc "control"
Chỉ đổi bản chính — không đổi nội dung, không đổi lưu lượng. Bản chính là bản được áp cho toàn bộ khách khi tắt test (mục 12). Trả về runtime.
| Lỗi | Khi nào |
|---|---|
| 400 | variant_key sai định dạng · nhánh chưa xuất bản |
| 404 | Chưa bật A/B · nhánh không nằm trong test |
Delete#
11. Xoá biến thể#
POST /api/v2/landingpage/ab-test/remove-variant/:id/:variantKey
Không có body. Xoá vĩnh viễn biến thể + lịch sử phiên bản của nó, chia lại lưu lượng đều cho các nhánh còn lại. Nếu biến thể đang là bản chính, bản chính chuyển về control.
{ "success": true, "data": { "runtime": { "allocation": { "control": 50, "variant_1": 50 }, "main_variant": "control", ... } } }
| Lỗi | Khi nào |
|---|---|
| 400 | :variantKey = control · đây là biến thể cuối cùng (muốn bỏ hết thì tắt A/B) |
| 404 | Biến thể không tồn tại · chưa bật A/B |
Khách đang giữ cookie của nhánh đã xoá sẽ được chia lại ở lần truy cập sau. Số liệu cũ của nhánh vẫn còn trong report, gắn nhãn “(đã xoá)”.
12. Bật / tắt A/B#
POST /api/v2/landingpage/ab-test/toggle/:id
{ "enabled": false }
| enabled | Hành vi |
|---|---|
| true | Bật lại runtime và đồng bộ lại file của mọi nhánh đã xuất bản (tên miền riêng). |
| false | Kết thúc test: áp bản chính lên page gốc (nội dung + HTML), xoá toàn bộ biến thể và lịch sử, allocation về {"control":100}, xoá file split bên PHP. Không hoàn tác được. |
Lưu ý: tắt test yêu cầu bản chính đã xuất bản, nếu không trả 400. Sau khi tắt, gọi lại
setupđể bắt đầu test mới.
Serve & cookie (public)#
Không cần token. Dùng khi hiển thị page cho khách:
GET /api/v2/landingpage/html/:id?host=<hostname>&ab_variant=<giá trị cookie>
| Query | Mô tả |
|---|---|
| host | Hostname đang hiển thị page (domain mặc định hoặc tên miền riêng) |
| ab_variant | Giá trị cookie ab_variant_<page_id> client đang giữ — bắt buộc gửi khi gọi cross-origin vì fetch không mang cookie |
| variant | Ép hiển thị 1 nhánh (preview), không gán cookie |
ab_variant/ cookie còn nằm trong allocation → trả đúng nhánh đó.- Ngược lại → chọn ngẫu nhiên theo allocation, trả
Set-Cookie: ab_variant_<page_id>=<key>(30 ngày) và headerX-Ab-Variant: <key>(đã expose qua CORS) để client cross-origin tự ghi cookie. - Nhánh được chọn chưa xuất bản → trả HTML control, không gán cookie.
Tên miền riêng do PHP (routes.php đọc .abtest.json) xử lý với cùng thuật toán và cùng tên cookie.
Mã lỗi#
| HTTP | Ý nghĩa |
|---|---|
| 400 | Tham số sai (variant_key, revision, html…) hoặc vi phạm quy tắc nghiệp vụ |
| 401 | Thiếu / hết hạn access token |
| 404 | Page không tồn tại hoặc không thuộc shop · nhánh không tồn tại |
| 409 | Lưu với revision cũ — kèm currentRevision, tải lại rồi lưu tiếp |
| 413 | Nội dung hoặc HTML vượt ~14 MB |
| 502 | Đồng bộ sang Partner thất bại — sync_status = failed, gọi lại thao tác để thử lại |