# Clover API 契约(OpenAPI 初稿 · Lead 冻结用) > 用途:开工前由 Lead/集成 冻结此契约,前后端按此并行开工,杜绝"前端假设字段、联调爆炸"。 > 铁律:所有 `type: object` 不许留空——冻结前嵌套对象一级子字段必须定义(官网V1坑4)。 > 来源:承接架构方案v0.3(golden standard)端点 + 16表字段,不重复定义,冲突以架构方案为准。 > 范围:一期 1A 生产链 + 1B 飞轮。二期端点(注册/矩阵号/管理后台)标 [二期] 不冻结。 ## 0. 全局约定 ### 认证 - 除登录/注册外,所有请求头带 `Authorization: Bearer `。 - JWT 载荷:`user_id` / `current_workspace_id` / `role`(HS256 签名,每请求验签)。 - 写操作 + 切 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 ticket(JWT认证→返回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. 关键 DTO(type: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 请求体: ```json { "username": "string", "password": "string" } ``` 返回 data: ```json { "token": "eyJ...", "user": { "id": 1, "username": "string", "email": "string", "current_workspace_id": 1, "role": "admin|supervisor|operator" } } ``` ### GET /api/v1/auth/me 返回 data ```json { "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 单项 ```json { "id": 1, "provider": "openai", "key_last4": "ab12", "created_at": "2026-01-01T00:00:00Z" } ``` 注:不返回余额/用量(CLAUDE.md前端红线)。 ### POST /api/v1/api-keys 请求体 ```json { "provider": "openai", "api_key": "sk-..." } ``` 注:url 字段不接收,后端固定走自家站 endpoint(架构方案§二决策)。 ### POST /api/v1/products 请求体 ```json { "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 请求体 ```json { "screenshot_url": "string", "highlights": "string", "link_url": "string|null" } ``` ### GET /api/v1/banned-words 返回 items 单项 ```json { "id": 1, "word": "美白", "level": "auto_fix|soft_warn|hard_block", "replacement": "提亮|null", "updatable": true, "workspace_id": 1 } ``` ### POST/PUT /api/v1/banned-words 请求体 ```json { "word": "祛斑", "level": "hard_block", "replacement": null, "updatable": true } ``` ### GET /api/v1/tasks 返回 items 单项 ```json { "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 单项 ```json { "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 ```json { "recent_preference": "最近偏好摘要:偏好痛点切入角度(已选3次)", "reject_reasons": ["标题太硬广", "图换了包装"], "injected_count": 3 } ``` ### POST /api/v1/tasks/{id}/package 请求体 ```json { "note_ids": [1, 2, 3] } ``` 返回 data:`{ "delivery_package_id": 1, "status": "pending" }` ### GET /api/v1/delivery-packages/{id}/download 返回 data ```json { "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 确认后方可修改。