96 KiB
Backend API 文档
生成日期:2026-05-08
生成依据:backend/routes/api.php、php artisan route:list --path=api、app/Http/Controllers/Api/**、app/Services/**
本文档是当前 backend API 的唯一维护入口。backend/docs 下的旧 API 文档已移除,后续接口变更统一更新本文件。
本文档只覆盖当前 backend 暴露的 API 路由,共 43 个路由入口。Laravel 自动支持的 HEAD 不单独列出。
通用约定
Base URL
生产或本地部署时以实际后端域名为准,下文统一写作:
{BACKEND_API_BASE_URL}/api
本地默认通常是:
http://127.0.0.1:8000/api
请求头
| 请求头 | 适用接口 | 说明 |
|---|---|---|
Content-Type: application/json |
有 JSON body 的接口 | 请求体使用 JSON。 |
Authorization: Bearer <sanctum_token> |
Dashboard / 当前用户接口 | Laravel Sanctum Web 用户 token。邮箱已验证账号可由 /api/auth/login 返回;邮箱未验证账号必须先通过 /api/auth/email/verify-code,验证码校验成功后才返回 token;重置密码仅在账号邮箱已验证时返回 token。Dashboard 路由会拒绝扩展 token。 |
Authorization: Bearer <extension_sanctum_token> |
Chrome 扩展接口 | 扩展专用 Sanctum token,由 /api/auth/extension-pair 或 /api/auth/refresh-extension-token 返回;后端会校验 storeai_extension token name 和 extension:* ability。扩展 token 只能访问扩展接口和 /api/auth/logout。 |
X-Ext-Version: <version> |
扩展扫描相关接口 | 用于扩展版本门禁,低于 MIN_EXT_VERSION 会返回 ext_outdated。 |
Authorization: Bearer <CRON_SECRET> |
Cron 接口 | 配置 CRON_SECRET 后必填;生产环境未配置会返回 misconfigured。 |
Stripe-Signature: <signature> |
Stripe webhook | Stripe webhook 原始 body 签名。 |
X-Telegram-Bot-Api-Secret-Token: <secret> |
Telegram webhook | Telegram webhook secret token。 |
响应格式
所有 backend API 统一返回三元素 envelope。前端已确定重写,不再保留旧 { ok: ... } 顶层兼容格式。
{
"code": 1,
"message": "ok",
"data": {}
}
失败响应的顶层 code 放整型 status,旧接口字符串错误码统一放入 data.apiCode;异常响应不改变实际 HTTP status code,HTTP
层保持 200:
{
"code": 400,
"message": "Validation failed.",
"data": {
"apiCode": "invalid_payload"
}
}
部分错误会附带额外字段:
{
"code": 400,
"message": "schema validation failed",
"data": {
"apiCode": "invalid_payload",
"details": {
"fieldErrors": {
"payload.today.orders": ["must be an integer"]
}
}
}
}
空业务数据返回空数组或空对象,取决于控制器业务返回值;客户端只应依赖外层 code/message/data。
{
"code": 1,
"message": "ok",
"data": {}
}
通用错误
下表的 响应 code 指 JSON 响应体里的 code 字段,不是 HTTP status code;异常响应的 HTTP status 保持 200。
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload / invalid_email / invalid_code |
请求参数格式错误。 |
| 401 | unauthorized |
缺少 token、token 无效、签名错误或未登录。 |
| 402 | subscription_required |
当前订阅状态不允许扫描。 |
| 404 | not_found |
资源不存在、无权访问或 debug 门禁关闭。 |
| 409 | platform_account_mismatch / subscription_active / agreement_stale |
业务状态冲突。 |
| 426 | ext_outdated |
Chrome 扩展版本低于后端要求。 |
| 429 | too_many_requests / rate_limited |
请求过于频繁或手动扫描达到日限额。 |
| 500 | server_error / config_error / stripe_error / handler_error |
服务端错误、配置缺失或第三方调用失败。 |
| 503 | misconfigured |
生产环境缺少必需配置。 |
公共对象
User
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | Laravel 用户 UUID。 |
email |
string | 邮箱。 |
name |
string|null | 用户名,可为空。 |
emailVerifiedAt |
string|null | 邮箱验证时间,ISO 字符串。 |
ApiUser
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 当前登录用户 UUID。 |
email |
string|null | 当前登录用户邮箱。 |
BrandCore
BrandCore 是 Dashboard 读接口返回的品牌基础对象。不同接口会按页面需要裁剪或追加字段。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 品牌 UUID。 |
name |
string | 品牌名称。 |
timezone |
string|null | 品牌时区,例如 Asia/Kuala_Lumpur。 |
morning_brief_hour |
integer|null | 早报本地小时,0-23。 |
evening_recap_hour |
integer|null | 晚报本地小时,0-23。 |
last_morning_brief_at |
string|null | 最近早报发送时间。 |
last_evening_recap_at |
string|null | 最近晚报发送时间。 |
subscription_status |
string|null | 订阅状态,例如 trial、active、past_due、canceled。 |
trial_ends_at |
string|null | 试用结束时间。 |
subscription_cancel_at |
string|null | 订阅预约取消时间。 |
is_comp |
boolean | 是否内部赠送账号。 |
telegram_chat_id |
string|null | Telegram chat id。 |
platform_account_id |
string|null | 已绑定 Shopee seller id。 |
created_at |
string|null | 创建时间。 |
last_extension_version |
string|null | 最近扩展心跳版本。 |
stores |
array | 店铺列表,元素为 { id: string }。 |
subscription_status 枚举
subscription_status 是 brands 表保存的原始账单状态;前端判断“是否可用 / 是否到期”时还要结合 is_comp、trial_ends_at
和 subscription_cancel_at。
| 值 | 含义 | 到期时间口径 |
|---|---|---|
trial |
试用中。创建首个品牌时后端写入 trial_ends_at = now + 1 day。 |
使用 trial_ends_at 判断试用到期;为空时按试用可用但无倒计时时间处理。 |
active |
Stripe 订阅有效。 | subscription_cancel_at 为空表示当前无已知到期时间;若为未来时间,表示已预约取消,付费权益到 subscription_cancel_at。 |
past_due |
Stripe 付款异常或 unpaid。 |
当前 API 不给确定到期时间;Stripe 后续重试失败并发送 deleted webhook 后会变为 canceled。 |
canceled |
订阅已取消或 Stripe incomplete_expired。 |
已到期,不再使用未来到期时间;后端会清空 subscription_cancel_at。 |
null / 其它值 |
旧数据或异常数据。 | 按无有效订阅处理。 |
有效订阅状态的派生规则:
| 条件 | 派生状态 | 扫描权限 |
|---|---|---|
is_comp = true |
comp |
允许,内部赠送账号优先级最高。 |
subscription_status = trial 且 trial_ends_at 为空或晚于当前 UTC 时间 |
trial_active |
允许。 |
subscription_status = trial 且 trial_ends_at 已过期或无法解析 |
trial_expired |
不允许。 |
subscription_status = active 且 subscription_cancel_at 为空 |
active |
允许。 |
subscription_status = active 且 subscription_cancel_at 晚于当前 UTC 时间 |
active_canceling |
允许,展示“已预约取消”。 |
subscription_status = active 且 subscription_cancel_at 已过期或无法解析 |
canceled |
不允许。 |
subscription_status = past_due |
past_due |
允许,但应提示用户更新付款方式。 |
subscription_status = canceled |
canceled |
不允许。 |
如果前端要展示“订阅到期时间”,优先按以下顺序取值:is_comp=true 时无到期时间;trial 取 trial_ends_at;active 且已预约取消时取
subscription_cancel_at;普通 active 当前没有到期时间字段,因为后端暂未保存 Stripe current_period_end。
ScanRow
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | scan UUID。 |
scanned_at |
string | 扫描时间。 |
status |
string | ok、partial 或 failed。 |
error |
string|null | 扩展提取错误文本。 |
payload |
object|null | 扫描原始 payload。 |
telegram_message_id |
integer|null | 手动扫描 Telegram 回声消息 id。 |
AlertRow
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | alert UUID。 |
rule_id |
string | 规则 ID。 |
priority |
string | 告警优先级,例如 P1、P2。 |
confidence |
string | 置信度。 |
title |
string | 标题。 |
diagnosis |
string | 诊断文案。 |
action |
string | 建议动作。 |
data |
object | 告警附加 JSON。 |
acknowledged |
boolean | 是否已确认。 |
created_at |
string | 创建时间。 |
StoreBrief
| 字段 | 类型 | 说明 |
|---|---|---|
verdict_tier |
string|null | AI 今日结论等级。 |
verdict_headline |
string|null | AI 今日结论标题。 |
verdict_factors |
array | AI 结论因素。 |
verdict_action |
string|null | AI 建议行动。 |
bottleneck_stage |
string|null | 增长瓶颈阶段。 |
growth_diagnosis_json |
object|null | AI 增长诊断 JSON。 |
top_actions_json |
array|null | AI Top Actions。 |
confidence |
object | AI 置信度 JSON。 |
系统接口
GET /api/health
鉴权:无。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/health"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.service |
string | 固定为 storeai-api。 |
data.version |
string|null | config('app.version')。 |
data.environment |
string | 当前 Laravel 环境。 |
认证接口
POST /api/auth/register
鉴权:无。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | email |
string | 是 | 合法邮箱,服务端会转小写并 trim。 |
| body | password |
string | 是 | 8-128 字符。 |
| body | name |
string|null | 否 | 最多 120 字符,空字符串按 null。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/auth/register" \
-H "Content-Type: application/json" \
-d '{
"email": "owner@example.test",
"password": "secure-password",
"name": "Store Owner"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.requiresEmailVerification |
boolean | 固定为 true,表示必须先完成邮箱验证码校验。 |
data.email |
string | 标准化后的邮箱。 |
data.user |
User |
新建用户。 |
data.sent |
boolean | 是否已发送邮箱验证码。 |
data.expiresInSeconds |
integer | 验证码剩余有效秒数。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
邮箱或密码格式错误。 |
| 400 | duplicate_email |
邮箱已注册。 |
备注:注册成功后会发送 6 位邮箱验证码,但不会签发登录 token;只有 /api/auth/email/verify-code 校验成功后才会签发 Web
Sanctum token。users.id 同时作为业务 account_id 使用。
POST /api/auth/login
鉴权:无。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | email |
string | 是 | 合法邮箱。 |
| body | password |
string | 是 | 8-128 字符。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/auth/login" \
-H "Content-Type: application/json" \
-d '{
"email": "owner@example.test",
"password": "secure-password"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.user |
User |
登录用户。邮箱未验证时也会返回,emailVerifiedAt 为 null。 |
data.token |
string | 邮箱已验证时返回的新签发 Sanctum Bearer token。 |
data.requiresEmailVerification |
boolean | 邮箱未验证时返回 true。 |
data.email |
string | 邮箱未验证时返回的标准化邮箱。 |
data.sent |
boolean | 邮箱未验证时表示是否已发送新的验证码。 |
data.expiresInSeconds |
integer | 新验证码剩余有效秒数。 |
data.retryAfterSeconds |
integer | 验证码重发限流时返回。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
入参格式错误。 |
| 401 | invalid_credentials |
邮箱或密码错误。 |
备注:邮箱已验证时,登录成功会先删除当前账号所有旧 Sanctum token,包括旧 Web token 和扩展 token,再签发新的 Web
token;扩展需要重新配对。邮箱未验证时不会返回 token,会清理该账号旧 token 并返回 requiresEmailVerification=true
,前端应进入邮箱验证码流程。
GET /api/auth/me
鉴权:Sanctum。仅 Web/Dashboard token 可调用,扩展 token 会被 dashboard.token 门禁拒绝。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/auth/me" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.user |
User|null |
当前 token 对应用户。 |
POST /api/auth/logout
鉴权:Sanctum。
入参:无。
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/auth/logout" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
备注:退出登录会删除当前账号所有 Sanctum token,包括 Web token 和扩展 token,让 Dashboard 和 Chrome 扩展同时下线。
POST /api/auth/rotate-session
鉴权:Sanctum。
入参:无。
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/auth/rotate-session" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.rotated |
boolean | 固定为 false。 |
data.reason |
string | 固定为 sanctum_bearer_tokens_are_already_device_scoped。 |
备注:兼容 Origin 的 Supabase session rotate 调用;Sanctum 下是 no-op。
PATCH /api/auth/password
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | password |
string | 是 | 新密码,8-128 字符。 |
请求示例:
curl -X PATCH "{BACKEND_API_BASE_URL}/api/auth/password" \
-H "Authorization: Bearer <sanctum_token>" \
-H "Content-Type: application/json" \
-d '{
"password": "new-secure-password"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
密码长度不合法。 |
GET /api/auth/check-verified
鉴权:无。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| query | email |
string | 是 | 要查询验证状态的邮箱。 |
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/auth/check-verified?email=owner%40example.test"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.verified |
boolean | users.email_verified_at 是否非空。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_email |
邮箱格式错误。 |
备注:响应头包含 cache-control: no-store。
POST /api/auth/email/verification-code
鉴权:无。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | email |
string | 是 | 要发送验证码的邮箱。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/auth/email/verification-code" \
-H "Content-Type: application/json" \
-d '{
"email": "owner@example.test"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.sent |
boolean | 是否触发发送。未知邮箱也返回 true 以防账号枚举。 |
data.alreadyVerified |
boolean | 已验证邮箱返回 true,此时 sent=false。 |
data.expiresInSeconds |
integer | 验证码剩余有效秒数,实际发送时返回。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_email |
邮箱格式错误。 |
| 429 | too_many_requests |
发送过于频繁,响应含 retryAfterSeconds。 |
POST /api/auth/email/verify-code
鉴权:无。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | email |
string | 是 | 邮箱。 |
| body | code |
string | 是 | 6 位数字验证码。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/auth/email/verify-code" \
-H "Content-Type: application/json" \
-d '{
"email": "owner@example.test",
"code": "123456"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.verified |
boolean | 成功为 true。 |
data.user |
User |
已验证用户。 |
data.token |
string | 新签发 Sanctum Bearer token。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_email |
邮箱格式错误。 |
| 400 | invalid_code |
验证码格式错误、邮箱不存在、验证码不存在或验证码错误。 |
| 400 | expired_code |
验证码已过期。 |
备注:校验成功等价于一次 Web 登录,会删除当前账号所有旧 Web/扩展 token,并签发新 Web token。
POST /api/auth/password/forgot
鉴权:无。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | email |
string | 是 | 要重置密码的邮箱。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/auth/password/forgot" \
-H "Content-Type: application/json" \
-d '{
"email": "owner@example.test"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.sent |
boolean | 固定按成功处理;未知邮箱不发信但仍返回 true。 |
data.expiresInSeconds |
integer | 验证码有效秒数,实际发信时返回。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_email |
邮箱格式错误。 |
| 429 | too_many_requests |
发送过于频繁,响应含 retryAfterSeconds。 |
POST /api/auth/password/reset
鉴权:无。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | email |
string | 是 | 邮箱。 |
| body | code |
string | 是 | 6 位数字验证码。 |
| body | password |
string | 是 | 新密码,8-128 字符。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/auth/password/reset" \
-H "Content-Type: application/json" \
-d '{
"email": "owner@example.test",
"code": "123456",
"password": "new-secure-password"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.reset |
boolean | 成功为 true。 |
data.user |
User |
重置密码后的用户。 |
data.token |
string | 邮箱已验证时返回的新签发 Sanctum Bearer token。 |
data.requiresEmailVerification |
boolean | 账号邮箱未验证时返回 true,此时不返回 token。 |
data.email |
string | 账号邮箱未验证时返回的标准化邮箱。 |
data.sent |
boolean | 账号邮箱未验证时表示是否已发送邮箱验证验证码。 |
data.expiresInSeconds |
integer | 邮箱验证验证码剩余有效秒数。 |
data.retryAfterSeconds |
integer | 邮箱验证验证码重发限流时返回。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_email |
邮箱格式错误。 |
| 400 | invalid_code |
验证码格式错误、邮箱不存在、验证码不存在或验证码错误。 |
| 400 | expired_code |
验证码已过期。 |
| 400 | invalid_payload |
新密码不符合长度要求。 |
备注:成功后会消费该邮箱所有未使用重置码。若账号邮箱已验证,会删除当前账号所有旧 Web/扩展 token,并签发新 Web
token;若账号邮箱未验证,只更新密码、清理旧 token、发送邮箱验证验证码并返回 requiresEmailVerification=true。
POST /api/auth/extension-pair
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | label |
string | 否 | 扩展设备标签,最多保留 120 字符。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/auth/extension-pair" \
-H "Authorization: Bearer <sanctum_token>" \
-H "Content-Type: application/json" \
-d '{
"label": "Chrome - Store Owner Laptop"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.token |
string | 扩展专用 Sanctum plaintext token,格式为 Sanctum 标准 <id>|<secret>。 |
data.expiresAt |
string | 扩展 token 过期时间。 |
data.userEmail |
string|null | 当前用户邮箱。 |
data.apiBaseUrl |
string | 后续扩展请求使用的 API base URL。 |
data.priorTokensRevoked |
integer | 本次撤销的旧扩展 token 数量。 |
data.priorTokensFromOtherDevice |
boolean | 是否踢掉了其他设备 token。 |
备注:扩展 token 明文只返回一次,数据库写入 personal_access_tokens,name 为 storeai_extension 或
storeai_extension:<label>,abilities 只包含扩展权限;新配对会删除该账号所有旧扩展 token,保持扩展侧单设备策略,但不会删除当前
Web token。普通 Dashboard Sanctum token 不能调用扩展接口。
POST /api/auth/refresh-extension-token
鉴权:扩展机器 token。
必需 header:Authorization: Bearer <extension_sanctum_token>。
入参:无。
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/auth/refresh-extension-token" \
-H "Authorization: Bearer <extension_sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.token |
string | 新扩展专用 Sanctum plaintext token,格式为 Sanctum 标准 <id>|<secret>。 |
data.expiresAt |
string | 新 token 过期时间。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 401 | unauthorized |
缺少、格式错误、已删除、已过期或缺少对应扩展 ability 的 token。 |
备注:刷新成功会先创建新扩展 token,再删除同账号其它扩展 token;删除范围只限扩展 token,不删除 Web token。
Dashboard 与 Onboarding
GET /api/dashboard/shell
鉴权:Sanctum。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/dashboard/shell" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.user |
ApiUser |
当前用户。 |
data.brand |
object|null | 当前账号第一品牌的 shell 信息。 |
data.brand.id |
string | 品牌 UUID。 |
data.brand.name |
string | 品牌名。 |
data.brand.subscription_status |
string | 订阅状态,枚举见 BrandCore.subscription_status。 |
data.brand.trial_ends_at |
string|null | 试用结束时间。 |
data.brand.subscription_cancel_at |
string|null | 预约取消时间。 |
data.brand.is_comp |
boolean | 是否内部赠送账号。 |
GET /api/dashboard
鉴权:Sanctum。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/dashboard" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.user |
ApiUser |
当前用户。 |
data.brand |
BrandCore|null |
当前账号第一品牌。无品牌时只返回 user 和 brand:null。 |
data.latestScan |
ScanRow|null |
最近一次扫描。 |
data.previousScan |
ScanRow|null |
最近一次可对比的历史成功扫描。 |
data.scanDots |
array | 最近 12 次扫描轻量列表,元素 { id, scanned_at, status }。 |
data.alerts |
AlertRow[] |
最近扫描下的告警列表。 |
data.competitorMeta |
array | 竞品元数据,元素 { product_url, label }。 |
data.storeBrief |
StoreBrief|null |
最近扫描 AI brief。 |
data.dismissedKeys |
string[] | 当前品牌本地日已 Mark done 的 action key。 |
data.skuMatrixEntries |
array|null | 当日 SKU Matrix 条目,来自 sku_matrix.matrix_json。 |
data.skuMatrixConfidence |
string|null | SKU Matrix 置信度。 |
data.gmvRows |
array | 最近 40 条成功 scan 的 { scanned_at, payload }。 |
GET /api/dashboard/settings
鉴权:Sanctum。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/dashboard/settings" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.user |
ApiUser |
当前用户。 |
data.brand |
BrandCore|null |
设置页品牌信息;附加 thresholds、platform_account_id_bound_at、stores。 |
data.manualUsedToday |
integer | 品牌本地日内已使用手动扫描次数。 |
data.lastAutoScanAt |
string|null | 最近一次 scheduled scan 时间。 |
data.competitors |
array | 竞品列表,元素 { id, product_url, label, created_at }。 |
GET /api/dashboard/billing
鉴权:Sanctum。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/dashboard/billing" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.user |
ApiUser |
当前用户。 |
data.brand |
BrandCore|null |
账单页品牌信息,含 Stripe customer/subscription id 字段。 |
data.billing |
null | 当前固定为 null,保留给前端兼容。 |
GET /api/onboarding/status
鉴权:Sanctum。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/onboarding/status" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.user |
ApiUser |
当前用户。 |
data.brand |
BrandCore|null |
Onboarding 所需品牌信息。 |
data.lastScanAt |
string|null | 当前店铺最近一次 ok 或 partial scan 时间。 |
当前用户与扩展接口
POST /api/me/brand
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | name |
string | 是 | 品牌名称,trim 后 1-80 字符。 |
| body | timezone |
string | 是 | PHP 支持的时区名,最长 64 字符。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/me/brand" \
-H "Authorization: Bearer <sanctum_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "StoreAI Demo Brand",
"timezone": "Asia/Kuala_Lumpur"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.brand.id |
string | 新品牌 UUID。 |
data.brand.name |
string | 品牌名称。 |
data.brand.timezone |
string | 品牌时区。 |
data.store.id |
string | 同步创建的店铺 UUID。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
品牌名或时区格式错误。 |
| 400 | brand_exists |
当前账号已有品牌。 |
PATCH /api/me/brand
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | name |
string | 否 | 品牌名称,trim 后 1-80 字符。 |
| body | timezone |
string | 否 | PHP 支持的时区名,最长 64 字符。 |
请求示例:
curl -X PATCH "{BACKEND_API_BASE_URL}/api/me/brand" \
-H "Authorization: Bearer <sanctum_token>" \
-H "Content-Type: application/json" \
-d '{
"name": "StoreAI Demo Brand",
"timezone": "Asia/Kuala_Lumpur"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.brand |
object | 有变更时返回 { id, name, timezone }。 |
data.noop |
boolean | 没有可更新字段时返回 true。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
品牌名或时区格式错误。 |
| 500 | server_error |
当前账号没有可更新品牌或更新后读取失败。 |
DELETE /api/me/account
鉴权:Sanctum。
入参:无。
请求示例:
curl -X DELETE "{BACKEND_API_BASE_URL}/api/me/account" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 409 | subscription_active |
品牌仍有 active 或 past_due 订阅,必须先取消或等待周期结束。 |
POST /api/me/action/dismiss
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | action_key |
string | 是 | Top Action key,1-200 字符。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/me/action/dismiss" \
-H "Authorization: Bearer <sanctum_token>" \
-H "Content-Type: application/json" \
-d '{
"action_key": "sku:SKU-001:fix_conversion"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | no_brand |
当前账号没有品牌。 |
| 400 | invalid_payload |
action_key 缺失或长度非法。 |
备注:dismiss_date 使用品牌本地日开始时间转 UTC 后的日期键,与 Dashboard 和定时报表过滤逻辑一致。
GET /api/me/competitors
鉴权:扩展机器 token。
必需 header:Authorization: Bearer <extension_sanctum_token>、X-Ext-Version。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/me/competitors" \
-H "Authorization: Bearer <extension_sanctum_token>" \
-H "X-Ext-Version: 0.1.0"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.competitors |
array | 当前账号所有品牌已登记竞品。 |
data.competitors[].id |
string | 竞品 UUID。 |
data.competitors[].brand_id |
string | 品牌 UUID。 |
data.competitors[].product_url |
string | Shopee 商品 URL。 |
data.competitors[].label |
string|null | 竞品备注。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 401 | unauthorized |
扩展 token 缺失或无效。 |
| 426 | ext_outdated |
扩展版本过旧。 |
GET /api/me/scan-quota
鉴权:扩展机器 token。
必需 header:Authorization: Bearer <extension_sanctum_token>、X-Ext-Version。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/me/scan-quota" \
-H "Authorization: Bearer <extension_sanctum_token>" \
-H "X-Ext-Version: 0.1.0"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.allowed |
boolean | 是否允许手动扫描。 |
data.reason |
string | 不允许时返回,例如 subscription_required、manual_cap_reached。 |
message |
string | 不允许时的用户提示。 |
data.manualUsed |
integer | 品牌本地日已使用手动扫描次数。 |
data.manualCap |
integer | 每日手动扫描上限,默认 3。 |
data.remaining |
integer | 剩余手动扫描次数。 |
data.boundPlatformAccountId |
string|null | 品牌已绑定 Shopee seller id。 |
data.subState |
string | 计算后的订阅状态。 |
data.trialEndsAt |
string|null | 试用结束时间。 |
data.latestExtVersion |
string | 后端配置的最新扩展版本。 |
data.extDownloadUrl |
string | 扩展下载地址。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 401 | unauthorized |
扩展 token 缺失或无效。 |
| 404 | not_found |
当前账号没有品牌或品牌没有店铺。 |
| 426 | ext_outdated |
扩展版本过旧。 |
POST /api/extension/heartbeat
鉴权:扩展机器 token。
必需 header:Authorization: Bearer <extension_sanctum_token>。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | skip_reason |
string|null | 是 | 扩展本轮跳过扫描原因,可为 null。 |
| body | ext_version |
string | 是 | 扩展版本,1-40 字符。 |
| body | tick_at |
string | 是 | 客户端心跳时间,可被解析为 datetime。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/extension/heartbeat" \
-H "Authorization: Bearer <extension_sanctum_token>" \
-H "Content-Type: application/json" \
-d '{
"skip_reason": "Shopee tab not open",
"ext_version": "0.1.0",
"tick_at": "2026-05-08T10:15:00Z"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
code |
string | 更新失败时可能返回 update_failed。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
请求体格式错误。 |
| 401 | unauthorized |
扩展 token 缺失或无效。 |
| 404 | not_found |
当前 token 所属账号没有品牌。 |
品牌设置接口
GET /api/brands/{id}/competitors
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| path | id |
string | 是 | 品牌 UUID。 |
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/brands/00000000-0000-4000-8000-000000000101/competitors" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.competitors |
array | 竞品列表。无权访问品牌时返回空数组,不泄露品牌存在性。 |
data.competitors[].id |
string | 竞品 UUID。 |
data.competitors[].product_url |
string | Shopee 商品 URL。 |
data.competitors[].label |
string|null | 竞品备注。 |
data.competitors[].created_at |
string|null | 创建时间。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
品牌 id 不是 UUID。 |
POST /api/brands/{id}/competitors
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| path | id |
string | 是 | 品牌 UUID。 |
| body | productUrl |
string | 是 | Shopee 商品页 URL,必须匹配 -i.<shopId>.<itemId>。 |
| body | label |
string|null | 否 | 竞品备注,最多 80 字符。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/brands/00000000-0000-4000-8000-000000000101/competitors" \
-H "Authorization: Bearer <sanctum_token>" \
-H "Content-Type: application/json" \
-d '{
"productUrl": "https://shopee.com.my/example-product-i.123456.987654321",
"label": "Main competitor"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.competitor.id |
string | 新竞品 UUID。 |
data.competitor.product_url |
string | Shopee 商品 URL。 |
data.competitor.label |
string|null | 竞品备注。 |
data.competitor.created_at |
string|null | 创建时间。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
URL 或备注格式错误。 |
| 400 | duplicate |
已登记相同 URL。 |
| 400 | over_limit |
超过每品牌 10 个竞品上限。 |
| 404 | not_found |
品牌不存在或不属于当前用户。 |
DELETE /api/brands/{id}/competitors/{competitorId}
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| path | id |
string | 是 | 品牌 UUID。 |
| path | competitorId |
string | 是 | 竞品 UUID。 |
请求示例:
curl -X DELETE "{BACKEND_API_BASE_URL}/api/brands/00000000-0000-4000-8000-000000000101/competitors/00000000-0000-4000-8000-000000000201" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
path UUID 格式错误。 |
备注:删除是幂等的,行不存在或无权删除也返回成功。
PATCH /api/brands/{id}/notifications
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| path | id |
string | 是 | 品牌 UUID。 |
| body | morningBriefHour |
integer | 否 | 早报本地小时,0-23。 |
| body | eveningRecapHour |
integer | 否 | 晚报本地小时,0-23。 |
| body | pushMode |
string | 否 | 兼容旧字段,只接受 daily_digest 或 instant_alert,不写库。 |
| body | dailyReportHour |
integer | 否 | 兼容旧字段,仅校验,不写库。 |
| body | silentStartHour |
integer | 否 | 兼容旧字段,仅校验,不写库。 |
| body | silentEndHour |
integer | 否 | 兼容旧字段,仅校验,不写库。 |
请求示例:
curl -X PATCH "{BACKEND_API_BASE_URL}/api/brands/00000000-0000-4000-8000-000000000101/notifications" \
-H "Authorization: Bearer <sanctum_token>" \
-H "Content-Type: application/json" \
-d '{
"morningBriefHour": 9,
"eveningRecapHour": 18,
"pushMode": "daily_digest",
"dailyReportHour": 9,
"silentStartHour": 22,
"silentEndHour": 8
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.morning_brief_hour |
integer | 更新后的早报小时,有实际更新时返回。 |
data.evening_recap_hour |
integer | 更新后的晚报小时,有实际更新时返回。 |
data.noop |
boolean | 没有可写字段时返回 true。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
小时值不是整数、范围不在 0-23,或同一请求内早晚报小时相同。 |
| 404 | not_found |
品牌不存在或不属于当前用户。 |
PATCH /api/brands/{id}/telegram
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| path | id |
string | 是 | 品牌 UUID。 |
| body | chatId |
string|null | 是 | Telegram chat id,必须匹配 ^-?\d{1,20}$;传 null 表示解绑。 |
请求示例:
curl -X PATCH "{BACKEND_API_BASE_URL}/api/brands/00000000-0000-4000-8000-000000000101/telegram" \
-H "Authorization: Bearer <sanctum_token>" \
-H "Content-Type: application/json" \
-d '{
"chatId": "-1001234567890"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.telegramChatId |
string|null | 更新后的 Telegram chat id。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
chatId 缺失或格式错误。 |
| 404 | not_found |
品牌不存在或不属于当前用户。 |
POST /api/brands/{id}/telegram/pair
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| path | id |
string | 是 | 品牌 UUID。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/brands/00000000-0000-4000-8000-000000000101/telegram/pair" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.token |
string | 一次性配对 token,格式 pair_<36hex>。 |
data.expiresAt |
string | 过期时间。 |
data.deeplink |
string | Telegram https://t.me/<bot>?start=<token> 链接。 |
data.ttlSeconds |
integer | 固定 900 秒。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 404 | not_found |
品牌不存在或不属于当前用户。 |
| 500 | misconfigured |
缺少 TELEGRAM_BOT_HANDLE。 |
PATCH /api/brands/{id}/thresholds
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| path | id |
string | 是 | 品牌 UUID。 |
| body | thresholds |
object | 是 | 阈值覆盖对象;未知 key 会忽略;值为 null 表示移除覆盖。 |
请求示例:
curl -X PATCH "{BACKEND_API_BASE_URL}/api/brands/00000000-0000-4000-8000-000000000101/thresholds" \
-H "Authorization: Bearer <sanctum_token>" \
-H "Content-Type: application/json" \
-d '{
"thresholds": {
"SALES_01_GMV_DECLINE": 0.25,
"SALES_01_MIN_ORDERS": 5,
"SALES_02_ZERO_HOURS": 3,
"SALES_04_DAYS_LEFT": 4,
"SALES_05_RATING_MAX": 3,
"AD_01_MIN_SPEND": 50,
"AD_02_KEYWORD_SHARE": 0.5,
"AD_03_ROAS_MIN": 2.5,
"COMP_01_GAP": 0.12,
"COMP_02_DROP": null
}
}'
允许的 thresholds key:
| key | 类型 | 范围 | 说明 |
|---|---|---|---|
SALES_01_GMV_DECLINE |
number | 0.1-0.5 | GMV 下降阈值。 |
SALES_01_MIN_ORDERS |
integer | 3-20 | 最小订单数。 |
SALES_02_ZERO_HOURS |
integer | 1-6 | 连续零单小时数。 |
SALES_04_DAYS_LEFT |
integer | 1-7 | 库存剩余天数。 |
SALES_05_RATING_MAX |
integer | 1-4 | 低评分上限。 |
AD_01_MIN_SPEND |
integer | 10-200 | 广告最低花费。 |
AD_02_KEYWORD_SHARE |
number | 0.3-0.8 | 关键词花费占比。 |
AD_03_ROAS_MIN |
number | 1.5-5.0 | 最低 ROAS。 |
COMP_01_GAP |
number | 0.05-0.3 | 竞品价差阈值。 |
COMP_02_DROP |
number | 0.05-0.3 | 竞品降价阈值。 |
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.thresholds |
object | 合并保存后的品牌阈值覆盖对象。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
thresholds 不是对象、值不是数字/null、整数 key 传小数或超出范围。 |
| 404 | not_found |
品牌不存在或不属于当前用户。 |
告警接口
POST /api/alerts/{id}/ack
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| path | id |
string | 是 | Alert UUID。 |
| body | ack |
boolean | 否 | 是否确认;缺省为 true。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/alerts/00000000-0000-4000-8000-000000000301/ack" \
-H "Authorization: Bearer <sanctum_token>" \
-H "Content-Type: application/json" \
-d '{
"ack": true
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.id |
string | Alert UUID。 |
data.acknowledged |
boolean | 更新后的确认状态。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
Alert id 不是 UUID。 |
| 404 | not_found |
Alert 不存在或不属于当前用户品牌。 |
扫描上传接口
POST /api/ingest/scan
鉴权:扩展机器 token。
必需 header:Authorization: Bearer <extension_sanctum_token>、X-Ext-Version。
顶层入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | brandId |
string | 是 | 品牌 UUID。 |
| body | storeId |
string | 是 | 店铺 UUID。 |
| body | scannedAt |
string | 是 | 扫描时间,可解析为 datetime。 |
| body | payload |
object | 是 | 扫描业务数据,见下方 schema。 |
| body | extractorStatus |
string | 是 | ok、partial 或 failed。 |
| body | extractorErrors |
string[] | 否 | 提取错误列表,最多 50 项,缺省 []。 |
| body | trigger |
string | 否 | manual 或 scheduled,缺省 manual。 |
| body | platformAccountId |
string|null | 否 | Shopee seller id,1-64 字符;4 位以上纯数字才会参与绑定/比对。 |
| body | fieldMeta |
object | 否 | 字段采集来源元数据,缺省 {}。 |
payload.today:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
orders |
integer | 是 | 今日订单数,>=0。 |
gmv_cents |
integer | 是 | 今日 GMV 分,>=0。 |
cancel_rate |
number | 是 | 取消率,0-1。 |
return_rate |
number | 是 | 退货率,0-1。 |
gmv_delta_yesterday_pct |
number|null | 否 | GMV 较昨日变化比例。 |
gmv_net_cents |
integer|null | 否 | 净 GMV 分,>=0。 |
conversion_rate |
number|null | 否 | 转化率。 |
visitors |
integer|null | 否 | 访客数,>=0。 |
product_clicks |
integer|null | 否 | 商品点击数,>=0。 |
aov_cents |
integer|null | 否 | 客单价分,>=0。 |
orders_delta_yesterday_pct |
number|null | 否 | 订单较昨日变化比例。 |
conversion_rate_delta_yesterday_pct |
number|null | 否 | 转化率较昨日变化比例。 |
visitors_delta_yesterday_pct |
number|null | 否 | 访客较昨日变化比例。 |
product_clicks_delta_yesterday_pct |
number|null | 否 | 商品点击较昨日变化比例。 |
aov_delta_yesterday_pct |
number|null | 否 | 客单价较昨日变化比例。 |
payload.skus[]:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
sku_id |
string | 是 | SKU id。 |
name |
string | 是 | SKU 名称。 |
price_cents |
integer|null | 否 | 价格分,>=0。 |
l30d_sales |
integer|null | 否 | 近 30 天销量,>=0。 |
l30d_impressions |
integer|null | 否 | 近 30 天曝光,>=0。 |
impressions |
integer | 是 | 曝光,>=0。 |
clicks |
integer | 是 | 点击,>=0。 |
add_to_cart |
integer | 是 | 加购,>=0。 |
orders |
integer | 是 | 订单,>=0。 |
gmv_cents |
integer | 是 | GMV 分,>=0。 |
stock |
integer | 是 | 库存,>=0。 |
status |
string | 是 | on_sale、unlisted 或 out_of_stock。 |
payload.ads[]:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
campaign_id |
string | 是 | 广告活动 id。 |
campaign_name |
string | 是 | 广告活动名称。 |
type |
string | 否 | 广告类型,缺省 unknown。 |
state |
string | 否 | ongoing、paused 或 unknown,缺省 unknown。 |
spend_cents |
integer | 是 | 花费分,>=0。 |
clicks |
integer | 是 | 点击,>=0。 |
orders |
integer | 是 | 订单,>=0。 |
revenue_cents |
integer | 是 | 收入分,>=0。 |
impressions |
integer | 否 | 曝光,>=0,缺省 0。 |
roas |
number | 否 | ROAS,>=0,缺省 0。 |
target_roas |
number|null | 否 | 目标 ROAS,>=0。 |
daily_budget_cents |
integer|null | 否 | 日预算分,>=0。 |
keywords |
array | 是 | 关键词列表。 |
keywords[].keyword |
string | 是 | 关键词。 |
keywords[].spend_cents |
integer | 是 | 关键词花费分,>=0。 |
keywords[].clicks |
integer | 是 | 关键词点击,>=0。 |
keywords[].orders |
integer | 是 | 关键词订单,>=0。 |
其他 payload 字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
store_id |
string | 是 | 店铺 UUID。 |
scanned_at |
string | 是 | 扫描时间。 |
recent_3h[] |
array | 是 | 元素 { hour:string, orders:integer>=0, gmv_cents:integer>=0 }。 |
reviews[] |
array | 是 | 元素 { review_id, sku_id:null|string, rating:1-5, text, replied:boolean, created_at }。 |
competitors[] |
array | 是 | 元素 { product_url:url, price_cents:integer>=0, rating:null|number 0-5, sold_label:null|string }。 |
review_summary |
object|null | 否 | 包含 shop_rating、total_reviews_l30d、unanswered_negative、rating_histogram。 |
product_aggregates |
object|null | 否 | 数值字典,值可为 number 或 null。 |
traffic_sources |
array|null | 否 | 流量来源数组,含 source、sales、clicks、conversion 等字段。 |
shop_health |
object|null | 否 | 店铺健康对象;传 null 表示本次未采集,传 object 时内部字段见下表。 |
payload.shop_health:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
rating_overall |
string|null | 是 | 店铺健康评级,允许 excellent、good、fair、poor 或 null。 |
penalty_points |
integer|null | 是 | 当前罚分,>=0,未知传 null。 |
penalty_threshold |
integer|null | 是 | 触发风险提示的罚分阈值,>=1,未知传 null。 |
metrics |
object | 是 | 健康指标字典,key 为指标名,value 见下方 metrics.*。 |
metrics.*.value |
number|null | 是 | 指标当前值。 |
metrics.*.target |
number|null | 是 | 指标目标值。 |
metrics.*.target_op |
string|null | 是 | 达标比较符,允许 lt、lte、gt、gte 或 null。 |
metrics.*.counts_toward_score |
boolean | 是 | 是否参与店铺健康评分。 |
fieldMeta 元素:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
source |
string | 是 | fetch_hook、dom、json_ld、og_meta、aggregate 或 missing。 |
selectorMatch |
string | 否 | primary、fallback 或 text_anchor。 |
shopeeDataStamp |
string | 否 | Shopee 数据戳。 |
ageMinutes |
number | 否 | 数据年龄分钟数。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/ingest/scan" \
-H "Authorization: Bearer <extension_sanctum_token>" \
-H "X-Ext-Version: 0.1.0" \
-H "Content-Type: application/json" \
-d '{
"brandId": "00000000-0000-4000-8000-000000000101",
"storeId": "00000000-0000-4000-8000-000000000102",
"scannedAt": "2026-05-08T10:15:00Z",
"extractorStatus": "ok",
"extractorErrors": [],
"trigger": "manual",
"platformAccountId": "1234567890",
"fieldMeta": {
"today.orders": {
"source": "fetch_hook",
"selectorMatch": "primary",
"shopeeDataStamp": "seller-center-v1",
"ageMinutes": 2
},
"ads[0].spend_cents": {
"source": "dom",
"selectorMatch": "fallback",
"ageMinutes": 5
}
},
"payload": {
"store_id": "00000000-0000-4000-8000-000000000102",
"scanned_at": "2026-05-08T10:15:00Z",
"today": {
"orders": 18,
"gmv_cents": 258000,
"cancel_rate": 0.02,
"return_rate": 0.01,
"gmv_delta_yesterday_pct": -0.18,
"gmv_net_cents": 252000,
"conversion_rate": 0.042,
"visitors": 1200,
"product_clicks": 260,
"aov_cents": 14333,
"orders_delta_yesterday_pct": -0.12,
"conversion_rate_delta_yesterday_pct": -0.08,
"visitors_delta_yesterday_pct": 0.06,
"product_clicks_delta_yesterday_pct": -0.03,
"aov_delta_yesterday_pct": -0.04
},
"recent_3h": [
{ "hour": "2026-05-08T08:00:00Z", "orders": 3, "gmv_cents": 42000 },
{ "hour": "2026-05-08T09:00:00Z", "orders": 0, "gmv_cents": 0 },
{ "hour": "2026-05-08T10:00:00Z", "orders": 1, "gmv_cents": 12000 }
],
"skus": [
{
"sku_id": "SKU-001",
"name": "Hydrating Serum 30ml",
"price_cents": 6900,
"l30d_sales": 180,
"l30d_impressions": 12000,
"impressions": 2800,
"clicks": 210,
"add_to_cart": 36,
"orders": 12,
"gmv_cents": 82800,
"stock": 24,
"status": "on_sale"
}
],
"ads": [
{
"campaign_id": "AD-001",
"campaign_name": "Hero SKU Search Ads",
"type": "search",
"state": "ongoing",
"spend_cents": 4200,
"clicks": 96,
"orders": 5,
"revenue_cents": 34500,
"impressions": 3600,
"roas": 8.21,
"target_roas": 5,
"daily_budget_cents": 10000,
"keywords": [
{ "keyword": "serum", "spend_cents": 1600, "clicks": 36, "orders": 1 },
{ "keyword": "hydrating serum", "spend_cents": 2600, "clicks": 60, "orders": 4 }
]
}
],
"reviews": [
{
"review_id": "REV-001",
"sku_id": "SKU-001",
"rating": 2,
"text": "Bottle leaked during shipping",
"replied": false,
"created_at": "2026-05-08T04:00:00Z"
}
],
"competitors": [
{
"product_url": "https://shopee.com.my/example-product-i.123456.987654321",
"price_cents": 6200,
"rating": 4.8,
"sold_label": "1.2k sold"
}
],
"review_summary": {
"shop_rating": 4.7,
"total_reviews_l30d": 42,
"unanswered_negative": 3,
"rating_histogram": { "1": 1, "2": 2, "3": 4, "4": 12, "5": 23 }
},
"product_aggregates": {
"avg_price_cents": 7200,
"active_sku_count": 18,
"out_of_stock_count": 2
},
"traffic_sources": [
{
"source": "search",
"sales": 12,
"clicks": 180,
"conversion_rate": 0.066,
"gmv_cents": 168000
}
],
"shop_health": {
"rating_overall": "good",
"penalty_points": 0,
"penalty_threshold": 3,
"metrics": {
"late_shipment_rate": {
"value": 0.01,
"target": 0.03,
"target_op": "lte",
"counts_toward_score": true
},
"chat_response_rate": {
"value": 0.92,
"target": 0.85,
"target_op": "gte",
"counts_toward_score": true
}
}
}
}
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.scanId |
string | 新 scan UUID。 |
data.alertsEmitted |
integer | 本次规则引擎产生的 alert 数量。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
JSON 非法或 schema 校验失败。 |
| 401 | unauthorized |
扩展 token 缺失或无效。 |
| 402 | subscription_required |
当前订阅状态不允许扫描。 |
| 403 | brand_mismatch |
品牌不属于 token 账号,或店铺不属于品牌。 |
| 409 | platform_account_mismatch |
当前 Shopee seller id 与品牌已绑定 seller id 不一致。 |
| 426 | ext_outdated |
扩展版本过旧。 |
| 429 | rate_limited |
手动扫描达到每日上限。 |
备注:成功后会写入 scans、SKU/ad/competitor snapshots,执行规则告警和 AI 派生数据生成;手动扫描会尝试发送 Telegram 回声,scheduled 扫描会尝试补发 catch-up brief。
Cron 定时报表接口
GET|POST /api/cron/scheduled-report
鉴权:Cron secret。生产环境要求 CRON_SECRET 已配置并通过 Authorization: Bearer <CRON_SECRET> 传入。非生产环境未配置
secret 时开放。
入参:无。
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/cron/scheduled-report" \
-H "Authorization: Bearer <CRON_SECRET>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.runAt |
string | 本次 cron UTC 执行时间。 |
data.report.scannedBrands |
integer | 扫描的品牌数量。 |
data.report.fired |
integer | 实际发送报表数量。 |
data.report.skipped.wrong_hour |
integer | 未到品牌本地发送窗口数量。 |
data.report.skipped.cooldown |
integer | 仍在 22 小时冷却期或并发抢占失败数量。 |
data.report.skipped.no_channel |
integer | 无 Telegram/WhatsApp 渠道数量。 |
data.report.skipped.no_store |
integer | 无店铺数量。 |
data.report.skipped.invalid_config |
integer | 报表小时配置非法数量。 |
data.report.errors |
integer | 单品牌处理异常数量。 |
data.trialReport.scanned |
integer | 扫描试用到期提醒的品牌数量。 |
data.trialReport.sent4h |
integer | 发送 4 小时试用提醒数量。 |
data.trialReport.sent1h |
integer | 发送 1 小时试用提醒数量。 |
data.trialReport.errors |
integer | 试用提醒异常数量。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 401 | unauthorized |
Cron secret 不匹配。 |
| 503 | misconfigured |
生产环境未配置 CRON_SECRET。 |
备注:服务端按品牌本地 morning_brief_hour / evening_recap_hour 判断发送窗口,并使用 last_morning_brief_at /
last_evening_recap_at 做跨入口防重复。
GET /api/cron/daily-digest
鉴权:同 /api/cron/scheduled-report。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/cron/daily-digest" \
-H "Authorization: Bearer <CRON_SECRET>"
响应参数:完全复用 /api/cron/scheduled-report。
备注:这是旧 daily digest URL 的兼容入口,当前直接复用 twice-daily scheduled-report 业务服务。
Stripe 接口
POST /api/stripe/checkout
鉴权:Sanctum。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| body | agreement_version |
string | 条件必填 | 当前品牌首次订阅且尚未接受 SaaS 协议时必填,必须等于 SAAS_AGREEMENT_VERSION。 |
| header | x-forwarded-for |
string | 否 | 审计 IP,优先取第一段。 |
| header | User-Agent |
string | 否 | 协议接受审计 UA,最多保存 500 字符。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/stripe/checkout" \
-H "Authorization: Bearer <sanctum_token>" \
-H "Content-Type: application/json" \
-H "x-forwarded-for: 203.0.113.10, 10.0.0.1" \
-H "User-Agent: StoreAI Web/1.0" \
-d '{
"agreement_version": "2"
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.url |
string | Stripe Checkout URL。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | no_brand |
当前账号没有品牌。 |
| 400 | agreement_required |
首次订阅缺少协议版本。 |
| 409 | agreement_stale |
协议版本不是当前版本。 |
| 500 | config_error |
缺少 Stripe price id 配置。 |
| 500 | stripe_error |
Stripe API 调用失败。 |
| 500 | server_error |
Stripe 未返回 URL。 |
POST /api/stripe/portal
鉴权:Sanctum。
入参:无。
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/stripe/portal" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.url |
string | Stripe Billing Portal URL。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | no_customer |
当前品牌没有 Stripe customer id。 |
| 500 | stripe_error |
Stripe API 调用失败。 |
| 500 | server_error |
Stripe 未返回 URL。 |
POST /api/stripe/webhook
鉴权:Stripe 签名。请求必须保留 Stripe 原始 body。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| header | Stripe-Signature |
string | 是 | Stripe webhook 签名。 |
| raw body | event | object | 是 | Stripe event JSON。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/stripe/webhook" \
-H "Stripe-Signature: t=1715150000,v1=<stripe_signature>" \
-H "Content-Type: application/json" \
-d '{
"id": "evt_123",
"type": "checkout.session.completed",
"data": {
"object": {
"id": "cs_test_123",
"customer": "cus_123",
"metadata": {
"brand_id": "00000000-0000-4000-8000-000000000101"
},
"subscription": "sub_123"
}
}
}'
支持事件:
| 事件 | 处理 |
|---|---|
checkout.session.completed |
写回品牌 stripe_customer_id。 |
customer.subscription.created |
更新订阅状态、subscription id、customer id、预约取消时间。 |
customer.subscription.updated |
同上;首次进入 past_due 会尝试 Telegram 提醒。 |
customer.subscription.deleted |
标记 canceled,清空 subscription id 和预约取消时间。 |
charge.dispute.created |
写入 dispute 审计,尝试通知管理员 Telegram。 |
charge.dispute.updated / charge.dispute.closed |
更新 dispute 状态。 |
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.received |
string | 已处理事件类型。 |
data.deduped |
boolean | 重复已完成事件返回 true。 |
data.eventId |
string | 被去重的 Stripe event id。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | missing_signature |
缺少 Stripe-Signature。 |
| 400 | bad_signature |
签名校验失败、webhook secret 缺失或 body 非法。 |
| 500 | handler_error |
handler 异常,HTTP 仍返回 200;后端记录 failed 审计行,响应体表达失败。 |
Telegram 接口
POST /api/telegram/webhook
鉴权:Telegram webhook secret。
入参:
| 位置 | 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|---|
| header | X-Telegram-Bot-Api-Secret-Token |
string | 是 | 必须等于后端 TELEGRAM_WEBHOOK_SECRET。 |
| body | message.chat.id |
integer | 条件必填 | Telegram chat id;无 chat id 会忽略。 |
| body | message.text |
string | 条件必填 | 配对命令,格式 /start pair_<hex>。 |
| body | message.from.first_name |
string | 否 | 成功提示中使用。 |
请求示例:
curl -X POST "{BACKEND_API_BASE_URL}/api/telegram/webhook" \
-H "X-Telegram-Bot-Api-Secret-Token: <TELEGRAM_WEBHOOK_SECRET>" \
-H "Content-Type: application/json" \
-d '{
"update_id": 100001,
"message": {
"message_id": 10,
"text": "/start pair_1234567890abcdef1234567890abcdef1234",
"chat": {
"id": -1001234567890,
"type": "group",
"title": "StoreAI Alerts"
},
"from": {
"id": 123456,
"first_name": "Owner"
}
}
}'
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.brandId |
string | 成功消费配对 token 后返回品牌 UUID。 |
data.ignored |
string | 非配对或无法消费时返回原因:no_chat_id、not_pair_command、unknown_token、already_consumed、expired、race_lost。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 400 | invalid_payload |
请求体不是合法 JSON。 |
| 401 | unauthorized |
Secret header 不匹配。 |
| 503 | misconfigured |
后端未配置 Telegram webhook secret。 |
Debug 接口
Debug 路由默认关闭。必须配置 ENABLE_DEBUG_ROUTES=true 后才可访问;关闭时返回 404
{ "code":0, "message":"Not found.", "data": { "error":"not_found" } }。开启后仍要求 Sanctum 鉴权。
GET /api/debug/last-scan
鉴权:Debug 门禁 + Sanctum。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/debug/last-scan" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.diagnosis |
string | 无品牌、无店铺、无扫描等状态下返回,例如 no_brand、no_store、no_scans。 |
data.message |
string | 诊断说明。 |
data.brand |
object | 品牌摘要,含时区、报表小时、Telegram 是否存在。 |
data.latest_scan |
object | 最新 scan 摘要 { id, scanned_at, status, error }。 |
data.field_presence |
object|null | payload 关键字段是否存在的统计。 |
data.audit_dump |
object|null | 最近 scan 的 today、商品、广告、竞品等排障摘录。 |
data.latest_store_brief |
object|null | 最近 scan 的 AI brief 原始摘要。 |
data.sku_matrix |
object|null | 最近 SKU Matrix,含 computed_for、generated_at、confidence、quadrant_counts、entries。 |
data.history_depth |
object | 历史 scan 深度统计。 |
data.competitor_diag |
object | 竞品配置和最近 10 次 scan 竞品字段统计。 |
data.rules_dry_run |
object|null | 规则引擎只读 dry-run 结果。 |
data.ai |
object | AI provider 与 key 存在性诊断,不返回真实 key。 |
data.live_verdict_reattempt |
object | 只读 AI verdict 重试结果。 |
data.live_growth_reattempt |
object | 只读 AI growth 重试结果。 |
data.diagnostics |
string[] | 额外排障建议。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 401 | unauthorized |
未登录。 |
| 404 | not_found |
Debug 门禁未开启。 |
备注:该接口只读,不写 alerts、store_briefs、sku_matrix 或业务表。
GET /api/debug/test-verdict
鉴权:Debug 门禁 + Sanctum。
入参:无。
请求示例:
curl -X GET "{BACKEND_API_BASE_URL}/api/debug/test-verdict" \
-H "Authorization: Bearer <sanctum_token>"
响应参数:
| 字段 | 类型 | 说明 |
|---|---|---|
code |
integer | 成功为 1。 |
message |
string | 成功为 ok。 |
data.diagnosis |
string | 诊断失败原因:no_store、no_scan_payload、no_api_key、unsupported_provider_for_debug。 |
data.provider |
string | 当前 DIAGNOSIS_PROVIDER。 |
data.model |
string | 实际请求 DeepSeek 的模型;deepseek-v4-flash、deepseek-reasoner 会改写为 deepseek-chat。 |
data.api_key_length |
integer | API key 长度,不返回真实 key。 |
data.request.endpoint |
string | DeepSeek endpoint。 |
data.request.tool_name |
string | 固定 emit_verdict。 |
data.request.user_prompt_chars |
integer | prompt 字符数。 |
data.response.http_status |
integer | DeepSeek HTTP 状态码。 |
data.response.duration_ms |
integer | 请求耗时毫秒。 |
data.response.network_error |
string|null | 网络异常摘要。 |
data.response.body_preview |
string | 响应前 2000 字符。 |
data.response.body_json_present |
boolean | 响应是否可解析为 JSON。 |
data.tool_call_summary |
object|null | tool call 摘要,含 finish reason、tool name、raw args、usage 等。 |
错误:
| 响应 code | data.apiCode | 说明 |
|---|---|---|
| 401 | unauthorized |
未登录。 |
| 404 | not_found |
Debug 门禁未开启。 |
备注:该接口只支持 DeepSeek wire format 诊断;Anthropic provider 请使用 /api/debug/last-scan 查看 provider 包装层诊断。