5.4 A/B 测试接口
5.4 A/B 测试接口
A/B 测试管理接口前缀为 /api/v1/ab_tests,均需认证。公开转化反馈接口为 /api/v1/public/ab_test_feedback,不需要登录态或 Bearer Token,写入范围由 _dwz_abt 签名 token 限制。
受保护接口支持工作区上下文请求头:
Authorization: Bearer {token}
X-Workspace-Id: 1
创建 A/B 测试
POST /api/v1/ab_tests
Content-Type: application/json
{
"short_link_id": 42,
"name": "落地页颜色测试",
"description": "比较红色与蓝色落地页的转化效果",
"traffic_split": "weighted",
"start_time": "2026-04-15T00:00:00Z",
"end_time": "2026-04-30T23:59:59Z",
"variants": [
{
"name": "红色",
"target_url": "https://example.com/red",
"weight": 60,
"is_control": true,
"description": "对照组"
},
{
"name": "蓝色",
"target_url": "https://example.com/blue",
"weight": 40,
"is_control": false,
"description": "实验组"
}
]
}
traffic_split 支持:equal / weighted / custom。创建时至少需要 2 个变体。
列表
GET /api/v1/ab_tests?page=1&page_size=20&status=running&short_link_id=42
查询参数:
| 参数 | 必填 | 说明 |
|---|---|---|
page |
否 | 页码,默认 1 |
page_size |
否 | 每页数量,默认 10,最大 100 |
short_link_id |
否 | 按短链接筛选 |
status |
否 | 按状态筛选 |
详情
GET /api/v1/ab_tests/{id}
更新
PUT /api/v1/ab_tests/{id}
Content-Type: application/json
{
"name": "新的测试名称",
"description": "更新后的描述",
"status": "paused",
"traffic_split": "equal",
"start_time": "2026-04-15T00:00:00Z",
"end_time": "2026-04-30T23:59:59Z",
"is_active": true
}
删除
DELETE /api/v1/ab_tests/{id}
启动
POST /api/v1/ab_tests/{id}/start
Content-Type: application/json
{
"start_time": "2026-04-15T00:00:00Z"
}
start_time 可选,不传则立即启动。
停止
POST /api/v1/ab_tests/{id}/stop
Content-Type: application/json
{
"end_time": "2026-04-30T23:59:59Z"
}
end_time 可选,不传则立即停止。停止后实验状态变为 completed。
获取 A/B 测试统计
GET /api/v1/ab_tests/{id}/statistics?days=30
查询参数:
| 参数 | 必填 | 说明 |
|---|---|---|
days |
否 | 统计最近天数,默认 7,最大 365 |
响应示例:
{
"code": 0,
"message": "success",
"data": {
"ab_test_id": 1,
"total_clicks": 1000,
"total_conversions": 138,
"conversion_value": 2999.5,
"variant_stats": [
{
"variant": {
"id": 1,
"ab_test_id": 1,
"name": "版本A",
"target_url": "https://example.com/page-a",
"weight": 50,
"is_control": true
},
"click_count": 480,
"unique_clicks": 460,
"conversion_count": 58,
"conversion_rate": 12.61,
"conversion_value": 1200,
"percentage": 48
},
{
"variant": {
"id": 2,
"ab_test_id": 1,
"name": "版本B",
"target_url": "https://example.com/page-b",
"weight": 50,
"is_control": false
},
"click_count": 520,
"unique_clicks": 500,
"conversion_count": 80,
"conversion_rate": 16,
"conversion_value": 1799.5,
"percentage": 52
}
],
"daily_stats": [
{
"date": "2024-01-15",
"variants": {
"1": 45,
"2": 55
}
}
],
"conversion_rate": 14.38,
"winning_variant": {
"id": 2,
"name": "版本B"
}
}
}统计口径:
click_count:变体点击次数。unique_clicks:变体唯一会话点击数。conversion_count:通过分流反馈接口写入的业务结果数。- 变体
conversion_rate:conversion_count / unique_clicks * 100,无唯一点击时为0。 - 顶层
conversion_rate:total_conversions / 所有变体 unique_clicks 之和 * 100。 conversion_value:反馈事件value汇总。winning_variant:当前按转化数最高的变体计算。
上报 A/B 测试转化反馈
POST /api/v1/public/ab_test_feedback
Content-Type: application/json
{
"feedback_token": "<_dwz_abt 参数值>",
"event_id": "order-202401150001",
"value": 99.9,
"currency": "CNY",
"metadata": {
"plan": "pro"
},
"occurred_at": "2024-01-15T10:30:00Z"
}
A/B 测试跳转到变体目标 URL 时会追加 _dwz_abt 参数。落地页或业务系统在产生注册、下单、购买等结果后,用该 token 回传转化。
参数说明:
| 参数 | 必填 | 说明 |
|---|---|---|
feedback_token |
是 | 短链 A/B 跳转目标 URL 中 _dwz_abt 的值 |
event_id |
是 | 业务事件唯一 ID,同一 A/B 测试内幂等,最长 128 字符 |
value |
否 | 转化价值,必须大于等于 0 |
currency |
否 | 币种,服务端会转为大写,最长 16 字符 |
metadata |
否 | 业务附加信息,序列化后最大 4096 字节 |
occurred_at |
否 | 业务事件发生时间,ISO 8601 格式;不传则使用服务端当前时间 |
成功响应:
{
"code": 0,
"message": "success",
"data": {
"id": 10,
"duplicate": false,
"workspace_id": 1,
"ab_test_id": 1,
"variant_id": 2,
"short_link_id": 5,
"session_id": "ab_1_5_xxx",
"event_id": "order-202401150001"
}
}重复提交相同 event_id 会返回成功,但 duplicate 为 true,不会重复计入转化。
错误说明:
| HTTP 状态 | code | 场景 |
|---|---|---|
| 400 | 40001 | 缺少 feedback_token、缺少 event_id、字段长度非法、value 为负数 |
| 401 | 40101 | token 无效、被篡改、过期,或 token 绑定的实验/变体不存在 |
| 500 | 50001 | 服务端写入失败 |
变体级点击统计
GET /api/v1/ab_test_click_statistics/{id}/variants
该接口按变体返回点击时间序列,适合绘图。转化数与转化率以 GET /api/v1/ab_tests/{id}/statistics 为准。
落地页自动回传示例
const token = new URLSearchParams(location.search).get('_dwz_abt');
if (token) {
await fetch('https://your-domain.com/api/v1/public/ab_test_feedback', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
feedback_token: token,
event_id: 'order-202401150001',
value: 99.9,
currency: 'CNY',
metadata: {
order_id: '202401150001',
plan: 'pro'
}
})
});
}