# 商品信息查询 API > 官方文档:https://docs.ozon.ru/api/seller/zh/#operation/ProductAPI_GetProductInfoListV3 --- ## 接口信息 | 项 | 值 | |---|---| | 方法 | POST | | 路径 | `/v3/product/info/list` | | 鉴权 | 需要 `Client-Id` + `Api-Key` | | 用途 | 查询商品详细信息(含审核状态、图片、属性、错误) | --- ## 1. 请求 ### 请求体 ```json { "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) ```json { "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 商品 ID(`product_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 ```python # 导入后用 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:检查审核状态 ```python 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:读取审核错误 ```python 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. 错误处理 ### 商品不存在 ```json { "result": { "items": [] } } ``` 返回空数组,非 404 错误。 ### 部分成功 ```json { "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(服务端) ```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(前端) ```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 层(待实现) ```python # 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} ``` ### 前端(商品详情页展示审核状态) ```tsx // 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 ( ); } ``` --- ## 10. 性能优化 ### 批量查询 ```python # 一次查询多个商品(最多 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" ]) ``` ### 缓存策略 ```python # 商品详情变化不频繁,可短期缓存 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() ``` --- ## 相关文档 - [04-product-import.md](./04-product-import.md) —— 创建/更新商品 - [06-product-list.md](./06-product-list.md) —— 商品列表分页 - [09-stocks.md](./09-stocks.md) —— 库存管理 - [docs/v2/ozon-publish.md](../v2/ozon-publish.md) —— 发布集成方案