Files
beige/启动包/API契约.md
yangqianqian 6a2632da70 baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。
当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包),
M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 11:30:22 +08:00

11 KiB
Raw Blame History

Clover API 契约OpenAPI 初稿 · Lead 冻结用)

用途:开工前由 Lead/集成 冻结此契约,前后端按此并行开工,杜绝"前端假设字段、联调爆炸"。 铁律:所有 type: object 不许留空——冻结前嵌套对象一级子字段必须定义官网V1坑4。 来源承接架构方案v0.3golden standard端点 + 16表字段不重复定义冲突以架构方案为准。 范围:一期 1A 生产链 + 1B 飞轮。二期端点(注册/矩阵号/管理后台)标 [二期] 不冻结。

0. 全局约定

认证

  • 除登录/注册外,所有请求头带 Authorization: Bearer <JWT>
  • JWT 载荷:user_id / current_workspace_id / roleHS256 签名,每请求验签)。
  • 写操作 + 切 workspace 必须查 workspace_members 校验权限(读操作可信 JWT 加速)。

统一响应包络

成功:{ "code": 0, "data": {...} }
失败:{ "code": <错误码>, "message": "用户可读信息" }

HTTP 状态码与业务 code 分离。

错误码规范7 类)

code 含义 HTTP
0 成功 200
40001 参数校验失败 400
40101 未认证/JWT失效 401
40301 无权限/越权访问 workspace 403
40401 资源不存在 404
40901 状态机非法流转 409
42201 业务校验失败(如未配key/违禁词硬拦截) 422
50001 服务端错误 500
50002 AI/token站调用失败(归因到个人key) 502

分页 & 限流

  • 列表统一 ?page=1&page_size=20,返回 {items, total, page, page_size}
  • 写操作与 AI 触发接口限流(防滥用),超限返 429。

1. 端点清单(一期 1A+1B

认证 & 账号

方法 路径 说明 角色
POST /api/v1/auth/login 登录,返 JWT 全部
GET /api/v1/auth/me 当前用户+workspace+role 全部
POST /api/v1/workspaces/switch 切换当前 workspace查membership 全部

API Key只录不显余额CLAUDE.md红线

方法 路径 说明
GET /api/v1/api-keys 列表(只显 provider + 后4位不显余额/用量
POST /api/v1/api-keys 录入Fernet加密存url固定自家站不接收url字段
DELETE /api/v1/api-keys/{id} 删除

品牌库(管理员)

方法 路径 说明
GET/POST /api/v1/products 产品档案 CRUD含文案角度+可调prompt+source
GET/PUT/DELETE /api/v1/products/{id} 单产品
GET/POST /api/v1/products/{id}/benchmarks 标杆笔记(截图+手填亮点为主通道)
GET/POST/PUT/DELETE /api/v1/banned-words 违禁词库level三级workspace可覆盖

生产链(熟手)

方法 路径 说明
POST /api/v1/tasks 发起任务(选产品+标杆+今天主题+数量校验有无key→只推task_id入队
GET /api/v1/tasks 任务列表(按状态筛)
GET /api/v1/tasks/{id} 任务详情(含候选+状态机当前态)
POST /api/v1/tasks/{id}/sse-ticket 换取一次性 SSE ticketJWT认证→返回60s短票FE立即建EventSource
GET /api/v1/tasks/{id}/stream SSE 实时进度见§2用 ?ticket= 认证,禁止 ?token=
POST /api/v1/tasks/{id}/text-candidates/select 选文案飞轮入口1+3
POST /api/v1/tasks/{id}/image-candidates/select 选图飞轮入口2+3
POST /api/v1/tasks/{id}/import-text 轨B导入外部文案直接进候选池
POST /api/v1/tasks/{id}/regenerate 重新生成(飞轮-1
POST /api/v1/tasks/{id}/submit-review 提交审核状态→pending_review
GET /api/v1/tasks/{id}/preference/context 取本任务飞轮偏好上下文(最近选中样本+最近3条打回原因供 prompt 组装;前端仅用于显示"本次已注入"摘要

🔴 飞轮只有读端点:偏好信号(选文案/选图/通过/打回/重生成)由上方 select / approve / reject / regenerate 接口内部写入 preference_events不暴露独立的 POST /preference/event 给前端(避免被当成"训练按钮",且防伪造信号)。

审核(组长)

方法 路径 说明
GET /api/v1/review/queue 待审队列
POST /api/v1/review/{task_id}/approve 通过(飞轮+5最重状态→approved
POST /api/v1/review/{task_id}/reject 打回(写原因,-3回pending_selection原因下次注入prompt

交付包

方法 路径 说明
POST /api/v1/tasks/{id}/package 生成达人素材交付包(去水印+打包)
GET /api/v1/delivery-packages/{id}/download 下载status: pending/ready/downloaded

[二期] 不冻结

注册(手机验证码)、matrix_accounts矩阵号、管理后台用量统计、preference_profile物化。

2. SSE 事件契约(异步生图电影 + 飞轮注入提示)

GET /api/v1/tasks/{id}/stream 流式推送,前端据此渲染"逐帧电影"。 断线重连时后端补发历史事件(修 banana 漏洞),前端按 event_seq 去重。

🔴 SSE 认证安全红线2026-06-08 倩倩姐拍板)EventSource 不支持自定义 header 故 FE 建连前先用 JWT 调 POST /sse-ticket 换一张一次性短票TTL 60s仅对该 task 有效), URL 只带 ?ticket=。ticket 用后即焚Redis getdel就算被日志记下也立即失效。 禁止 ?token= 直传 JWT 进 URL。

event 类型 data 字段(一级子字段必须全定义) 前端用途
task_started {task_id, total_text, total_image} 显示 0/N 进度
analyze_done {features:[8特征]} 标杆分析完成
text_progress {done, total} 文案 done/total
text_candidate {candidate_id, angle_label, content, score} 单条文案冒出来
image_progress {done, total} 图 done/total
image_candidate {candidate_id, strategy(A/B/C), url, role} 单张图冒出来(谁好谁先冒)
flywheel_injected {recent_preference, reject_reasons:[最近3条]} UI显"本次已注入"飞轮基石E第4点
batch_failed {batch, reason, retryable:bool} 单批失败可重试,不阻塞其他
task_done {task_id, status} 全部完成,可离开
error {code, message} 对接§0错误码
heartbeat {ts} 保活

3. 关键 DTOtype:object 不许留空)

发起任务 POST /api/v1/tasks 请求体

{
  "product_id": 12,
  "benchmark_ids": [3, 5],
  "theme": "今天主题:夏日清爽控油",   // 前端交互设计的"今天主题"字段
  "text_count": 5,                    // 用户设定,不写死(基石A)
  "image_count": 3,
  "track": "ai"                       // ai=轨A生成 / import=轨B导入
}

text_candidate 文案候选(含五维分,挑文案屏用)

{
  "candidate_id": 101, "angle_label": "痛点切入", "content": "...",
  "source": "ai",                     // ai / import
  "score": { "title":24, "emotion":23, "selling":25, "keyword":18, "compliance":5, "total":95 },
  "banned_word_status": "pass"        // pass / auto_fixed / soft_warn / hard_block
}

五维标题25/情绪25/买点25/关键词20/合规5过线90承接产品包评分引擎

打回 POST /api/v1/review/{task_id}/reject 请求体

{ "reason": "标题太硬广,不像素人分享" }   // 原文存 preference_events.reason不做AI归纳

选择类(飞轮信号)统一体

{ "candidate_id": 101 }   // 后端据登录态拿 user/workspace/product写 preference_events

凡涉及 workspace_id / product_id 的写入,后端从 JWT + 任务上下文自动带,前端不传(防越权)。

4. 补全 DTO冻结补全官网V1坑4

POST /api/v1/auth/login

请求体:

{ "username": "string", "password": "string" }

返回 data

{
  "token": "eyJ...",
  "user": {
    "id": 1, "username": "string", "email": "string",
    "current_workspace_id": 1, "role": "admin|supervisor|operator"
  }
}

GET /api/v1/auth/me 返回 data

{
  "id": 1, "username": "string", "email": "string",
  "current_workspace_id": 1, "role": "admin|supervisor|operator",
  "workspace": { "id": 1, "name": "string", "slug": "string" }
}

POST /api/v1/workspaces/switch

请求体:{ "workspace_id": 2 } 返回 data{ "current_workspace_id": 2, "role": "string", "token": "eyJ..." }

GET /api/v1/api-keys 返回 items 单项

{ "id": 1, "provider": "openai", "key_last4": "ab12", "created_at": "2026-01-01T00:00:00Z" }

注:不返回余额/用量CLAUDE.md前端红线

POST /api/v1/api-keys 请求体

{ "provider": "openai", "api_key": "sk-..." }

url 字段不接收,后端固定走自家站 endpoint架构方案§二决策

POST /api/v1/products 请求体

{
  "name": "素颜霜", "category": "string", "source": "preset|custom",
  "selling_points": ["控油", "补水"],
  "style_tone": "素人分享风",
  "text_angles": ["痛点切入", "成分党", "使用场景"],
  "custom_prompt": "string|null",
  "banned_word_ids": [1, 2]
}

POST /api/v1/products/{id}/benchmarks 请求体

{
  "screenshot_url": "string",
  "highlights": "string",
  "link_url": "string|null"
}

GET /api/v1/banned-words 返回 items 单项

{
  "id": 1, "word": "美白", "level": "auto_fix|soft_warn|hard_block",
  "replacement": "提亮|null", "updatable": true, "workspace_id": 1
}

POST/PUT /api/v1/banned-words 请求体

{ "word": "祛斑", "level": "hard_block", "replacement": null, "updatable": true }

GET /api/v1/tasks 返回 items 单项

{
  "id": 1, "product_id": 1, "theme": "string", "status": "pending|generating|pending_selection|pending_review|approved|rejected|archived",
  "created_at": "2026-01-01T00:00:00Z", "text_count": 5, "image_count": 3
}

GET /api/v1/review/queue 返回 items 单项

{
  "task_id": 1, "product_name": "string", "theme": "string",
  "submitted_at": "2026-01-01T00:00:00Z", "operator_name": "string",
  "selected_text": { "candidate_id": 101, "angle_label": "string", "content": "string" },
  "selected_image": { "candidate_id": 201, "strategy": "A|B|C", "url": "string" }
}

GET /api/v1/tasks/{id}/preference/context 返回 data

{
  "recent_preference": "最近偏好摘要偏好痛点切入角度已选3次",
  "reject_reasons": ["标题太硬广", "图换了包装"],
  "injected_count": 3
}

POST /api/v1/tasks/{id}/package 请求体

{ "note_ids": [1, 2, 3] }

返回 data{ "delivery_package_id": 1, "status": "pending" }

GET /api/v1/delivery-packages/{id}/download 返回 data

{ "id": 1, "status": "pending|ready|downloaded", "download_url": "string|null", "expires_at": "string|null" }

image_candidate 的 role 字段说明

role = storyboard角色枚举hook(钩子) / pain(痛点) / proof(证明) / quality(质感) / credit(背书) / convert(转化)。单张图时 role=main


冻结戳记Lead 冻结,日期 2026-06-08。BE/FE/AIE 按此并行开工,契约变更需所有 agent 确认后方可修改。