9.0 KiB
采集箱与浏览器插件 API
采集箱只属于工作空间:采集时没有目标平台、目标门店或店铺凭据。列表上架状态则按 ERP 页头当前平台汇总;它不是 Ozon 在线商品可见性,也不因切换到另一平台而继承。
1. 浏览器扩展授权
扩展不读取 ERP 网页 localStorage,也不用 Web 端 refresh cookie。每个发布扩展在服务端登记 client_id 和允许的 chrome-extension://{extension-id}/… 回调地址,扩展使用 PKCE 授权码流程。
- 扩展打开:
GET /api/extension/authorize?client_id={id}&redirect_uri={uri}&state={random}&code_challenge={S256}&code_challenge_method=S256。 - 用户在 Atlas Web 完成登录;首期不额外展示工作空间选择/授权确认页。
- 服务端只向已登记的
redirect_uri回跳?code={one-time}&state={original}。授权码 60 秒有效、只能交换一次,且绑定 client、回调地址和 PKCE challenge。 - 扩展调用
POST /api/extension/auth/exchange:
{
"client_id": "atlas-collector",
"code": "one-time-code",
"redirect_uri": "chrome-extension://extension-id/callback",
"code_verifier": "original-pkce-verifier"
}
成功返回扩展专用短期 access token 与可轮换 refresh token;令牌只存 Chrome 扩展专属存储:
{ "data": {
"access_token": "eyJ…", "expires_in": 900, "refresh_token": "opaque", "refresh_token_expires_in": 2592000,
"token_type": "Bearer"
} }
POST /api/extension/auth/refresh 接收 { "refresh_token": "opaque" },轮换并返回同形状令牌;POST /api/extension/auth/revoke 撤销当前扩展 refresh token,返回 204。令牌续期、撤销和错误响应不得泄露 Web 会话或其他工作空间信息。
GET /api/extension/session
使用扩展 access token,返回最小化状态:
{ "data": { "authenticated": true, "workspace": { "id": "uuid", "available": true }, "can_collect": true } }
不返回平台、默认店铺、Cookie、Ozon 凭据或 Web refresh token。令牌失效返回 401,插件只展示“登录 Atlas ERP”。
2. 插件上报采集结果
POST /api/extension/materials
Headers:扩展 Bearer token 与 Idempotency-Key 必填。请求体为已定义的 CollectedProductPayload,其 source 至少包含 platform、itemKey、canonicalUrl、collectedAt、parserVersion;variants 仅列来源有稳定证据的真实组合,媒体只传来源 URL/关系和证据,不传图片二进制。
请求体不得包含或传入 workspace_id、target_platform_id、platform_id、store_id、api_key、Cookie、授权头或来源站会话字段。服务端从扩展令牌解析工作空间,并将来源 platform 映射为系统已启用的来源平台。
服务端在一个事务中:检查工作空间采集箱上限 → 按 workspace + source_platform + source_item_key 查找活动记录 → 创建或复用 collection_item、product、来源确认的 product_variants 和持久化 collection_asset_transfers → 再经 outbox 投递素材 Worker。重复上报不会增加采集箱配额,也不覆盖用户已编辑草稿。
成功返回 201(新建)或 200(复用):
{ "data": {
"collection_item": { "id": "uuid", "status": "materializing", "source_title": "商品标题", "variant_count": 3 },
"product_id": "uuid", "reused": false,
"editor_entry_path": "/collection-items/uuid/edit",
"materialization": { "total": 12, "queued": 12, "succeeded": 0, "failed": 0 }
} }
达到工作空间上限时返回 422 COLLECTION_LIMIT_REACHED,并附 {current_count, limit};不能因重复点击或并发请求绕过 1000 条限制。单个来源素材失败不使采集事务失败,关键商品/SKU 解析失败才使记录进入 failed 并返回可展示的脱敏摘要。
3. Ozon Seller 网页本地观察
POST /api/collection-items/{collection_item_id}/seller-enrichment-observations
仅接受扩展 token。用户在浏览器已登录 seller.ozon.ru 标签页内,同源调用已白名单的四个网页内部接口后,将净化结果或不可用原因上报:
{
"interface_code": "whitelisted-interface-code",
"source_variant_key": "optional-source-variant-id",
"status": "observed",
"observed_at": "2026-09-02T08:00:00Z",
"parser_version": "collector-1.0.0",
"sanitized_payload": { "only": "whitelisted business fields" }
}
不可用时使用 status: "unavailable" 并传 unavailable_reason;请求、响应在客户端和服务端均须过滤 Cookie、Authorization、店铺/个人无关信息、会话 ID 及完整 company_id。标准公开页面采集已成功时,本接口失败或未调用不得改变采集箱主状态、更不能阻断编辑。
服务端解析 source_variant_key 到当前采集商品的 SKU 后写入 collection_seller_enrichment_observations。接口代码不在白名单、记录不属于该工作空间或 payload 含禁用会话字段时返回 422;不把原始敏感 payload 写日志。
4. 采集箱列表与详情
GET /api/collection-items?cursor={opaque}&limit=20&q={keyword}&source_platform_code={code}&listing_status={not_listed|partially_listed|listed}
要求 X-Atlas-Platform-Id,按 updated_at DESC, id DESC 返回当前工作空间未删除记录。q 模糊匹配标题;source_platform_code 是来源筛选,不等于页头目标平台;listing_status 是按页头目标平台汇总的状态。
{ "data": [{
"id": "uuid", "product_id": "uuid", "source": { "kind": "platform", "platform_code": "1688", "url": "https://…" },
"title": "商品标题", "thumbnail": { "asset_id": "uuid", "preview_url": "short-lived-or-null", "status": "succeeded" },
"source_price": { "min": "20.00", "max": "35.00", "currency_code": "CNY" },
"variant_summary": { "count": 3, "needs_review": false }, "note": "待确认材质",
"collection_status": "ready_for_edit",
"listing_summary": { "status": "partially_listed", "detail": "1 个 SKU 已导入;2 个等待首次上架", "last_error": null },
"created_at": "2026-09-02T08:00:00Z", "updated_at": "2026-09-02T08:05:00Z", "revision": 4
}], "meta": { "next_cursor": null, "has_more": false, "active_count": 12, "collection_limit": 1000 } }
listing_summary.status 映射 UI 的未上架/已部分上架/已上架;collection_status 是采集/素材处理状态,二者绝不混用。首张素材转存未完成时 thumbnail 为占位状态;商品未命名时 title 返回 null,前端展示“未命名商品”。
GET /api/collection-items/{collection_item_id}
无平台 header 时仅返回采集基础信息、SKU/素材转存摘要、净化 Seller 观察结果摘要与警告;带 header 时附加 listing_summary。观察结果详情只对当前工作空间成员可见。
PATCH /api/collection-items/{collection_item_id}
Headers:If-Match、Idempotency-Key 必填。首期仅允许修改 note;标题、SKU 和素材在商品编辑 API 中修改。响应返回更新后的采集记录。If-Match 对应记录的 revision,包括采集 Worker 状态更新在内的每次修改都递增;不得覆盖 Worker 的状态更新。
5. 手动创建、删除与素材重试
POST /api/collection-items
Headers:Idempotency-Key 必填。
{ "title": "未命名手工商品", "note": "可选备注" }
创建 source_kind=manual 的采集记录、空商品草稿和默认 SKU,返回 201、collection_item、product_id 与 editor_entry_path。它仍不绑定目标平台/店铺,也不绕过之后的类目、定价和首次发布校验。
POST /api/collection-items/{collection_item_id}/asset-retries
Headers:Idempotency-Key 必填。请求 { "transfer_ids": ["uuid"] };只能重试该采集记录中 failed 或 cancelled 的转存任务。返回 202 和 operation。不能用它重试来源商品解析或已成功素材。
POST /api/collection-items/bulk-delete
Headers:Idempotency-Key 必填。请求 { "ids": ["uuid"] },原子软删除其中当前工作空间的全部有效记录,返回 202、deleted_count 和延迟素材清理 operation。已上架记录同样可删除,但响应/前端确认文案必须说明:不删除、不下架任何 Ozon 商品,也不移除在线商品镜像。
POST /api/collection-items/clear
Headers:Idempotency-Key 必填。先由列表读取 active_count 并展示二次确认;确认请求:
{ "confirm": true, "expected_active_count": 12 }
服务端锁定工作空间后比较当前有效数量;数量变化返回 409 COLLECTION_COUNT_CHANGED,要求用户重新确认。匹配时软删除全部有效记录并返回 202、删除数与延迟清理 operation。它不影响平台商品、发布快照或在线商品。
6. 与商品编辑接口的边界
editor_entry_path 只是 ERP 前端入口。下一批商品编辑接口会在用户进入该入口时根据页头目标平台取得/创建 product_platform_profile、预选默认店铺、允许切换同平台店铺,并定义 SKU 编辑、AI、定价确认、图片选择与仅未导入 SKU 的首次上架。