# API 统一约定 ## 1. 鉴权与工作空间隔离 除 `POST /api/auth/register`、`POST /api/auth/login`、`POST /api/auth/refresh` 外,所有接口都要求: ```http Authorization: Bearer ``` 访问令牌只携带用户会话标识,不以客户端传入的 `workspace_id` 判定数据归属。服务端解析用户、选择首期唯一的有效工作空间、验证成员关系后,在每一个数据库事务中执行 `SET LOCAL app.workspace_id`。RLS 仅是第二道防线,业务层仍须验证资源归属。 Web 登录后的 refresh token 放在 `HttpOnly`、`Secure`(生产)、`SameSite=Lax` 的 cookie 中,永不出现在 JSON、日志或前端持久化存储。access token 由前端仅在内存中短暂持有;插件使用独立授权码流程,留待采集插件接口文档定义。 ## 2. 页头平台与默认店铺 平台范围接口使用: ```http X-Atlas-Platform-Id: 57ad…-uuid ``` - 无此 header 的接口不依赖平台,例如 `GET /api/auth/me`、水印和通用素材接口。 - 需要平台却没有 header 时返回 `400 PLATFORM_CONTEXT_REQUIRED`。 - header 指向停用或不存在的平台时返回 `422 PLATFORM_UNAVAILABLE`。 - 默认店铺是服务端按工作空间和该 header 查出的状态,响应中以 `default_store` 明确返回;客户端不应缓存为事实。 列表筛选中的 `platform_id` 只用于平台目录或跨平台管理页,不能绕开数据授权;同一请求不得同时带 `X-Atlas-Platform-Id` 和语义相同的 `platform_id`。 ## 3. 响应、分页和字段格式 成功响应统一为: ```json { "data": { "id": "uuid" } } ``` 列表响应: ```json { "data": [{ "id": "uuid" }], "meta": { "next_cursor": "opaque-string-or-null", "has_more": false } } ``` - 游标是服务端不透明值,客户端只能原样传回 `cursor`;默认按文档指定稳定排序。 - `limit` 默认 `20`,最大 `100`;超出时返回 `422 VALIDATION_FAILED`。 - 金额与费率以 JSON **字符串**返回和提交,例如 `"199.90"`、`"0.125"`,避免 JavaScript 浮点误差;币种用 ISO 4217 大写代码。 - 所有时间用 UTC,例如 `"2026-09-02T08:00:00Z"`;日期用 `YYYY-MM-DD`。 - 不返回 `password_hash`、API Key、Cookie、密文、对象存储永久密钥或长期访问 URL。 ## 4. 错误格式和 HTTP 状态 ```json { "error": { "code": "VALIDATION_FAILED", "message": "请求参数不符合要求", "request_id": "req_…", "details": [ { "field": "email", "code": "EMAIL_ALREADY_REGISTERED", "message": "该邮箱已注册" } ] } } ``` | 状态 | 使用场景 | |---:|---| | `400` | 缺少平台上下文、请求语义矛盾、无法解析的 JSON。 | | `401` | 缺少、失效或撤销的 access token。 | | `403` | 已登录但无工作空间成员资格或无操作权限。 | | `404` | 当前工作空间范围内不存在该资源;不暴露其他工作空间资源。 | | `409` | 重复唯一资源、幂等键与既有请求不一致、乐观锁冲突。 | | `422` | 字段格式、业务状态或平台能力不满足。 | | `429` | 登录、刷新、上传签发或重试操作触发限流。 | | `500` | 未预期服务错误;不返回秘密或平台原始敏感响应。 | ## 5. 写操作的幂等、并发和异步 - 创建店铺、上传意图、删除水印模板等可重试写操作应携带 `Idempotency-Key: `。服务端按“当前用户 + 工作空间 + HTTP 方法 + 规范化实际路径 + key”保存至少 24 小时(首期默认 48 小时);请求摘要保存为服务端 HMAC,而非明文请求体。相同键、相同请求返回已保存响应;相同键、不同请求返回 `409 IDEMPOTENCY_KEY_REUSED`;原请求仍在执行返回 `409 IDEMPOTENCY_REQUEST_IN_PROGRESS`。 - 更新可编辑资源时,读取响应携带 `revision`;客户端在 `PATCH`/`PUT` 中提交 `If-Match: ""`。版本已变化返回 `409 VERSION_CONFLICT` 和最新 `current_revision` 摘要。`revision` 是并发控制版本;不要把水印的渲染 `current_version` 当作它。 - 耗时动作(店铺凭据验证、仓库同步、媒体处理)先返回 `202 Accepted`,并在资源中返回 `operation`。客户端轮询 `GET /api/operations/{operation_id}`;后续可追加 SSE,不改变资源接口。 - 所有启动异步工作的业务写入,必须在同一 PostgreSQL 事务中写入业务资源/`operation` 和 `outbox_events`;独立 Dispatcher 再投递 BullMQ。BullMQ 和 Redis 不得作为任务存在或最终业务结果的唯一事实。 `operation` 统一形状: ```json { "id": "uuid", "kind": "store_credential_verification", "status": "queued", "created_at": "2026-09-02T08:00:00Z", "completed_at": null, "error": null } ``` 操作接口只返回当前工作空间可见的任务,且任务状态不能作为唯一事实;业务资源中的状态字段才是页面最终展示依据。 `GET /api/operations/{operation_id}` 返回上述 operation 形状。任务不属于当前工作空间时返回 `404`;`error` / `error_message` 只能是可展示的脱敏摘要,不能包含 API Key、Cookie、对象存储签名或平台原始敏感响应。 ## 6. 删除与软删除 首期店铺和水印模板使用软删除。成功删除返回 `204 No Content`;之后以普通 `GET` 读取返回 `404`,列表默认不包含删除记录。历史发布、素材派生和审计记录保留其快照,不因软删除被改写。