Files
ozon-seller-kit/docs/ozon-seller-api/05-product-info.md
T

14 KiB
Raw Blame History

商品信息查询 API

官方文档:https://docs.ozon.ru/api/seller/zh/#operation/ProductAPI_GetProductInfoListV3


接口信息

方法 POST
路径 /v3/product/info/list
鉴权 需要 Client-Id + Api-Key
用途 查询商品详细信息(含审核状态、图片、属性、错误)

1. 请求

请求体

{
  "offer_id": ["MY-THERMOS-001", "MY-THERMOS-002"],
  "product_id": [987654321],
  "sku": [123456789]
}

参数说明

字段 类型 必填 说明
offer_id array 可选 自己的货号数组(最多 100 个)
product_id array 可选 Ozon 商品 ID 数组(最多 100 个)
sku array 可选 Ozon SKU 数组(最多 100 个)

⚠️ 至少提供一个筛选条件offer_id / product_id / sku)。


2. 响应

成功响应(200

{
  "result": {
    "items": [
      {
        "id": 987654321,
        "name": "Термокружка Thermos из нержавеющей стали 500 мл",
        "offer_id": "MY-THERMOS-001",
        "barcode": "",
        "buybox_price": "2990.00",
        "category_id": 17033876,
        "created_at": "2024-08-10T10:30:00Z",
        "images": [
          {
            "file_name": "thermos-main-1.jpg",
            "default": true,
            "index": 0
          }
        ],
        "marketing_price": "2990.00",
        "min_price": "2690.00",
        "old_price": "3490.00",
        "premium_price": "2790.00",
        "price": "2990.00",
        "recommended_price": "2990.00",
        "sources": [
          {
            "is_enabled": true,
            "sku": 123456789,
            "source": "fbs"
          }
        ],
        "state": "processed",
        "stocks": {
          "coming": 0,
          "present": 100,
          "reserved": 5
        },
        "errors": [],
        "vat": "0.00",
        "visible": true,
        "visibility_details": {
          "has_price": true,
          "has_stock": true,
          "active_product": true
        },
        "price_index": "5.0",
        "images360": [],
        "color_image": "",
        "primary_image": "",
        "status": {
          "state": "processed",
          "state_failed": "",
          "moderate_status": "approved",
          "decline_reasons": [],
          "validation_state": "success",
          "state_name": "Processed",
          "state_description": "Product is processed",
          "is_failed": false,
          "is_created": true,
          "state_tooltip": ""
        },
        "description_category_id": 17033876,
        "type_id": 97114,
        "width": 80,
        "height": 200,
        "depth": 80,
        "dimension_unit": "mm",
        "weight": 320,
        "weight_unit": "g",
        "attributes": [
          {
            "attribute_id": 85,
            "complex_id": 0,
            "values": [
              {
                "dictionary_value_id": 971082156,
                "value": "Thermos"
              }
            ]
          }
        ]
      }
    ]
  }
}

3. 核心字段说明

基本信息

字段 类型 说明
id integer Ozon 商品 IDproduct_id
name string 商品名称
offer_id string 自己的货号
barcode string 条形码
created_at string 创建时间(ISO 8601

价格

字段 类型 说明
price string 当前售价
old_price string 划线价(原价)
marketing_price string 营销价
buybox_price string BuyBox 价格(赢得购物车的价格)
recommended_price string 平台推荐价
min_price string 允许的最低价(低于此价需申请)
premium_price string Premium 会员价

状态

字段 类型 说明
state string 商品状态(见下表)
status object 状态详情(含审核状态、错误原因)
visible boolean 是否可见(上架)
visibility_details object 可见性详情(是否有价格/库存/激活)

state 状态值

state 含义 说明
processed 已处理 商品创建成功,可正常展示
processing 处理中 正在处理(刚导入)
moderating 审核中 平台审核中
failed 失败 创建/审核失败,查看 errors
archived 📦 已归档 商品已下架归档

status.moderate_status 审核状态

moderate_status 含义
approved 审核通过
pending 待审核
declined 审核拒绝

库存

字段 类型 说明
stocks.present integer 当前库存
stocks.reserved integer 已预订数量
stocks.coming integer 即将到货数量

图片

字段 类型 说明
images array 图片数组
images[].file_name string 图片文件名
images[].default boolean 是否主图
images[].index integer 顺序
primary_image string 主图 URL
images360 array 360° 图
color_image string 营销色图

尺寸与属性

字段 类型 说明
description_category_id integer 类目 ID
type_id integer 商品类型 ID
width / height / depth number 尺寸
dimension_unit string 尺寸单位
weight number 重量
weight_unit string 重量单位
attributes array 属性数组(结构同导入)

错误信息

字段 类型 说明
errors array 错误数组(审核失败原因、字段错误等)
status.decline_reasons array 审核拒绝原因
status.validation_state string 校验状态:success / failed

4. 使用场景

场景 1:发布后回查 product_id

# 导入后用 offer_id 查询,获取 product_id
async def get_product_id_by_offer(offer_id: str):
    resp = await ozon_client.get_product_info(
        offer_id=[offer_id]
    )
    items = resp["result"]["items"]
    if items:
        return items[0]["id"]
    return None

场景 2:检查审核状态

async def check_moderation_status(product_id: int):
    resp = await ozon_client.get_product_info(
        product_id=[product_id]
    )
    item = resp["result"]["items"][0]
    status = item["status"]
    
    return {
        "state": status["state"],
        "moderate_status": status["moderate_status"],
        "is_approved": status["moderate_status"] == "approved",
        "decline_reasons": status["decline_reasons"]
    }

场景 3:读取审核错误

async def get_product_errors(offer_id: str):
    resp = await ozon_client.get_product_info(offer_id=[offer_id])
    item = resp["result"]["items"][0]
    
    errors = []
    
    # 字段错误
    if item.get("errors"):
        errors.extend(item["errors"])
    
    # 审核拒绝原因
    if item["status"].get("decline_reasons"):
        errors.extend(item["status"]["decline_reasons"])
    
    return errors

5. 错误处理

商品不存在

{
  "result": {
    "items": []
  }
}

返回空数组,非 404 错误。

部分成功

{
  "result": {
    "items": [
      {
        "id": 987654321,
        "offer_id": "EXISTING-001",
        /* ... 完整信息 */
      }
    ]
  }
}

请求 3 个 offer_id,只有 1 个存在 → 只返回 1 个 item。


6. 与其他接口的关系

/v3/product/import 的配合

① POST /v3/product/import → task_id
② POST /v1/product/import/info → status=imported, product_id=X
③ POST /v3/product/info/list (product_id=X) → 读取完整信息(含图片/审核状态)

用途:导入后可能需要:

  • 确认图片上传成功
  • 检查审核状态
  • 读取 Ozon 生成的 SKU
  • 查看价格索引(price_index,影响排名)

/v3/product/list 的区别

接口 用途 返回字段
/v3/product/info/list 详情查询 完整字段(图片/属性/状态/错误)
/v3/product/list 列表分页 基本字段(id/name/price/state),支持筛选/排序

选择建议

  • 已知 offer_id/product_id,要完整信息 → 用 info/list
  • 分页浏览所有商品、筛选状态 → 用 list

7. 示例代码

Python(服务端)

async def fetch_product_detail(
    client_id: str,
    api_key: str,
    offer_id: str = None,
    product_id: int = None
):
    """查询商品详情"""
    headers = {
        "Client-Id": client_id,
        "Api-Key": api_key,
        "Content-Type": "application/json"
    }
    
    payload = {}
    if offer_id:
        payload["offer_id"] = [offer_id]
    if product_id:
        payload["product_id"] = [product_id]
    
    async with httpx.AsyncClient(timeout=30.0) as client:
        resp = await client.post(
            "https://api-seller.ozon.ru/v3/product/info/list",
            headers=headers,
            json=payload
        )
        resp.raise_for_status()
        data = resp.json()
    
    items = data.get("result", {}).get("items", [])
    return items[0] if items else None

TypeScript(前端)

async function getProductDetail(
  productId: string,
  by: 'offer_id' | 'product_id' = 'offer_id'
) {
  const resp = await fetch('/api/products/detail', {
    method: 'POST',
    body: JSON.stringify({
      [by]: [productId]
    })
  });
  const data = await resp.json();
  return data.result.items[0];
}

// 使用
const detail = await getProductDetail('MY-THERMOS-001', 'offer_id');
console.log('审核状态:', detail.status.moderate_status);
console.log('库存:', detail.stocks.present);

8. 审核拒绝原因解读

常见拒绝原因

decline_reason 含义 解决
图片不符合要求 图片非白底/有水印/模糊 重新上传符合规范的图片
标题含禁用词 标题有夸大宣传/品牌侵权词 修改标题,去除违规词
描述不完整 描述过短或缺少关键信息 补充完整商品描述
类目错误 商品与类目不匹配 重新选择正确类目
属性缺失 缺少必填属性 补充必填属性
品牌未授权 品牌需授权认证 提供品牌授权书或改用无品牌

处理流程

① 读取 status.decline_reasons
② 根据原因修改商品(改图/改文案/改属性)
③ 重新调用 /v3/product/import(同 offer_id = 更新)
④ 再次审核

9. V2 项目集成

API 层(待实现)

# server/api/products.py

@router.get("/products/{product_id}/ozon-detail")
async def get_ozon_detail(
    product_id: str,
    db: AsyncSession = Depends(get_db)
):
    """查询商品在 Ozon 的详情(审核状态/库存/图片)"""
    product = await get_product(db, product_id)
    
    if not product.ozon_product_id and not product.offer_id:
        raise HTTPException(404, "商品尚未发布到 Ozon")
    
    # 获取店铺凭证(从发布记录找)
    task = await db.execute(
        select(PublishTask)
        .where(PublishTask.product_id == product_id)
        .order_by(PublishTask.created_at.desc())
        .limit(1)
    )
    task = task.scalar_one_or_none()
    if not task:
        raise HTTPException(404, "未找到发布记录")
    
    shop = await get_shop(db, task.shop_id)
    
    # 调用 Ozon API
    detail = await ozon_client.get_product_info(
        shop.client_id_dec,
        shop.api_key_dec,
        offer_id=[product.offer_id] if product.offer_id else None,
        product_id=[product.ozon_product_id] if product.ozon_product_id else None
    )
    
    return {"result": detail}

前端(商品详情页展示审核状态)

// studio/src/pages/product/components/OzonStatusBadge.tsx

import { Badge, Tooltip } from 'antd';

export function OzonStatusBadge({ productId }) {
  const [status, setStatus] = useState(null);
  
  useEffect(() => {
    fetch(`/api/products/${productId}/ozon-detail`)
      .then(r => r.json())
      .then(data => {
        const item = data.result.items[0];
        setStatus(item.status);
      });
  }, [productId]);
  
  if (!status) return null;
  
  const statusMap = {
    approved: { color: 'success', text: '审核通过' },
    pending: { color: 'processing', text: '审核中' },
    declined: { color: 'error', text: '审核拒绝' }
  };
  
  const config = statusMap[status.moderate_status] || {};
  
  return (
    <Tooltip title={status.decline_reasons?.join(', ')}>
      <Badge status={config.color} text={config.text} />
    </Tooltip>
  );
}

10. 性能优化

批量查询

# 一次查询多个商品(最多 100 个)
async def batch_get_products(offer_ids: list[str]):
    resp = await ozon_client.get_product_info(offer_id=offer_ids)
    return {
        item["offer_id"]: item
        for item in resp["result"]["items"]
    }

# 使用
details = await batch_get_products([
    "PROD-001", "PROD-002", "PROD-003"
])

缓存策略

# 商品详情变化不频繁,可短期缓存
from functools import lru_cache

@lru_cache(maxsize=1000)
async def get_product_info_cached(offer_id: str, ttl=300):
    # TTL 5 分钟
    detail = await ozon_client.get_product_info(offer_id=[offer_id])
    return detail["result"]["items"][0] if detail["result"]["items"] else None

# 审核状态变化时清缓存
get_product_info_cached.cache_clear()

相关文档