# 商品编辑与首次上架 API > 此处的商品始终是采集箱内的 Atlas 草稿。所有平台范围接口要求 `X-Atlas-Platform-Id`。商品通用资料可以跨平台复用;`product_platform_profile`、SKU 首次导入状态和在线商品不能跨平台/门店复用。 ## 1. 进入编辑与读取编辑器 `POST /api/collection-items/{collection_item_id}/editor-contexts` Headers:`Idempotency-Key` 必填,页头平台由 header 指定,body 为空。服务端验证采集记录归属、平台可用,并取得或创建该商品在当前平台的 `product_platform_profile`: - 首次创建时使用该平台默认店铺作为 `target_store_id`,并为所有未删除 SKU 建立本平台 `product_variant_platform_listing`; - 当前平台没有默认店铺时返回 `422 DEFAULT_STORE_REQUIRED`,只给出店铺管理引导,不创建缺少目标门店的 profile; - 已存在 profile 时只读取,绝不因再次进入而覆盖用户已选门店、类目、属性或价格; - 不创建跨平台 profile,也不改变其他平台的上架状态。 返回: ```json { "data": { "collection_item_id": "uuid", "product_id": "uuid", "profile_id": "uuid", "target_store": { "id": "uuid", "name": "Ozon 主店", "currency_code": "RUB" }, "editor_path": "/products/uuid/editor" } } ``` `GET /api/products/{product_id}/editor` 要求 header,返回当前平台 profile 的完整编辑快照。核心形状: ```json { "data": { "product": { "id": "uuid", "revision": 12, "common": { "source_title": "来源标题", "brand": null, "model": null, "barcode": null, "purchase_url": "https://…", "supply_note": null }, "default_physical_spec": { "length_mm": null, "width_mm": null, "height_mm": null, "net_weight_g": null, "status": "missing" }, "default_package_spec": { "length_mm": null, "width_mm": null, "height_mm": null, "shipping_weight_g": null, "status": "missing" } }, "profile": { "id": "uuid", "revision": 8, "platform_id": "uuid", "market_code": "RU", "fulfillment": "rfbs", "target_store": { "id": "uuid", "name": "Ozon 主店", "currency_code": "RUB" }, "title": "俄语标题", "description": null, "tags": [], "category": { "id": "uuid", "platform_type_id": "123", "source": "source_capture" }, "listing_summary": { "status": "partially_listed", "detail": "1 个 SKU 已导入" } }, "variants": [], "attributes": [], "media_candidates": [], "attribute_schema": null, "warnings": [] } } ``` `variants` 每项同时返回通用 SKU、合并后实物/包装规格、当前平台 listing 摘要(含 `offer_id`、价格、首次导入状态、最近脱敏失败原因)及已选媒体。`attribute_schema` 仅在当前类目已确定时返回。候选和已选媒体只含 `asset_id`、尺寸、MIME、来源角色与短时预览 URL;不含 `object_key` 或永久 URL。 ## 2. 原子保存草稿与 SKU 规则 `PUT /api/products/{product_id}/editor-draft` Headers:`If-Match: "{product_revision}:{profile_revision}"`、`Idempotency-Key` 必填。请求是整个编辑页的原子快照: ```json { "common": { "source_title": "商品名称", "brand": "品牌", "model": null, "barcode": null, "purchase_url": "https://…", "supply_note": null, "default_physical_spec": { "length_mm": "100", "width_mm": "50", "height_mm": "20", "net_weight_g": "300", "status": "confirmed" }, "default_package_spec": { "length_mm": "120", "width_mm": "60", "height_mm": "30", "shipping_weight_g": "400", "status": "confirmed" } }, "profile": { "target_store_id": "uuid", "fulfillment": "rfbs", "title": "Название", "description": "…", "tags": ["tag"], "platform_product_category_id": "uuid", "platform_type_id": "123", "category_source": "manual" }, "variants": [{ "id": "uuid-or-null", "client_key": "new-row-key", "copy_from_variant_id": null, "display_name": "黑色 / M", "source_sku": null, "specification_values": [{ "axis_key": "color", "value_key": "black", "value_name": "黑色" }], "purchase_cost_cny": "80.00", "physical_spec": { "length_mm": null, "width_mm": null, "height_mm": null, "net_weight_g": null, "status": null }, "package_spec": { "length_mm": null, "width_mm": null, "height_mm": null, "shipping_weight_g": null, "status": null }, "offer_id": null, "media_asset_ids": ["uuid", "uuid"] }], "attributes": [{ "scope": "common", "variant_id": null, "platform_attribute_id": "100", "complex_id": "0", "values": ["value"], "language_code": "ru", "source": "manual", "confirmation_status": "confirmed" }] } ``` 语义与校验: - `variants` 是当前完整未删除 SKU 集合。省略既有 SKU 表示软删除;无论当前 SPU 状态都允许新增、复制、删除。`copy_from_variant_id` 仅用于新行,复制通用 SKU 数据但不复制任何平台导入映射,生成的新 SKU 从 `not_listed` 开始。 - 删除已导入 SKU 只从 Atlas 草稿及当前平台可发布集合移除,**不删除、不下架、不修改** Ozon 商品或在线镜像;其他平台历史映射保留。删除后当前平台只剩已导入 SKU 时,SPU 汇总自然为“已上架”。 - `null` 的 SKU 规格字段表示继承父商品;包装物流规格的 `status` 为 `missing`/`ambiguous` 时,不允许定价确认或首次上架,但仍允许保存草稿。实物规格只影响尺寸图,不用于物流。 - `target_store_id` 只能是当前工作空间、当前平台的活动店铺。变更目标店铺只更新**尚未导入** listing 的将来发布目标;`imported` listing 永远保留其首次导入时的店铺快照。 - 类目变化后不匹配的旧属性作为未映射草稿返回 warnings,不能自动进入发布 payload。属性由平台 schema 校验 `type`、必填、字典、多值/复杂属性和 `is_aspect`;客户端不能用任意 JSON 绕过。 - `media_asset_ids` 是本 SKU 当前平台的最终选图顺序;首期最多 15 张。格式只允许 JPG/PNG/WebP,单文件最多 20 MB,边长超过 10,000 px 拒绝,短边小于 700 px 不得作为首图并给出警告。第一张是主图。 - `common.model` 是用户填写的商品型号;采集时允许为空,但每次首次上架前必须非空。`offer_id` 是用户填写的目标平台卖家货号;采集时不得从 `source_sku` 自动生成或填入。草稿可为空,但每个待首次上架 SKU 的 `offer_id` 必须非空且在同一店铺/平台唯一。 成功返回新 `product.revision`、`profile.revision`、创建的 SKU ID/`client_key` 映射和 warnings。任一字段、属性或媒体无效时返回 `422` 且不部分保存;版本冲突返回 `409 VERSION_CONFLICT`,客户端应重新读取编辑快照。 ## 3. 目标店铺、类目与属性 `PUT /api/product-platform-profiles/{profile_id}/target-store` Headers:`If-Match`、`Idempotency-Key` 必填。Body:`{ "target_store_id": "uuid" }`。这是编辑器完整保存之外的快捷动作,遵守“仅未导入 SKU 更换未来目标店铺”的规则。 `GET /api/product-platform-profiles/{profile_id}/attribute-schema` 当前 profile 必须已有类目。返回平台适配后的字段 schema、字典端点、`is_required`、`is_collection`、`max_value_count`、`is_aspect` 和适用 scope。类别未选返回 `422 CATEGORY_REQUIRED`。 `GET /api/product-platform-profiles/{profile_id}/attribute-dictionaries/{dictionary_id}?q={keyword}&cursor={opaque}` 转发并缓存平台字典值;不让前端直接持有 Ozon 密钥。字典缺失、过期或类目/属性不匹配时返回 `422`。 `GET /api/product-platform-profiles/{profile_id}/validation` 返回 profile 与每个未删除 SKU 的本地首次上架校验:商品型号、类目/属性、标题、目标店铺、用户填写的 offer ID、确认的包装规格、已确认定价快照、图片、格式/数量及平台特有规则。已导入 SKU 单独标记 `not_publishable_again`,而不是当作校验失败。 ## 4. SKU 定价确认 `POST /api/product-platform-profiles/{profile_id}/variants/{variant_id}/pricing-confirms` Headers:`Idempotency-Key` 必填。Body: ```json { "preview_id": "opaque-id", "selected_option_id": "candidate-1", "one_time_overrides": null } ``` 服务端重新解析 SKU 的有效包装规格、验证预览仍未过期且配置/汇率/规则未变化,重新计算后写入 `pricing_quotes`、全部 `pricing_quote_options` 和当前平台 listing 的售价/划线价/币种/报价引用。成功返回 `201` 与不可变报价摘要。 包装规格缺失或 `ambiguous`、预览失效、候选不属于预览、目标店铺不一致、物流不适用时返回 `422`,不覆盖既有价格。临时更换方案或运输方式仅写入本次报价,不改门店默认方案。 ## 5. 素材选择与水印派生 媒体选择通常随第 2 节草稿一起保存。为支持图片弹窗的异步“添加水印”,提供: `POST /api/product-variant-platform-listings/{variant_listing_id}/watermark-derivatives` Headers:`Idempotency-Key` 必填。Body: ```json { "source_asset_id": "uuid", "watermark_template_id": "uuid" } ``` 服务端验证 source asset 是该 SKU 当前已选图片、模板属于当前工作空间且活动;固化模板**当前版本**后创建 `media_derivatives` 和 Worker 任务,返回 `202` 及 operation。任务成功后返回独立 output asset;原图和其他 SKU 的选择均不改变。用户在图片弹窗确认后,将 output asset 写入下一次 `editor-draft.media_asset_ids` 来替换原图位置;可再次选择原图恢复。首期不在发布时自动添加水印。 `GET /api/media-derivatives/{derivative_id}` 返回任务状态、脱敏错误及成功时的输出素材短时预览;不能泄露 `object_key`。 ## 6. AI 任务与人工应用 所有 AI 接口都只创建任务并返回 `202` + operation/`ai_task_id`,绝不直接改写草稿或发布数据。 | 接口 | body 核心字段 | 成功结果 | |---|---|---| | `POST /api/product-platform-profiles/{profile_id}/ai/category-suggestions` | 可选 `hint` | 读取采集证据和当前平台类目树,创建类目候选任务。Ozon 来源默认不自动调用,但用户可主动调用。 | | `POST /api/product-platform-profiles/{profile_id}/ai/copy` | `fields: [title,tags,description]`、可选语言 | 创建当前平台文案候选。首期不生成富内容。 | | `POST /api/product-platform-profiles/{profile_id}/ai/suite-plans` | `variant_ids` 必填、`prompt` 可选 | 每个目标 SKU 明确绑定,创建套图规划/生成任务。 | | `POST /api/product-platform-profiles/{profile_id}/variants/{variant_id}/ai/dimension-images` | 可选样式 | 固化该 SKU 合并后的**实物**规格快照;实物规格缺失/待确认返回 `422`。 | `GET /api/product-ai-tasks/{ai_task_id}` 返回状态、模型/输入摘要、候选输出或脱敏错误。 `GET /api/product-ai-tasks/{ai_task_id}/category-suggestions` 返回候选类目、置信度、依据、rank。 `POST /api/product-ai-tasks/{ai_task_id}/category-suggestions/{suggestion_id}/apply` Headers:`If-Match`、`Idempotency-Key` 必填。用户确认后才将候选写入 profile 的类目/类型并标记 `category_source=ai_suggestion`;随后要求重新读取 schema、审查未映射属性。文案/图片 AI 输出同样由用户在下一次 `editor-draft` 中明确选择并保存。 ## 7. 部分 SKU 首次上架 `POST /api/product-platform-profiles/{profile_id}/publish-jobs` Headers:`Idempotency-Key` 必填。Body: ```json { "variant_ids": ["uuid", "uuid"] } ``` 服务端在创建任务前重新运行第 3 节校验。只允许当前 profile 中状态为 `not_listed` 或 `failed` 的未删除 SKU;`submitting` 返回 `409 SKU_IMPORT_IN_PROGRESS`,`imported` 返回 `422 SKU_ALREADY_IMPORTED`,不得重新提交或把编辑草稿当平台更新。 校验通过后在同一事务创建 `product_publish_job` 和每个 SKU 的 `product_publish_job_item`,固化 profile、目标店铺、类目/属性、价格快照、图片访问 URL、offer ID 与平台适配请求,再投递 Worker。返回 `202`: ```json { "data": { "publish_job": { "id": "uuid", "status": "queued", "store_id": "uuid", "item_count": 2 } } } ``` 首期 Ozon `/v3/product/import` 不传仓库或库存。Worker 按 SKU 保存提交、轮询、成功/失败和平台原因;成功 SKU 状态为 `imported` 后不能从采集箱再次上架。SPU 的未上架/已部分上架/已上架由当前平台未删除 SKU 汇总,采集箱记录始终保留。 `GET /api/product-publish-jobs/{publish_job_id}` 返回 job、每个 SKU item 的 `queued/submitting/polling/imported/failed/skipped`、Ozon task/product ID、时间和可展示的脱敏错误。发布失败不删除草稿或报价;用户修正未导入 SKU 后可以创建新的首次上架任务。在线商品镜像由另一个同步模块维护,不能以此 job 的状态替代 Ozon 当前 `visible`、库存或审核事实。 ## 8. 已替代的旧草案 本文替代 [`../product/api.md`](../product/api.md) 中与商品编辑有关的路径。旧草案保留为历史背景,实施以本文件、[`pricing.md`](./pricing.md) 与 [`collection-and-extension.md`](./collection-and-extension.md) 为准。