Files
cmroubao/docs/api.md
T

11 KiB
Raw Blame History

API 合约

MVP 使用 /api/v1、UTF-8 JSON over HTTPS。文件上传使用 multipart/form-data。 Web 页面可以直接调用同一 usecase,但不能形成不同的业务规则。

通用约定

  • 所有 ID 为 UUID 字符串。
  • 时间为 UTC ISO 8601,例如 2026-07-25T08:30:00Z。
  • 金额使用十进制定点字符串,例如 "199.00",币种固定 CNY;不使用浮点数。
  • 写接口在合约标注处接收 Idempotency-Key 请求头。
  • App 的任务执行接口同时需要用户 Bearer Token、设备身份和 X-Claim-Token。
  • 分页使用 limit 和不透明 cursor;MVP limit 最大 100。
  • 客户端不得根据 HTTP 超时判断操作失败,必须查询资源最终状态。

通用错误:

{
  "error": {
    "code": "TASK_STATE_CONFLICT",
    "message": "任务当前状态不允许此操作",
    "retryable": false,
    "details": {}
  },
  "request_id": "f2cf02e9-57e0-4fca-817b-c85228acfc81"
}

HTTP 语义:

  • 400 请求格式或业务校验失败
  • 401 未建立有效身份
  • 403 身份有效但无权限、设备禁用或 claim 不属于当前设备
  • 404 资源不存在,外部响应不泄露其他人的资源存在性
  • 409 幂等冲突、状态冲突或设备已有活跃任务
  • 413 文件过大
  • 415 不支持的媒体类型
  • 422 结构可解析但字段不满足 schema
  • 429 频率限制
  • 503 依赖暂不可用

认证

管理 Web 会话

POST /login 接受表单账号密码,成功后设置 HttpOnly、Secure、SameSite=Lax 会话 Cookie。POST /logout 清除会话。Web 会话不能调用设备执行接口。

POST /api/v1/auth/token

采购 App 建立用户和设备联合会话。

{
  "username": "buyer01",
  "password": "local-input-only",
  "device_id": "f0c3d438-1c44-4f80-b898-c645afe7eaa8",
  "device_token": "local-input-only",
  "app_version": "0.1.0",
  "android_version": "待设备填写"
}

成功:

{
  "access_token": "opaque-or-jwt-token",
  "token_type": "Bearer",
  "expires_in": 3600,
  "user": {
    "id": "d950db90-cf58-4f21-86fd-067e1a91a3a6",
    "username": "buyer01",
    "role": "BUYER"
  },
  "device": {
    "id": "f0c3d438-1c44-4f80-b898-c645afe7eaa8",
    "enabled": true
  }
}

密码和设备 token 不得出现在响应、日志或 execution event 中。

资产

POST /api/v1/assets

管理会话上传任务参考图片;App 也可用设备会话上传执行截图。请求必须带 Idempotency-Key。

表单字段:

  • file:必填,MVP 允许 JPEG/PNG/WebP;实际大小上限在配置中固定。
  • purpose:TASK_REFERENCE 或 EXECUTION_EVIDENCE。
  • task_id:证据图片必填;参考图创建时为空。

成功:

{
  "id": "2bbc1bf2-30f7-497e-a52b-bcb4c61d57c3",
  "media_type": "image/jpeg",
  "size_bytes": 183245,
  "sha256": "hex-value",
  "created_at": "2026-07-25T08:30:00Z"
}

GET /api/v1/assets/{asset_id}/content

受鉴权的文件流。只能读取用户有权查看的任务资产;不返回服务端文件路径。

采购任务

POST /api/v1/tasks

采购管理员或授权的外部管理系统创建任务。必须带 Idempotency-Key。

{
  "source_ref": "external-admin-task-10001",
  "title": "黑色双肩包",
  "description": "容量约20L,外观接近参考图",
  "image_asset_id": "2bbc1bf2-30f7-497e-a52b-bcb4c61d57c3",
  "quantity": 2,
  "max_budget": "200.00"
}

规则:

  • title 或 description 至少一个非空,image_asset_id 必填。
  • quantity 是正整数。
  • max_budget 可为空,否则为大于零、最多两位小数的 CNY 金额。
  • 同一调用方的 source_ref 如填写必须唯一。

成功返回 201:

{
  "id": "37c9c715-9b51-4ed5-984e-66dad2710c71",
  "status": "PENDING",
  "title": "黑色双肩包",
  "quantity": 2,
  "max_budget": "200.00",
  "created_at": "2026-07-25T08:30:00Z"
}

GET /api/v1/tasks

管理端列表。可选参数:status、created_from、created_to、limit、cursor。

{
  "items": [
    {
      "id": "37c9c715-9b51-4ed5-984e-66dad2710c71",
      "title": "黑色双肩包",
      "status": "RUNNING",
      "quantity": 2,
      "device_name": "pdd-phone-01",
      "updated_at": "2026-07-25T08:35:00Z"
    }
  ],
  "next_cursor": null
}

GET /api/v1/tasks/{task_id}

管理会话返回完整任务、当前 execution、时间线、候选和资产元数据。App 只能读取 当前设备已领取的任务。响应必须同时包含:

  • original_requirement:不可变原始输入。
  • derived_requirement:模型输出,可能为空。
  • claim:管理端可见设备和到期时间;claim token 永不返回。
  • execution:step、outcome、错误、order_submitted。
  • events 和 assets:有权限的摘要。

POST /api/v1/tasks/{task_id}/cancel

管理端取消任务。PENDING 可立即取消;执行中只设置取消请求,App 在安全检查点确认 后进入 CANCELED。终态返回 409。

{
  "reason": "需求已撤销"
}

设备与领取

POST /api/v1/devices/heartbeat

App 空闲或运行时上报设备状态;运行时任务续租使用任务专用 heartbeat。

{
  "device_id": "f0c3d438-1c44-4f80-b898-c645afe7eaa8",
  "app_version": "0.1.0",
  "pdd_version": "device-observed-value",
  "readiness": {
    "accessibility_enabled": true,
    "pdd_installed": true,
    "active_task_id": null
  }
}

POST /api/v1/tasks/claim-next

由用户点击触发,在单个数据库事务中领取下一条任务。必须带 Idempotency-Key。

{
  "device_id": "f0c3d438-1c44-4f80-b898-c645afe7eaa8"
}

有任务时:

{
  "task": {
    "id": "37c9c715-9b51-4ed5-984e-66dad2710c71",
    "status": "CLAIMED",
    "title": "黑色双肩包",
    "description": "容量约20L,外观接近参考图",
    "image_asset_id": "2bbc1bf2-30f7-497e-a52b-bcb4c61d57c3",
    "quantity": 2,
    "max_budget": "200.00"
  },
  "claim_token": "returned-once",
  "claim_expires_at": "2026-07-25T08:40:00Z"
}

没有任务返回 204。设备未就绪或已有活跃任务返回 409。幂等重放返回同一领取 结果;claim token 的安全重放形式在实现前通过测试固定。

POST /api/v1/tasks/{task_id}/start

把当前设备持有的 CLAIMED 任务改为 RUNNING 并创建 execution。请求带 X-Claim-Token 和 Idempotency-Key。

POST /api/v1/tasks/{task_id}/heartbeat

运行时续租并返回是否请求取消:

{
  "execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
  "step": "SCAN_RESULTS",
  "client_time": "2026-07-25T08:33:30Z"
}
{
  "claim_expires_at": "2026-07-25T08:43:30Z",
  "cancel_requested": false
}

POST /api/v1/tasks/{task_id}/release

只允许尚未开始的 CLAIMED 任务释放回 PENDING。运行中使用取消/失败流程。

AI 合约

POST /api/v1/tasks/{task_id}/ai/extract-requirements

只允许当前 execution 调用。后端从任务读取原始图片和文字,客户端不能替换硬约束。

{
  "execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f"
}

响应:

{
  "schema_version": 1,
  "search_query": "黑色 20L 双肩包",
  "category": "双肩包",
  "attributes": [
    {"name": "color", "value": "black", "source": "text"},
    {"name": "capacity", "value": "about 20L", "source": "text"}
  ],
  "sku": "BLACK-20L",
  "quantity": 2,
  "max_budget": "200.00",
  "confidence": 0.86,
  "warnings": [],
  "manual_review": {
    "required": false,
    "reasons": []
  },
  "provenance": {
    "provider_id": "configured-provider",
    "model": "configured-model",
    "prompt_version": "requirement-extraction-v1",
    "reference_image_sha256": "64-char-lowercase-hex"
  }
}

sku、quantity 和 max_budget 必须来自原始任务,不能采用模型返回值。第一层 ProbeTask 尚无预算字段,因此 T-103 输出 max_budget: null 并追加 MAX_BUDGET_NOT_PROVIDED 警告。属性 source 的 API 表示使用小写 title/image/both;供应商响应在 adapter 内规范化后才进入此合约。

POST /api/v1/tasks/{task_id}/ai/evaluate-candidate

{
  "execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
  "candidate_index": 1,
  "screenshot_asset_id": "7b733922-f90f-4bc4-a9ad-3e8ec4769122",
  "observed": {
    "title": "页面可见标题",
    "price": "189.00"
  }
}

响应:

{
  "schema_version": 1,
  "decision": "REVIEW",
  "score": 0.82,
  "matched": ["颜色接近", "价格未超预算"],
  "missing_or_uncertain": ["容量无法从当前页面确认"],
  "rejection_reasons": [],
  "confidence": 0.78
}

decision 只允许 REVIEW、REJECT、MANUAL_REQUIRED。模型没有“提交订单”权限。

执行事件与结果

POST /api/v1/tasks/{task_id}/events

批量追加事件,必须带 Idempotency-Key:

{
  "execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
  "events": [
    {
      "event_id": "client-generated-uuid",
      "step": "SEARCH",
      "type": "STEP_COMPLETED",
      "message": "已进入搜索结果页",
      "occurred_at": "2026-07-25T08:34:00Z"
    }
  ]
}

message 不能包含凭证或完整个人敏感信息;同一 event_id 重放不重复插入。

POST /api/v1/tasks/{task_id}/complete

人员完成确认后调用,必须带 Idempotency-Key:

{
  "execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
  "outcome": "CANDIDATE_ACCEPTED",
  "operator_reason": "款式和预算符合验证要求",
  "candidate": {
    "title": "页面可见标题",
    "price": "189.00",
    "evidence_asset_ids": ["7b733922-f90f-4bc4-a9ad-3e8ec4769122"]
  },
  "order_submitted": false
}

outcome 允许:

  • CANDIDATE_ACCEPTED
  • CANDIDATE_REJECTED
  • NO_MATCH
  • MANUAL_REQUIRED

后端对 MVP 强制 order_submitted=false,成功后任务进入 SUCCEEDED;这里的成功表示 验证工作流正常结束,业务结果由 outcome 表达。

POST /api/v1/tasks/{task_id}/fail

{
  "execution_id": "e3190742-a24b-441e-b5c1-c7ed10ed342f",
  "error": {
    "code": "PDD_RISK_CONTROL",
    "message": "检测到平台风险提示,已停止自动化",
    "step": "SCAN_RESULTS",
    "retryable": false
  },
  "evidence_asset_ids": ["7b733922-f90f-4bc4-a9ad-3e8ec4769122"]
}

后端只接受文档登记的错误码族和合法状态迁移。

待实现前固定

  • 上传大小、像素和保留期限的具体数值。
  • access token、管理会话和 claim lease 的最终时长。
  • claim token 幂等重放与轮换细节。
  • VLM confidence 阈值、模型和提示词版本记录格式。
  • 外部管理后台的服务账号认证方式和调用频率。