Salekit Docs

API A/B Test — Page Variant

https://api.salekit.com:3036/api/v2/landingpage

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 document page_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_seq và không tái sử dụng: xoá variant_2 rồi thêm mới sẽ ra variant_3.
  • Chỉ enabled, allocation, main_variant được đồng bộ sang Partner MySQL (ab_test_pages) để phục vụ report.

Lưu ý: :variantKey phải khớp ^(control|variant_[1-9]\d*)$. Các endpoint nội dung/xuất bản/lịch sử chỉ nhận variant_<n> — truyền control trả 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 & pathNhómMô tả
POST /ab-test/setup/:idCreateBật A/B, tạo variant_1 từ nội dung page
POST /ab-test/add-variant/:idCreateThêm 1 biến thể, chia lại lưu lượng
GET /ab-test/detail/:idReadCấu hình A/B + danh sách nhánh
GET /ab-test/variant/:id/:variantKeyReadNội dung builder của 1 biến thể
GET /ab-test/variant-versions/:id/:variantKeyReadLịch sử phiên bản của biến thể
GET /ab-test/preview-variant/:id/:variantKeyReadHTML đã xuất bản của 1 nhánh (kể cả control)
PUT /ab-test/variant/:id/:variantKeyUpdateLưu nội dung (khoá theo revision)
POST /ab-test/publish-variant/:id/:variantKeyUpdateXuất bản HTML của biến thể
POST /ab-test/restore-variant-version/:id/:variantKey/:versionIdUpdateKhôi phục 1 phiên bản cũ
POST /ab-test/choose-main/:idUpdateĐặt bản chính (áp dụng khi tắt test)
POST /ab-test/remove-variant/:id/:variantKeyDeleteXoá 1 biến thể + lịch sử của nó
POST /ab-test/toggle/:idDeleteenabled: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_id hoặ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
BodyKiểuBắt buộcMô tả
source_variant_keystringKhôngNhá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ỗiKhi nào
400A/B chưa bật · đã đủ 4 biến thể · source_variant_key sai định dạng
404source_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
BodyKiểuBắt buộcMô tả
revisioninteger ≥ 1CóRevision đang giữ ở client (optimistic lock)
blocksobjectKhôngChỉ field nào gửi lên mới bị ghi đè
hierarchyobjectKhông
popupsarrayKhông
save_modestringKhôngmanual (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
BodyKiểuBắt buộcMô tả
htmlstringCó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ỗiKhi nào
400variant_key sai định dạng · nhánh chưa xuất bản
404Chư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ỗiKhi nào
400:variantKey = control · đây là biến thể cuối cùng (muốn bỏ hết thì tắt A/B)
404Biế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 }
enabledHành vi
trueBậ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).
falseKế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>
QueryMô tả
hostHostname đang hiển thị page (domain mặc định hoặc tên miền riêng)
ab_variantGiá 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
  1. ab_variant / cookie còn nằm trong allocation → trả đúng nhánh đó.
  2. Ngược lại → chọn ngẫu nhiên theo allocation, trả Set-Cookie: ab_variant_<page_id>=<key> (30 ngày) và header X-Ab-Variant: <key> (đã expose qua CORS) để client cross-origin tự ghi cookie.
  3. 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
400Tham số sai (variant_key, revision, html…) hoặc vi phạm quy tắc nghiệp vụ
401Thiếu / hết hạn access token
404Page không tồn tại hoặc không thuộc shop · nhánh không tồn tại
409Lưu với revision cũ — kèm currentRevision, tải lại rồi lưu tiếp
413Nộ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