Files
atlas-seller-kit/docs/api/product-editor-and-publishing.md

48 KiB
Raw Permalink Blame History

商品编辑与首次上架 API

M3.0~M3.4 已实现编辑上下文、草稿、SKU、来源素材、类目和动态属性。平台目录刷新方案第 1~4 步已把类目树、类型属性、字典读取及发布预检迁至平台级缓存:编辑器按需刷新,首次发布预检只刷新当前类型和实际使用的字典。AI 填报任务及随商品保存的定价确认已实现;真正调用 Ozon 的首次发布 job 仍未实现。

此处的商品始终是采集箱内的 Atlas 草稿。所有平台范围接口要求 X-Atlas-Platform-Id。商品通用资料可以跨平台复用;product_platform_profile、SKU 首次导入状态和在线商品不能跨平台/门店复用。

架构迁移说明:2026-09-18 已决定将类目/商品类型树、属性定义和字典候选值迁至平台级参考目录,同一平台所有门店共用,不维护门店差异层。迁移已完成到商品编辑和发布预检;旧门店级目录表仅保留兼容/回滚,不再作为读取、保存或发布校验事实。

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;
  • 首期新建 Ozon profile 的履约方式为 rfbs,与当前定价费率卡及报价接口一致;其他平台保持 unknown,由后续平台适配确定;
  • 当前平台没有默认店铺时返回 422 DEFAULT_STORE_REQUIRED,只给出店铺管理引导,不创建缺少目标门店的 profile;
  • 已存在 profile 时只读取,绝不因再次进入而覆盖用户已选门店、类目、属性或价格;
  • 不创建跨平台 profile,也不改变其他平台的上架状态。

插件「编辑上架」打开 /products/{product_id}/editor?collection_item_id={collection_item_id} 时,Web 先按页头当前平台调用此接口。核对响应 product_id 与路径一致后读取编辑快照,使首次采集和既有草稿均进入对应商品编辑页;采集接口本身不创建编辑上下文。

返回:

{ "data": {
  "collection_item_id": "uuid",
  "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 的完整编辑快照。核心形状:

{ "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" },
    "package_weight_mode": "common", "package_dimensions_mode": "common"
  },
  "profile": {
    "id": "uuid", "revision": 8, "platform_id": "uuid", "market_code": "RU", "fulfillment": "rfbs",
    "target_store": { "id": "uuid", "name": "Ozon 主店", "currency_code": "RUB" },
    "title": "俄语标题", "title_mode": "common", "description": null, "tags": [],
    "category": { "id": "uuid", "platform_type_id": "123", "source": "source_capture", "name": "收纳箱", "path": "家居 › 收纳 › 收纳箱" },
    "listing_summary": { "status": "partially_listed", "detail": "1 个 SKU 已导入" }
  },
  "variants": [], "attributes": [],
  "source_material": {
    "title": "用户整理后的来源标题", "description": "用户整理后的来源描述",
    "price": { "amount": "99.90", "currency_code": "CNY" }, "brand": "来源品牌",
    "attributes": [{ "key": "material", "name": "材质", "value": "棉" }],
    "categories": [{ "key": "bags", "name": "箱包" }], "source_snapshot_revision": 3
  },
  "source_material_state": {
    "latest": {},
    "overridden_fields": ["title", "description"],
    "changed_fields": ["title"],
    "base_source_snapshot_revision": 2,
    "has_source_updates": true
  },
  "captured_category_match": {
    "status": "ambiguous", "root_category_name": "儿童用品", "type_name": "小雕塑",
    "candidates": [{ "id": "uuid", "name": "小雕塑", "path": "儿童用品 › … › 小雕塑", "external_category_id": "123", "external_type_id": "456" }]
  },
  "ai_input": {
    "source_material": {}, "default_physical_spec": {}, "default_package_spec": {},
    "variants": [{ "variant_id": "uuid", "display_name": "黑色 / M", "specification_values": [],
      "effective_physical_spec": {}, "effective_package_spec": {} }]
  },
  "warnings": []
} }

variants 每项同时返回通用 SKU、合并后实物/包装规格、当前平台 listing 摘要(含 offer_id、价格、首次导入状态、最近脱敏失败原因)、SKU 覆盖的 tags / description 及已选媒体。AI 填报落地时新增 SKU 发布标题 title;title: null、tags: []、description: null 分别表示继承 SPU 当前平台的标题、主题标签、产品描述。读取模型还须返回逐 SKU 三项有效文案及中俄对照来源(inherit_spu / override),以支持编辑页和发布适配器使用同一继承结果;display_name 不能作为发布标题。动态属性 schema 由第 3 节的独立接口按当前类目读取。候选和已选媒体只含 asset_id、尺寸、MIME、来源角色与短时预览 URL;不含 object_key 或永久 URL。

default_package_spec 是 SPU 的存储事实,variants[].package_spec 仅保存该 SKU 的覆盖值;effective_package_spec 按“SKU 覆盖值优先、SPU 默认值回退”解析。Ozon 发布适配器在创建 job 时才将每个目标 SKU 的有效值物化为独立 items[]:length_mm → depth、width_mm → width、height_mm → height、shipping_weight_g → weight,并固定带上 dimension_unit: "mm" 与 weight_unit: "g"。不得为了 payload 预先将 SPU 默认值复制到所有 SKU 草稿。商品净重/尺寸不是这组固定物流字段;它们的 SPU/SKU 展示归属由当前 Ozon schema 的 is_aspect 决定。

编辑快照的 product.package_weight_mode 和 product.package_dimensions_mode 分别保存包装重量、包装尺寸的编辑模式(common / variant);保存草稿的 common 中可传同名字段,旧客户端省略时保留当前模式,新商品默认 common。模式独立于数值保存,使逐 SKU 表单尚未填写时重新打开仍保持原模式。切到 variant 时客户端清空并禁用对应 SPU 输入;切回 common 时经确认清空所有 SKU 对应覆盖值并恢复 SPU 输入。两项切换互不影响,模式不改变有效包装规格的“SKU 覆盖优先、SPU 回退”解析规则。

商品编辑页的包装重量和长宽高仅允许手工输入正整数(g / mm),定价详情弹窗采用相同输入规则。已有小数值保留原样供人工核对,不自动取整;API 暂保留三位小数的历史数据兼容能力。

source_material 是供页面展示和 AI 使用的有效来源素材,由当前 collection_items.normalized_payload 的最新规范化值与用户明确修改过的顶层字段合并而成。未修改字段随重复采集自动更新;已修改字段继续保留用户值。source_material_state.latest 保留本次最新规范化来源,overridden_fields 表示用户覆盖字段,changed_fields 表示复采后与用户稿不同的覆盖字段,页面可据此提示、逐项恢复或全部采用最新采集值。原始 normalized_payload 与 raw_evidence 始终作为采集证据保留,不因编辑而改变。

captured_category_match 仅对 Ozon 来源返回自动类目判定结果。服务端只使用公开详情页的一级面包屑和结构化 Type_0 商品类型,在当前平台目录的活动可发布叶子中精确匹配;公开页后二、三级导航不参与判定。matched 时服务端已自动写入 source_capture;ambiguous 时前端展示 candidates 的完整 Atlas 路径,用户确认后按普通手选类目保存为 manual;unmatched、unavailable 均不得自动选择。

ai_input 是后续 AI 任务的服务端输入视图:由上述有效 source_material、父商品默认实物/包装规格,以及每个 SKU 的稳定 ID、来源/展示名称、合并继承后的有效规格与规格轴组成。统一 AI 填报还需读取已保存的 SPU 与逐 SKU 文案及其覆盖状态,避免覆盖手填值;来源 SKU 仅作证据,不直接作为目标货号。

2. 原子保存草稿与 SKU 规则

单张 AI 图片编辑(SKU 选图弹窗)

商品编辑页固定任务入口使用 GET /api/products/{product_id}/image-tasks?limit=20&cursor=...。limit 默认 20、最大 100,按进行中优先、创建时间倒序、ID 倒序稳定分页,返回 { data: ProductImageTask[], meta: { next_cursor, has_more } }。接口只返回当前工作空间、当前商品的图片 AI 任务;包含单张编辑和套图任务;套图汇总为整套任务,内部子任务不重复展示。每项包含 kind: single_ai_edit | ai_suite、status、phase、已完成数/总数、创建/完成时间、稳定错误码、failure_source: pcore | atlas | null、failure_stage: task_setup | input_preparation | provider_submission | provider_polling | result_download | result_processing | result_storage | null、failure_detail: string | null、provider_error_raw: string | null 与结果素材 ID 数组。失败来源表示 Pcore 接口返回/调用失败或 Atlas 自身处理失败;步骤指出实际出错的位置。failure_detail 是最长 500 字符的脱敏诊断摘要,不包含密钥、对象键或预签名 URL。Pcore 失败时 provider_error_raw 保留供应商原始错误响应文本(JSON 响应保存错误对象,最多 32,768 字符),便于在当前工作空间的任务列表中查看;没有供应商响应时为 null。历史任务未记录的字段返回 null,不得按通用错误码猜测失败来源。GET /api/media-ai-edits/{task_id} 的单张编辑响应也返回三个诊断字段,供未关闭的编辑弹窗显示。不返回供应商任务 ID、对象键或预览 URL。任务入口每 3 秒刷新运行中的任务,其余时间低频刷新;任务提交后即时刷新。查看结果时再用素材访问接口签发短时预览 URL。单张编辑任务取消沿用 POST /api/media-ai-edits/{task_id}/cancel;套图取消与失败重试见 商品 AI 套图。

POST /api/media-assets/{asset_id}/ai-edits:必须带 Idempotency-Key,请求体带当前工作空间 product_id,图片字段只使用正式素材 ID,不接受图片 URL。asset_id 为固定主图;reference_asset_ids 最多两项,仅作辅助参考。请求支持 prompt、mode: edit | outpaint、ratio: 3:4 | 1:1 | 4:5、图中文字翻译与目标语言、背景模式与描述、五种可改写的视觉风格、商品长宽高及尺寸标注图。outpaint 只允许 3:4、单主图,不生成尺寸标注。成功返回 HTTP 202、任务 ID 和 queued 状态。

GET /api/media-ai-edits/{task_id} 查询 queued | running | succeeded | failed | cancelled,成功时才返回独立 output_asset 的正式素材 ID、尺寸与短时预览地址;失败仅返回稳定错误码。GET /api/media-ai-edits?product_id={product_id} 返回当前商品最近 100 条成功任务,供右侧「AI 生图记录」复用素材。POST /api/media-ai-edits/{task_id}/cancel 需带 Idempotency-Key,将未完成任务标记为取消;已完成任务保持原结果。任务、operation、Outbox 在同一工作空间事务提交;Worker 持久化供应商任务 ID 后通过延迟 Outbox 继续轮询。供应商密钥、对象存储键和供应商临时图片地址不进入 API 响应或 Outbox。

供应商异步任务返回终态失败时不再轮询或重试同一任务 ID。只有供应商明确返回内容政策或审核错误码时才使用稳定错误码 AI_IMAGE_CONTENT_REJECTED;笼统的 failed(即使消息称“可能”涉及安全政策)及其他供应商终态失败使用 AI_IMAGE_PROVIDER_FAILED;提交、网络、存储等非终态问题在重试耗尽后使用 AI_IMAGE_GENERATION_FAILED。Worker 在终态失败或重试耗尽时把失败来源、步骤和脱敏摘要写入同工作空间的 operation 结果;用户界面显示阶段和摘要,并在任务卡片展示已取得的 Pcore 原始错误内容。历史任务若原先未保存错误内容,则相关字段为 null。

Pcore 返回的图片地址若与服务端 PCORE_BASE_URL 同源,且严格对应当前已保存任务的 /v1/images/content/{provider_task_id}/0(无凭据、查询参数和片段),Worker 将其作为供应商任务 API 读取:从配置地址与任务 ID 重新构造 HTTPS 请求,保持 TLS 校验,不发送 API Key,不跟随重定向,下载上限 25 MiB,后续仍校验实际图片格式和尺寸。该受控接口沿用供应商 API 的系统网络解析,可在开发代理 Fake-IP DNS 环境下工作;不将 Fake-IP 地址加入通用下载白名单。外部图片地址继续校验公网 IPv4 并固定连接地址;同源但不匹配当前任务的路径直接拒绝。此处理共用于单张编辑与套图,不改变 HTTP 接口字段或任务状态契约。

生成结果先在单张 AI 编辑抽屉中预览。用户点击“添加到当前 SKU”后,新素材插入原图后方,原图保留;选图弹窗“确认”仅写入编辑页表单,页面“保存”后才持久化 SKU media_asset_ids。关闭或取消编辑不修改 SKU 草稿。图片源、参考图、结果均受工作空间隔离;任务输出与源图分别存储。

PUT /api/products/{product_id}/editor-draft

Headers:If-Match: "{product_revision}:{profile_revision}"、Idempotency-Key 必填。请求是整个编辑页的原子快照:

{
  "ai_source_material": {
    "title": "用户整理后的来源标题", "description": "用户整理后的来源描述",
    "price": { "amount": "99.90", "currency_code": "CNY" }, "brand": "来源品牌",
    "attributes": [], "categories": [], "source_snapshot_revision": 3
  },
  "ai_source_overridden_fields": ["title", "description"],
  "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" },
    "package_weight_mode": "common", "package_dimensions_mode": "common" },
  "profile": { "target_store_id": "uuid", "fulfillment": "rfbs", "title": "Название", "title_mode": "common", "description": "…", "tags": ["tag"],
    "platform_product_category_id": "uuid", "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, "tags": ["tag"], "description": "该 SKU 的描述",
    "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" }]
}

语义与校验:

  • ai_source_material 是客户端当前看到的完整有效素材,ai_source_overridden_fields 仅列出用户明确修改的顶层字段。服务端只持久化这些字段的覆盖值,其余字段继续跟随最新采集结果;恢复字段时将其从该数组移除即可。
  • 来源素材与商品/SKU 草稿在同一事务保存,受 product.revision 保护。ai_source_material.source_snapshot_revision 必须等于服务端当前采集版本;保存期间发生复采时返回 409 SOURCE_SNAPSHOT_CHANGED,客户端应重新读取合并结果并让用户确认,不能用旧页面覆盖新来源。
  • 保存来源素材不会修改原始采集证据。ai_source_overridden_fields 只允许 title、description、price、brand、attributes、categories,且每项按完整顶层值覆盖,不做数组或对象内部的隐式合并。
  • profile.platform_product_category_id 必须是当前平台的活动类目;同平台门店共享类目、类型属性和字典缓存。服务端从平台类目记录取得 platform_type_id,客户端不能伪造类型 ID。过渡期仍接受 store_product_category_id,但只用于按 description_category_id + type_id 映射平台类目,不参与当前读取和校验。
  • 类目切换或平台 schema 移除属性时,原有不再适用的属性不会进入新的发布草稿;服务端将它们写入 unmapped_attributes 并在编辑快照中返回。用户删除 SKU 时,其 SKU 属性随 SKU 一起移除,不生成无主未映射属性。
  • variants 是当前完整未删除 SKU 集合,最多 1000 项(2026-10-06 从 100 提高,与采集载荷共享 PRODUCT_VARIANT_LIMIT)。省略既有 SKU 表示软删除;无论当前 SPU 状态都允许新增、复制、删除。copy_from_variant_id 仅用于新行,复制通用 SKU 数据但不复制任何平台导入映射,生成的新 SKU 从 not_listed 开始。超出上限返回字段校验错误且不部分保存;首次发布每批最多 50 个 SKU 的独立限制保持不变。
  • variants[].purchase_cost_cny 是人民币采购价,编辑快照以两位小数字符串返回;草稿输入最多两位小数。来源采集写入时按两位小数四舍五入。
  • 删除已导入 SKU 只从 Atlas 草稿及当前平台可发布集合移除,不删除、不下架、不修改 Ozon 商品或在线镜像;其他平台历史映射保留。删除后当前平台只剩已导入 SKU 时,SPU 汇总自然为“已上架”。
  • null 的 SKU 规格字段表示继承父商品;包装物流规格的 status 为 missing/ambiguous 时,不允许定价确认或首次上架,但仍允许保存草稿。发布前服务端必须对每个待发布 SKU 解析 effective_package_spec,确认长、宽、高、重量均存在且大于 0 后才可生成 Ozon item。实物规格不用于物流;是否向 SPU 或 SKU 展示由其对应的类目 schema 属性决定,尺寸图仍可使用已确认的有效实物规格。
  • target_store_id 只能是当前工作空间、当前平台的活动店铺。变更目标店铺只更新尚未导入 listing 的将来发布目标;imported listing 永远保留其首次导入时的店铺快照。
  • 类目变化后不匹配的旧属性作为未映射草稿返回 warnings,不能自动进入发布 payload。属性由平台 schema 校验 type、必填、字典、多值/复杂属性和 is_aspect;客户端不能用任意 JSON 绕过。
  • media_asset_ids 是本 SKU 当前平台的最终选图顺序;最多 30 张。格式只允许 JPG/PNG/WebP,单文件最多 20 MB,边长超过 10,000 px 拒绝,短边小于 700 px 不得作为首图并给出警告。第一张是主图。已完成的 product_image 上传素材和网络导入素材仅可供所属商品的 SKU 选择;当前商品成功的水印与 3:4 处理结果也可选择。采集图片仍须先转存,且来源变体须匹配。
  • common.model 是用户手填或主动 AI 填报的商品型号;采集时允许为空,但每次首次上架前必须非空。offer_id 是用户手填或主动 AI 填报的目标平台卖家货号;采集入箱时不填值,生成时不直接复用 source_sku。草稿可为空,但每个待首次上架 SKU 的 offer_id 必须非空且在同一店铺/平台唯一。AI 填报接口和回填规则见方案。
  • 商品编辑器的货号控件固定展示 common.model + "-" 前缀,用户只编辑 SKU 后缀;请求中的 offer_id 仍为完整货号,无新增字段。未导入 SKU 修改型号时保留后缀、同步前缀;后缀为空时货号仍为空。旧货号匹配当前型号前缀时拆出后缀,否则将完整旧值保留为后缀,补前缀只进入待保存草稿,不批量改写历史数据。AI 回填遵循相同组合方式。完整货号继续限制 50 字符,导入/进行中 SKU 保留原货号并只读,已有型号锁定规则继续生效。
  • common.source_title 在编辑页显示为“中文商品名”;profile.title 显示为“俄文标题”。profile.title_mode 为 common / variant,编辑快照必须返回当前模式,保存草稿时可传入;旧客户端省略时保留原模式,新商品默认为 common。切到 variant 时清空并禁用 SPU 俄文标题,在各 SKU 展示标题输入;切回 common 时经确认清空所有 SKU 俄文标题,恢复 SPU 输入。两种模式均允许空白草稿;首次上架预检在 common 模式检查 profile.title,在 variant 模式逐个检查待发布 SKU 的 variants[].title,缺失时返回 PUBLISH_TITLE_REQUIRED。草稿仍须携带有效的 target_store_id 作为店铺上下文,并遵守已填写字段的格式和长度约束。SKU 标题 variants[].title 为 null 时在旧覆盖语义下继承 profile.title,不回退到 display_name;新版逐 SKU 模式不允许发布时依赖该继承。现有 SKU tags=[]、description=null 保持继承语义。标题、标签、描述分别独立覆盖。中俄对照按 SPU/SKU 与字段随 editor-draft 原子保存;SKU 继承项直接引用 SPU 对照,手工俄文覆盖但无匹配中文时标为对照待更新。发布适配器使用 SKU 有效文案仍是后续工作。

成功返回新 product.revision、profile.revision、创建的 SKU ID/client_key 映射和 warnings。任一字段、属性或媒体无效时返回 422 且不部分保存;版本冲突返回 409 VERSION_CONFLICT,客户端应重新读取编辑快照。

2.1 独立保存采集商品描述

PUT /api/products/{product_id}/source-description

右侧“采集的商品信息 → 商品描述”的“保存描述”按钮使用该接口,不依赖整页草稿可通过校验。Headers:X-Atlas-Platform-Id、If-Match: "{product_revision}:{profile_revision}" 和 Idempotency-Key 均必填。请求体为:

{ "description": "用户补充后的来源描述", "source_snapshot_revision": 3 }

服务端仅更新 ai_source_material_overrides.description,保留其他来源字段已有的覆盖值,并递增 product.revision;profile.revision 不变。若描述与当前最新采集描述完全相同,则移除该字段覆盖,使它继续跟随后续复采。接口返回最新双版本号及合并后的 source_material / source_material_state,页面据此立即更新右侧面板;刷新编辑页后仍能读取已保存描述。采集版本或编辑版本不一致时返回 409,客户端显示保存失败提示,不得以旧描述覆盖新采集快照。

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 必须已有平台类目。响应包含 { items, cache };字段 schema 额外返回 Atlas attribute_id,供字典接口精确限定属性作用域,其余字段包括 platform_attribute_id、complex_id、名称、value_type、is_required、is_collection、max_value_count、dictionary_id、is_aspect 与 editor_field。editor_field 只按 Ozon 属性 ID 计算,不允许按名称或翻译文本匹配:品牌 85 → brand、型号名称 9048 → model、类型 8229 → category、主题标签 23171 → tags、JSON 富内容 11254 → rich_content、简介/产品描述 4191 → description,其他 ID 返回 null。可带 ?category_id={platform_category_uuid} 预览尚未保存的新类目 schema。

商品颜色按 Ozon 属性 ID 10096 识别,允许每个 SKU 选择最多 100 个有效字典值。此业务规则统一用于编辑、草稿保存和发布快照,优先于目录中 is_collection=false / max_value_count=1 的单值标记;其他属性继续遵守自身 schema。草稿仍保存字典值 ID,发布时逐项映射为 { dictionary_value_id, value },不得只保留第一个颜色或合并成一条文字值。Web 已选标签保留对应名称,初始化时加载颜色字典;名称加载前可使用已知颜色的俄文名称兜底,编号不作为颜色文本显示。

cache.status 为 fresh | stale | expired | missing,并返回 last_success_at、fresh_until、expires_at、is_complete、refresh_run_id、refresh_status 和 retry_after_ms。fresh 直接返回;stale 返回旧 schema 并创建或复用低优先级平台任务;expired/missing 创建或复用高优先级平台任务并最多等待 5 秒,仍未完成时以 HTTP 202 返回空 items 和缓存状态。API 不直连 Ozon。

GET /api/product-platform-profiles/{profile_id}/attribute-dictionaries/{attribute_id}?q={keyword}&cursor={opaque}

返回 { items: [{ value_id, value }], next_cursor, cache },每页最多 200 项。attribute_id 是 schema 返回的 Atlas 平台属性 UUID,不再只用可能跨类型重复的 Ozon dictionary_id 定位。完整字典使用字典整体状态;部分字典本地搜索未命中时,规范化关键词使用独立 dictionary_search 状态。fresh/stale/expired/missing 的返回、优先级和 5 秒等待规则与 schema 相同;expired/missing 的旧候选值不作为当前选项返回。

GET /api/product-platform-profiles/{profile_id}/category-options?q={keyword}

返回当前平台共享的缓存类目树节点,包含平台类目 UUID、父节点 UUID、Ozon description_category_id / type_id、名称、路径和活动状态。接口不再接收或使用 store_id;切换同平台目标门店不会清空类目。树为空时客户端提示联系系统管理员同步。

POST /api/product-platform-profiles/{profile_id}/attribute-schema-syncs?category_id={uuid}

兼容入口:为平台类目创建或复用高优先级 platform_catalog_sync_runs。普通编辑器读取已自动按新鲜度触发刷新,不要求用户先点击本接口。

POST /api/product-platform-profiles/{profile_id}/attribute-dictionaries/{attribute_id}/syncs?category_id={uuid}&q={keyword}

兼容入口:不传 q 时刷新该平台属性的字典批次;传入至少两个字符的 q 时刷新独立关键词作用域。任务写入平台 Outbox,由 Worker 使用系统后台配置的平台参考源凭据调用 Ozon,响应不会携带密钥。

GET /api/product-platform-profiles/{profile_id}/validation

返回 profile 与每个未删除 SKU 的本地首次上架校验:每个待发布 SKU 的有效标题(SKU 覆盖优先、SPU 默认回退)、型号、品牌、类目/属性、目标店铺、用户填写的 offer ID、确认的包装规格、已确认定价快照、图片、格式/数量及平台特有规则。SKU 覆盖标题或 SPU 回退标题均为空时,该 SKU 不可发布;已导入 SKU 单独标记 not_publishable_again,而不是当作校验失败。SKU 标题生效前此处仍按现有 SPU 标题校验。

响应 data:

{
  "ready": false,
  "profile": {
    "ready": false,
    "issues": [
      {
        "code": "OZON_ATTRIBUTE_REQUIRED",
        "field": "attributes.10001.0",
        "message": "请填写最小儿童年龄",
        "platform_attribute_id": "10001"
      }
    ]
  },
  "variants": [
    {
      "variant_id": "uuid",
      "display_name": "红色",
      "status": "blocked",
      "ready": false,
      "issues": [
        {
          "code": "OZON_ATTRIBUTE_REQUIRED",
          "field": "variants.uuid.attributes.10096.0",
          "message": "请填写颜色",
          "platform_attribute_id": "10096"
        }
      ]
    }
  ]
}

profile.issues 包含商品级基础信息、类目 schema 和 is_aspect=false 的必填属性;每个 SKU 的 issues 包含货号、价格、有效包装规格、图片和 is_aspect=true 的必填属性。只有 confirmation_status=confirmed 且包含非空值的属性满足发布要求。ready=true 要求 profile 无问题、至少存在一个未上架 SKU,且全部未上架 SKU 均通过校验。

POST /api/product-platform-profiles/{profile_id}/publish-preflights

Body:{ "variant_ids": ["uuid", "uuid"] },最多 50 个且不能重复。该接口是创建真正发布 job 之前的可重算预检,不固化商品发布快照;平台目录任务按资源 scope_key 幂等合并,因此重复预检不会并发重复请求 Ozon。

预检只接受当前 profile 下 not_listed / failed 的未删除 SKU。submitting 返回 409 SKU_IMPORT_IN_PROGRESS,imported 返回 422 SKU_ALREADY_IMPORTED,不属于当前商品或已删除返回 422 PUBLISH_VARIANTS_INVALID。

目录处理顺序:

  1. 只检查当前 platform_product_category_id 的 schema。fresh 直接使用;stale 使用旧 schema 并创建/复用 low、trigger_kind=publish 的平台任务;expired/missing 创建/复用 high 任务。
  2. schema 可用后,从本次 SKU 集合的已确认草稿中提取实际使用的字典属性。未填写的可选字典、其他 SKU 和同类型下未使用字典都不刷新。
  3. 字典沿用相同 14/21 天状态机。expired/missing 候选值不能作为发布依据;预检最多共用一个 5 秒等待窗口观察所有 high Worker 任务,不按字典数量逐项累加等待。
  4. high schema 成功后,预检再根据新 schema 计算字典依赖;最后重新执行必填、scope、包装、价格、图片以及字典值有效性校验。

可立即完成时返回 HTTP 200;仍在等待 high 任务时返回 HTTP 202。响应包含 status=ready|blocked|refreshing、完整 validation、schema/已用字典的 catalog cache 元数据,以及本次创建或复用的 refresh_runs。

refreshing 时客户端使用相同 SKU 集合重新预检;不得退回过期缓存。blocked 表示目录资源可用但商品草稿仍有业务校验问题;ready 才能进入未来的发布快照创建事务。Ozon 最终仍可能拒绝 payload,该错误属于具体商品/门店的发布结果,不能据此创建门店级目录副本。

当前预检只对 profile 引用的类目执行本地活动状态校验,并对该类目的 schema 与实际使用字典执行新鲜度刷新;它尚未判断整棵类目树缓存是否过期,也不会因树过期先创建树同步任务。若本地类目不存在、已停用或不属于当前平台,API 返回 Atlas 自己的 422 CATEGORY_INVALID,这不是 Ozon 接口响应。后续真正发布 job 接入前,需要明确补齐树级新鲜度保护或确认“每周同步 + 本地活动状态”足以作为发布边界。

4. SKU 定价确认

定价确认与 PUT /api/products/{product_id}/editor-draft 同一事务完成。每个 SKU 可携带 pricing_draft 和 pricing_selection: { preview_id, option_id, input_hash };新 SKU 使用 client_key 绑定预览,保存时映射为真实 ID。独立试算不能直接确认到 SKU。

服务端核对预览归属、15 分钟有效期、当前采购价、有效包装、类目、门店、币种及规则版本,再写入不可变 pricing_quotes、全部候选和 listing 价格。任何一个明确选择的报价失效,整个商品保存回滚。客户端可提示用户重新计算,或经用户选择清除本次确认意图后仅保存草稿。

已上架(imported)SKU 可通过「重新试算」或「试算详情 → 应用到 SKU」显式选择新报价。应用前提示「该 SKU 已上架。本次价格调整仅保存为本地记录,不会同步至 Ozon;如需修改线上价格,请前往 Ozon 后台。」取消不应用报价,确认后仍须保存商品。编辑快照 variants[].listing.store_id 返回首次导入店铺,已上架 SKU 按此店铺试算和保存,不能改用 profile 后来选择的目标店铺。服务端仅在携带 pricing_selection 时更新其本地报价引用和价格;普通草稿保存保留原报价。首次导入状态、店铺、货号、平台商品 ID 和导入时间不变,不创建发布任务或价格同步任务。

没有报价时仍可保存商品,但首次发布预检返回 PRICING_REQUIRED。已确认报价不会随预览到期而失效;采购、包装、目标门店/类目、履约或门店币种改变后须重新确认。调整默认方案、汇率或费率不会反向改写历史快照。详见定价 API。

经维护者 2026-09-29 确认,使用草稿物流费率卡完成的报价可以保留到首次发布预检;未审核状态只作为来源信息展示,不再生成 PRICING_RATE_REVIEW_REQUIRED。当前仍要求 SKU 已确认有效报价、售价、包装和其他发布必填数据。生产环境默认可使用草稿卡,管理员可通过 PRICING_ALLOW_DRAFT_RATES=false 限制其进入新试算;审核流程仍可用于核实费率。

5. 素材选择与水印派生

媒体选择通常随第 2 节草稿一起保存。为支持图片弹窗的异步“添加水印”,提供:

POST /api/media-assets/{asset_id}/watermark-derivatives

Headers:Idempotency-Key 必填。Body:

{ "watermark_template_id": "uuid" }

服务端验证源图片是当前工作空间已校验的 JPG/PNG/WebP 正式素材,模板属于同一工作空间且活动;固化模板当前版本后,在同一事务创建水印派生记录、operation 和 Outbox 事件,返回 202 及任务摘要。采集候选图先按需转存为正式素材。任务成功后返回独立 output asset;原图和其他 SKU 的选择均不改变。用户在图片弹窗确认后,将 output asset 写入下一次 editor-draft.media_asset_ids 来替换原图位置;同一弹窗内可撤销为原图。首期不在发布时自动添加水印。

GET /api/media-watermark-derivatives/{derivative_id} 返回 id、status、source_asset_id、watermark_template_id、watermark_template_version、output_asset 和脱敏 error_code。成功时 output_asset 包含正式素材 ID、宽高、类型、大小及短时预览 URL;不能泄露 object_key。输出保留原图显示方向与宽高,合成水印后编码为 JPG;透明原图以白色合成。模板的文字大小、图片像素尺寸、不透明度、圆角和相对位置以固化版本为准。失败后原图留在弹窗中。

6. AI 填报任务与页面回填

AI 填报接口只创建异步任务并返回 202 + ai_task_id,不直接改写已保存草稿或发布数据。任务完成后,编辑页自动把校验合格的字段回填到未保存表单,再由用户使用现有 editor-draft 保存。详细字段规则、冲突策略与中文对照存储见商品 AI 填报方案。

创建填报、文案或专用尺寸图 AI 任务前,前端必须先保存当前编辑草稿,服务端从已持久化的 ai_input 组装输入。填报任务读取全部 SKU 的规格摘要,并固化商品、profile、采集和类目目录版本。2026-10-03 经维护者确认的套图抽屉采用独立可编辑资料:目标 SKU 必须已经保存;名称、颜色、实物尺寸/重量、采集信息、卖点和逐张方案作为本次套图输入快照保存,不自动改写商品字段或发布规格。此行为及正式接口以 商品 AI 套图 为准;替换此前尚未实现的 profile 级套图规划接口。

接口 body 核心字段 成功结果
POST /api/product-platform-profiles/{profile_id}/ai/autofills 可选 hint 与策略开关;Header 必填 Idempotency-Key 一次创建类目、型号、未导入 SKU 货号、可证包装规格及中俄文案的填报任务。Ozon 来源先运行确定性类目匹配;其他来源使用受限类目树候选。
POST /api/product-platform-profiles/{profile_id}/variants/{variant_id}/ai/copy 可选 hint(用户描述/生成要求,去除首尾空白后最多 1000 字符);Header 必填 Idempotency-Key 为指定 SKU 创建中俄文案任务,固化用户要求与商品输入;返回 202、ai_task_id 和 queued。
POST /api/products/{product_id}/ai/suite-plans variant_id、底图、套图资料与配置 创建显式绑定一个目标 SKU 的异步逐张规划;生成由 POST /api/products/{product_id}/ai/image-suites 提交。详见 套图契约。
POST /api/product-platform-profiles/{profile_id}/variants/{variant_id}/ai/dimension-images 可选样式 固化该 SKU 合并后的实物规格快照;实物规格缺失/待确认返回 422。

GET /api/product-ai-tasks/{ai_task_id} 返回任务状态、输入版本、脱敏结果或错误;全局填报成功时返回类目或候选、型号、逐 SKU 货号、可证包装建议及 SPU 中俄文案,不生成 SKU 文案。单 SKU 文案由 POST /api/product-platform-profiles/{profile_id}/variants/{variant_id}/ai/copy 单独创建任务,在弹窗预览并由用户应用。Ozon 动态类目属性不属于当前 AI 填报范围,仍通过类目确认后加载的 schema 由用户填写。歧义类目仍需用户选择。中文对照与俄文在下一次 editor-draft 中一同保存,读取编辑上下文时一同返回。

单 SKU 文案中的 hint 是本次用户直接提供的描述、事实修正与生成要求,优先于采集标题、描述、旧 SKU 资料及已有文案;发生冲突时使用用户明确的修正,未修正的事实从当前 SKU 与来源资料补充。采集资料中的命令只当作资料,不可覆盖用户要求。JSON、中俄配对、俄文发布字段、标题及标签格式等输出契约继续生效。该要求只固化在本次 AI 任务输入中,不自动改写采集信息或商品草稿。修改要求会使弹窗旧结果失效,须重新生成后才能应用;关闭或切换 SKU 后,旧异步结果不能回填新弹窗。

应用当前 SKU 的标题结果而需从通用模式切换为逐 SKU 时,其他 SKU 原先继承的通用标题及对应中文对照转为各自的草稿值,继续保留其有效内容;仅当前 SKU 使用新生成结果。用户手工切换标题模式的原有清空规则不变。

2026-10-05 货源链接来源回填

编辑快照 product.common.purchase_url 优先返回商品手工保存的非空链接;为空时返回关联采集记录的 source_url,手动创建且无来源时仍为 null。插件已上报 source.canonicalUrl,新采集商品据此初始化货源链接;历史商品只在读模型中回退,不批量修改草稿或 revision。手工非空链接不因重复采集覆盖;保存 null 表示恢复使用采集来源地址。请求/响应字段及链接格式校验不变。

7. SKU 首次上架(2026-09-30 实施修订)

底部提供「上架至 OZON」和「部分上架」。前者选择全部 not_listed / failed 活动 SKU,列出已导入或正在提交的跳过原因;后者由用户选择相同资格的 SKU。一次 1–50 个,超过时提示分批,不自动拆批。先保存草稿,再对相同选择集合预检,用户确认后创建任务。

POST /api/product-platform-profiles/{profile_id}/publish-jobs

Headers:X-Atlas-Platform-Id、Idempotency-Key、If-Match: "product_revision:profile_revision" 必填。Body:

{ "variant_ids": ["uuid", "uuid"] }

创建事务锁定商品、profile 和 listing,重新校验版本、活动店铺、当前类目、仍可用的 schema / 实际引用字典和选中 SKU 的字段。无需强制刷新整棵目录;必须读取当前有效类目,过期 schema / 字典通过预检刷新,创建时仍过期则阻断。返回 409 VERSION_CONFLICT、409 SKU_IMPORT_IN_PROGRESS、422 SKU_ALREADY_IMPORTED、422 PUBLISH_VALIDATION_FAILED(逐字段 details)、422 PUBLISH_VARIANTS_INVALID。OZON_PUBLISH_ENABLED 在开发/测试默认允许,生产默认关闭,真实 VAT / 图片验收后部署显式开启;关闭时返回 503 OZON_PUBLISH_DISABLED。

同一事务创建 product_publish_jobs / product_publish_job_items、将 listing 置为 submitting,写 Outbox(只含 job ID)。冻结商品/profile 版本、店铺、货号、Ozon 类目和属性、十进制价格、逐 SKU 标题/标签/描述、有效包装规格、素材 ID / checksum / 对象身份;签名 URL 不持久化。标题按 title_mode 选择,标签/描述按 SKU 覆盖回退。货号最多 50 字符,发布标题最多 500 字符,每 SKU 1–30 张图。首期仅 rFBS,不传 SPU 共用条码、库存、仓库、顶层 description、视频或富内容;中国主体跨境 rFBS 首期省略 vat,实际导入与商品信息税率核对后再决定。

经维护者 2026-10-04 要求,首次上架的 Ozon price 使用已确认报价的 listing.list_price(划线价),保留十进制金额字符串精度,不发送 old_price。selling_price 在 Web 显示为「实际售价」,表示后续折扣后的目标售价,利润仍按此金额试算;划线价为初始上架预留折扣空间,商品上线后由用户在 Ozon 调整实际售价。预检与任务创建要求划线价大于 0 且不低于实际售价,缺失或不合法时返回 LIST_PRICE_REQUIRED / LIST_PRICE_INVALID,要求重新确认报价,不回退为实际售价。折扣率为 0 时两价相同,正常发布。上架确认页同时展示实际售价和划线价(本次上报价)。已创建任务继续使用冻结的价格快照,已导入 SKU 不因编辑草稿而更新远端价格。

主题标签在编辑与草稿中仍为数组,支持最多 30 个标签。生成 Ozon 属性 23171 时,按 SKU 覆盖或 SPU 继承取得有效标签,去除空项并用空格拼成一个 values[].value,例如 tags: ["#boho", "#loft"] 映射为 values: [{ "value": "#boho #loft" }];无标签时不提交该属性。schema 的 max_value_count=1 限制的是属性值条目数,不是其中的标签个数,不得据此拒绝多标签草稿。

每个数组项表示一个标签,必须以单个 # 开头,正文只允许 Unicode 字母、数字和下划线,标签内不得包含空格或其他 #;每项最多 30 个字符(含 #)。Web 对手工输入显示具体格式提示,草稿仍可保存供后续修改;发布预检及创建任务按所选 SKU 的有效标签(SKU 覆盖或 SPU 继承)检查,不合规时返回 OZON_HASHTAG_INVALID 字段问题并阻止发布。未被选中或被 SKU 覆盖的通用标签不阻断当前发布。AI 全局填报和单 SKU 文案提示词采用同一格式要求;输出回填前补齐缺失的 #,将词间空白、连字符转为下划线,过滤仍含非法字符、重复或超长的标签,并保留对应中文对照。不自动改写手工及历史草稿标签。

产品描述/简介的编辑、草稿及发布快照保留纯文本中的内部换行和空行。Worker 生成 Ozon 请求时,仅将简介属性 4191 的 CRLF/CR 规范为 LF 并转换成 <br>,连续换行转换成连续 <br>;文本中的 &、<、> 转义为实体,避免被当成 HTML 标签。其他属性不转换,快照不被修改。简介换行标签依据 Ozon 官方商品描述规范。Web 通用/SKU 描述及文案预览的复制入口直接复制原始纯文本,保留换行和空行,不复制上报用的 HTML;SKU 空描述复制其有效的 SPU 继承文本。此修复影响后续提交,不自动更新已导入的 Ozon 商品。

{ "data": { "publish_job": { "id": "uuid", "profile_id": "uuid", "store_id": "uuid", "status": "queued", "item_count": 2, "created_at": "ISO8601", "completed_at": null, "items": [] } } }

GET /api/product-publish-jobs/{publish_job_id} 返回 {data: ProductPublishJob};必须传平台上下文,仅可访问当前工作空间和平台。GET /api/product-platform-profiles/{profile_id}/publish-jobs 返回 {data: ProductPublishJob[]},最近 10 次任务,供页面刷新恢复。实际 items 含 id、variant_id、display_name、offer_id、status、platform_product_id、errors(code/field/message),不返回发布快照、签名 URL、密钥。job 状态为 queued/running/succeeded/partially_succeeded/failed/reconciling;item 状态为 queued/submitting/polling/imported/failed/skipped/reconciling。reconciling 表示提交结果不确定,listing 保持 submitting,禁止再次导入。202/任务 ID 仅表示受理。

Worker 提交前查询远端货号;已存在则标 skipped 并保留原因,不能调用导入更新它。每批最多 50 个 SKU,提交前生成七牛独立 HTTPS 私有签名图地址(有效期可配置,WebP 转 JPEG);交付域名应配置海外或全球加速并保证 Ozon 抓取端可访问,本地可下载不能替代海外连通性验收。来源与派生对象身份固定,地址仅在内存使用。导入请求的 task ID 持久化后通过延迟 Outbox 轮询,逐项处理 imported/failed/skipped。Ozon 商品导入与图片抓取独立异步完成:收到 imported 后通过 /v2/product/pictures/info 核对主图和附加图数量;图片尚未齐且无图片错误时继续既有轮询,不提前报告全量成功。确认缺图或图片下载错误时保留 imported 身份及脱敏警告,结果说明包含实际接收数/快照图片数,Web 显示「已导入(有提示)」。超过既有 120 次快速轮询窗口仍未齐或无法核对时,以 imported 并提示图片未齐/尚未确认结束,不把已创建商品降为可重复导入失败。平台导入返回的警告也必须保留;图片链接只用于内存核对,不持久化。提交前预写 submitting:如果超时、5xx 或响应丢失,下次仅按货号对账,不盲目再次导入;无法确认则保持 reconciling。明确 429 可按 Retry-After 延迟再提交。读请求失败可以重试。平台 errors 必须脱敏,不得包含签名 URL 或请求体。

导入成功锁定历史货号/product ID 与商品型号;其他草稿属性可修改,不隐式更新平台。存在提交中 SKU 时禁止删除该 SKU、改型号或变更目标店铺。任一 SKU 已导入后型号不可修改。沿用现有草稿换店行为:没有正在提交的 SKU 时可调整目标店铺,已导入 SKU 保留原店铺和导入身份,仍不能重复上架。失败保留草稿/报价,可修复并新建任务;任务成功不代表审核通过或有库存,在线商品事实仍由独立同步维护。

生产验收:同类中国主体跨境 rFBS 店铺省略 VAT 的真实导入及 /v3/product/info/list.vat 结果、七牛域名/证书/签名期/延迟抓取和 WebP 派生图。不能仅以模拟平台测试宣称已经通过生产验收。

8. 已替代的旧草案

本文替代 ../product/api.md 中与商品编辑有关的路径。旧草案保留为历史背景,实施以本文件、pricing.md 与 collection-and-extension.md 为准。

2026-09-28 SKU 定价确认修订

草稿的每个 variant 可携带 pricing_draft 和 pricing_selection,与商品保存原子提交。 读取 listing 返回 pricing_draft、pricing_result 和 pricing_valid。 确认失败整笔回滚并定位 SKU;无报价允许保存,但发布预检返回 PRICING_REQUIRED。 输入变化使报价失效;已确认快照保留。具体 DTO 与错误见 pricing.md 实施修订。