---
title: "Response Schema"
description: "SearchResult、Product、ReviewPage、CategoryList 与 ShippingQuote——所有平台统一返回的归一化类型，逐字段说明。"
---

五种类型覆盖了所有平台操作。同一个字段无论出现在哪里，含义都相同，而且每个概念只有一个名字——配送信息
在搜索卡片、商品、报价和运费报价中都是同一种形状。

可选字段是真正可选的：**缺失表示平台没有给出该信息**，而不是某个默认值。没有 `price` 意味着这条商品当前
没有在售报价，而不是免费。没有 `delivery.cost` 意味着运费尚未算出，而不是包邮——包邮是
`delivery.free: true`，那是另一个断言。

某个平台实际会填充哪些可选字段，差异很大；每个平台的文档里都有实测比例。

## SearchResult

```json
{
  "marketplace": "amazon",
  "query": "running shoes",
  "page": { "index": 1, "size": 48, "total": 1200 },
  "items": [ /* SearchItem */ ],
  "cursor": "…"
}
```

| 字段 | 类型 | 说明 |
|---|---|---|
| `marketplace` | string | 由哪个平台作答 |
| `query` | string? | 以图搜图时没有——依据的是一张图片 |
| `page.index` | number | 从 1 开始 |
| `page.size` | number | 实际返回的条数，而非请求的条数 |
| `page.total` | number? | 整个列表的条目总数，与平台给出的确切数值一致；若平台只给出约数（"260,000+"）则省略。它不保证每一条都能取到：没有下一页时翻页即结束 |
| `items` | SearchItem[] | |
| `cursor` | string? | 不透明值；只有按游标翻页的平台才会给出 |
| `facets` | Facet[]? | 平台针对本次查询给出的筛选栏——见下 |

### Facet

站点针对本次查询给出的筛选栏。缺失表示没有返回筛选栏；`[]` 表示本次查询没有筛选栏。

| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | string | 该分组的键：`brand`、`sock_rise` |
| `name` | string | 它的标签："Brand"、"Sock Rise" |
| `kind` | `select` \| `range` | `select` 列出取值；`range` 给出上下界 |
| `values[]` | FacetValue | `{value, name, count?, selected?}` |
| `min` `max` | number? | 用于 `range`；金额区间的单位与 `Money.amount` 相同 |
| `maps_to` | string? | 覆盖同一概念的类型化过滤器，例如 `brands` |

`value` 就是你在 `facets` 请求参数里回传的内容。它是可读的标签，而不是内部 id。

当 `maps_to` 存在时，优先使用那个类型化参数：它在每个平台上含义相同。

## SearchItem 与 Product

两者都扩展自同一种条目结构，因此同一个字段在结果卡片和商品详情页里含义一致。`Product` 只是补上了详情页
才有的内容。

### 两者共有

| 字段 | 类型 | 说明 |
|---|---|---|
| `marketplace` `id` `title` | string | 始终存在 |
| `url` | string? | 该商品自身的页面 |
| `price` | Money? | 买家今天支付的金额 |
| `listPrice` | Money? | 被划掉的参考价，折扣由此计算 |
| `condition` | string? | 按原文表述：`"Pre-Owned"`、`"Used: Very Good"`、`"New"` |
| `conditionNote` | string? | 卖家对该成色的自述 |
| `unitPrice` | object? | `{price, unit}`——`"$1.00 / count"` |
| `brand` | string? | 从规格表中提取；"Brand" 行不会在 `attributes` 中重复出现 |
| `identifiers` | object? | `{gtin?, mpn?, model?}`——条码（UPC/EAN/ISBN）、零件/型号编号、型号名称 |
| `rating` | Rating? | `{average, count?, distribution?, grouped?}` |
| `seller` | Seller? | `{id?, name?, url?, rating?, country?, detailedRatings?, positivePercent?}`——见下 |
| `salesCount` | number? | 归一化后的销量；由于平台会取整，这是一个**下限** |
| `salesLabel` | string? | 原样的销量文案：`"3,000+ sold"` |
| `inStock` | boolean? | |
| `stock` | number? | 可售数量，平台给出数字时才有 |
| `status` | string? | 按原文表述的库存说明：`"Only 3 left in stock"` |
| `delivery` | DeliveryOption? | 默认的配送方式——见下 |
| `shippingOptions` | DeliveryOption[]? | 展示或报价的全部配送选项，默认选项在前 |
| `returnsPolicy` | string? | |
| `badges` | string[] | 营销徽章：`["Amazon's Choice"]` |
| `promotions` | string[] | 优惠：`["5% off coupon applied"]` |
| `paymentOptions` | string[]? | 付款方式：`["Pay in 4 payments at 0% interest"]` |
| `listingFormat` | string? | 销售方式：`"Buy It Now"`、`"Auction"` |
| `groupId` | string? | 它所属的变体系列 |
| `attributes` | Attribute[] | `{name, value}` 键值对——规格表，去掉已提取到类型化字段的行 |
| `offersSummary` | object? | `{count?, fromPrice?, url?}`——汇总，而不是列表 |
| `priceRange` | object? | 以 Money 表示的 `{min, max}`——平台给出的各变体价格区间（"$12.99 – $24.99"），绝不根据部分变体推算 |
| `endsAt` | string? | 当前报价的截止时间：拍卖结束或限时优惠结束。ISO 8601 |
| `purchaseLimit` | number? | 每位买家最多可购买的数量（"每人限购 2 件"） |
| `listedAt` | string? | 商品首次在平台上架的时间（不是抓取时间）。ISO 8601 日期或时间 |
| `deliveryTo` | object? | `{label?, country?, postalCode?}`——`delivery` 和 `shippingOptions` 的配送承诺所针对的收货地 |
| `ext` | object? | 共享字段无法容纳、仅该平台才有的信息——见下 |

`Money` 是 `{currency, amount}`，其中 `amount` 以十进制主单位表示。

`ext` 是最后的手段：只要有含义相同的共享字段，信息就放进该字段；只有其他平台都没有的信息，才以该平台自己的
名称放进 `ext`。商品、变体、卖家、报价、评价、评价页和类目节点都可能带有它。

### DeliveryOption

所有配送信息都是这一种形状——`delivery`、`shippingOptions` 的每一项、报价的 `delivery`，以及
[ShippingQuote](#shippingquote) 的每个选项。

| 字段 | 类型 | 说明 |
|---|---|---|
| `estimate` | string? | 按原文表述的送达预估：`"FREE delivery Wednesday, October 28"` |
| `cost` | Money? | 运费 |
| `free` | boolean? | 平台声明包邮 |
| `minDays` `maxDays` | number? | 以数字给出时的运输天数区间 |
| `handlingDays` | number? | 卖家发货前所需的天数 |
| `guaranteedDays` | number? | 平台自己的送达保证 |
| `cutoff` | string? | 该选项的下单倒计时：`"Order within 3 hrs 22 mins"` |
| `condition` | string? | 适用条件：`"on orders shipped by Amazon over $35"` |
| `available` | boolean? | `false`——平台不向所请求的地区发货 |
| `membership` | string? | 此选项所需的会员资格，例如 `"Prime"`；缺省 ⇒ 适用于所有买家 |
| `minimumOrder` | Money? | `cost`/`free` 生效所需的订单金额——如Amazon的 "on $35 of items" |
| `carrier` `code` | string? | 平台所称的配送服务 |
| `tracking` | boolean? | |
| `shipsFrom` | string? | 包裹发出的国家 |

没有 `cost` 表示**未说明**，而不是包邮；包邮是 `free: true`。`available: false` 是一个答案，绝不等于运费为零。只要平台展示了，`delivery` 始终是适用于**所有**买家的选项——
会员专享的包邮（Prime）带着 `membership` 放在 `shippingOptions` 中，绝不会被当作所有人的条件。

### Seller

| 字段 | 类型 | 说明 |
|---|---|---|
| `id` `name` `url` | string? | |
| `rating` | Rating? | 卖家的评分，与商品的 `rating` 形状相同 |
| `country` | string? | 卖家所在地 |
| `detailedRatings` | object? | `{itemAsDescribed?, communication?, shippingSpeed?}`，采用平台自己的 0–5 分制 |
| `positivePercent` | number? | 卖家反馈中好评所占的比例，以 0–100 的百分数表示（"99.8% positive"），不是星级评分 |

`rating.grouped: true` 表示该汇总覆盖整个变体系列，而不是所请求的那一个变体。该字段缺失意味着*不确定
是否被合并*，这比"确定是按变体统计的"要弱。

### 仅 SearchItem

| 字段 | 类型 | 说明 |
|---|---|---|
| `imageUrl` | string? | 卡片的主图 |
| `sponsored` | boolean? | 付费展示位，而非自然结果 |
| `variantCount` | number? | 卡片上标明的组合数量（"+12 colors"）；`product` 会在 `variants` 中逐一列出 |
| `category` | CategoryStep[]? | 该结果在类目体系中的位置，叶子类目在最后；卡片通常只带叶子类目 |
| `matchScore` | number? | 仅限以图搜索：与所传图片的相似程度，取值 0–1（1 = 平台认定的最佳匹配），只能在同一组结果内比较 |

### 仅 Product

| 字段 | 类型 | 说明 |
|---|---|---|
| `description` | string? | 可能是 HTML |
| `features` | string[] | 页面顺序排列的要点 |
| `media` | object | `{images: string[], videos: []}` |
| `category` | CategoryStep[] | 完整路径，末级在最后；每一级有 `id`、`name`，或两者 |
| `options` | Option[] | 各个维度：颜色、尺码 |
| `variants` | Variant[] | 各种组合——见下 |
| `offers` | Offer[] | 平台会逐条列出时的竞争卖家 |
| `subscriptionPrice` | Money? | Subscribe & Save 及同类 |
| `reviewSummary` | string? | 平台自己生成的评价摘要 |
| `reviewAspects` | array | 带标签的情感分析：`{name, mentions?, positive?, negative?}` |
| `boxContents` | string[] | 包装清单 |
| `brandUrl` `fulfilledBy` `replacedBy` | string? | |
| `package` | object? | 寄出的包裹：`{lengthCm?, widthCm?, heightCm?, weightKg?, soldInLot?}` |
| `wholesale` | boolean? | 该商品提供批发价（见 `variants[].bulkPrice`） |
| `reviews` | Review[] | 商品页自身携带的热门评价 |
| `included` | string[]? | 所请求的扩展中实际取到的部分 |

### 变体，以及唯一需要检查的那件事

现实中存在两种变体模型，一个字段就能区分：

- **存在 `variant.productId`**——该组合本身就是可单独获取的商品。把这个 id 回传给 `product`。Amazon属于
  这一类。
- **没有 `variant.productId`**——该组合只存在于这个商品内部，你需要的东西都已经拿到了。Shopify、Walmart
  与AliExpress属于这一类。

你永远不需要知道自己在跟哪个平台打交道，检查这个字段即可。

| 变体字段 | 类型 | 说明 |
|---|---|---|
| `id` | string | 平台对该组合的 id |
| `productId` | string? | 存在 ⇒ 可以用 `product` 获取（见上） |
| `sku` | string? | 商家自己的库存编码 |
| `price` `listPrice` | Money? | |
| `bulkPrice` | Money? | 批量购买时的单价，仅在与 `price` 不同时给出 |
| `purchaseLimit` | number? | 单个买家最多可购买的数量 |
| `taxIncluded` | boolean? | `price` 是否含税 |
| `inStock` | boolean? | |
| `stock` | number? | 该变体的可售数量 |
| `identifiers` | object? | `{gtin?, mpn?, model?}`——平台按变体公布时，该组合自己的条码和零件号 |
| `weightKg` | number? | 该组合的重量（千克） |
| `selections` | array | `{optionId, valueId, optionName, valueName}`——它对应哪些选项取值 |

### Offer

平台会逐条列出时的竞争卖家：`{seller?, price, listPrice?, condition?, conditionNote?, fulfilledBy?,
inStock?, status?, delivery?}`。`delivery` 是一个 DeliveryOption，因为更便宜但发货更晚的报价是另一个报价。

## ReviewPage

```json
{
  "marketplace": "walmart",
  "productId": "996425081",
  "summary": { "average": 4.6, "count": 1204, "distribution": { "5": 78 } },
  "page": { "index": 1, "size": 10, "total": 990 },
  "reviews": [ /* Review */ ],
  "grouped": true
}
```

| 字段 | 类型 | 说明 |
|---|---|---|
| `summary` | Rating? | 描述 `reviews` 所取自的那个集合 |
| `page.total` | number? | 平台给出的列表中的评价条数，可能少于统计全部评分的 `summary.count`（Walmart 只列出带文字的评价）。**`0` 表示该商品没有评价** |
| `reviews[]` | Review | `{id?, author?, rating, title?, body?, date?, country?, verified?, helpfulCount?, variant?, images[], incentivized?, reply?}` |
| `grouped` | boolean? | 这些是整个系列的评价，而不只是这个商品的 |
| `cursor` | string? | |

### 没有评价的商品

它会以明确的零返回 `200`，因此你可以放心地显示"0 条评价"：

```json
{ "marketplace": "shopify", "productId": "shop.com/handle", "page": { "index": 1, "size": 0, "total": 0 }, "reviews": [] }
```

`page.total: 0` 只在商品确实没有任何评价时才会发出。如果有证据表明该商品*确实有*评价但取不到——报出了
条数或平均分，或者商品页上能看到评价——你会收到一个错误，而绝不是零。因此一个空的 `reviews` 数组只可能
意味着两件事之一，而 `page.total` 会告诉你是哪一件：

| `page.total` | 含义 |
|---|---|
| `0` | 该商品没有评价 |
| 其他任何值（数字，或缺失） | 你已经翻过了评价的末尾 |

返回零时不会有 `summary`：没有平均分可报。

`grouped` 是必须披露的事实，而不是细节。商家可以用整个系列的 9,000 条评价来回答某一种颜色的页面，而评价
本身不会有任何提示——在被合并的页面上计算"这个商品的评分"，得到的是整个系列的评分。

`review.incentivized: true` 表示平台将这条评价标注为获得回报（免费商品、样品、奖励）后撰写；没有该字段只表示
没有标注，绝不代表"确认未获回报"。`review.reply` 是商家的公开回复，形如 `{body, date?, author?}`。

当平台给出时，`review.variant` 会指明该评价是针对哪一个同系列商品写的。在被合并的页面里，`variant` 为空表示
*平台没有说明*，绝不表示"这条是针对你所请求的那个商品的"。

`page.total` 与 `summary.count` 统计的对象不同：一个商品可以报出 7,654 个评分，而它的评价列表只收录 990 条带文字的评价。
count 忠实于商品，total 忠实于列表。两者都不保证每条评价都能取到：各平台对可翻页的深度设有上限，`cursor` 结束时列表即结束。

## CategoryList

`categories` 返回的内容：一份扁平列表，每个节点都指明自己的父级。之所以扁平而非嵌套，是因为常见的请求是
"整棵分类树"，而嵌套会让它在传输上更大、也更难分页。重建树结构只是按 `parentId` 做一次 `groupBy`。

```json
{
  "marketplace": "aliexpress",
  "path": [ { "id": "200000345", "name": "Women's Clothing" } ],
  "categories": [
    { "id": "349", "name": "Skirts", "parentId": "200000345" },
    { "id": "201531101", "name": "Matching Sets", "parentId": "200000345" }
  ]
}
```

| 字段 | 类型 | 说明 |
|---|---|---|
| `marketplace` | string | |
| `path` | CategoryStep[] | 所请求类目的各级祖先，末级在最后。位于根时为空。 |
| `categories` | CategoryNode[] | |

### CategoryNode

| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | string | **你要回传的 id**——下钻时传给 `categories`，`browsable` 允许时传给 `browse` |
| `name` | string | |
| `parentId` | string? | 位于根时没有 |
| `hasChildren` | boolean? | 缺失表示**未知**，而不是"没有"——多数平台并不说明 |
| `browsable` | boolean? | 见下 |
| `productCount` | number? | 仅当平台给出时；绝不从页面上自行统计 |

与本结构的大多数字段不同，`id` 与 `name` **都是必填**。类目列表存在的意义就是被使用：没有 id 的条目无法
使用，没有名称的条目无法阅读。

### `browsable`——最值得仔细读的一个字段

**缺失表示可以**，这也是常态。`false` 标记的是真实存在、可以导航，但**背后没有商品列表页**的层级。

这里的每个平台都有这样的层级，而且并非个别情况：

| | 不可列出商品的是 |
|---|---|
| Amazon | 各个部门（它们是店铺 slug，不是 browse 节点） |
| Walmart | 部分部门——Electronics 会列出商品，Toys 则是没有商品网格的枢纽 |

遇到这种情况时，**用 `categories` 再下钻一层**，那里的 id 就可以列出商品了。

给 `categories` 传 **`browsableOnly=true`**，只会得到 `browse` 接受的 id——费用与何时返回 400，见
[平台概览](/docs/api-reference/marketplaces)。

### 你不必信任这个标志

`browsable` 只在已经确认的情况下才会被填上，因此一个无法列出商品的层级仍可能以缺失该字段的形式——也就是
乐观地——返回给你。在Walmart上这无法避免：Electronics（`3944`）与 Toys（`4171`）都是有子类目的部门，其中
一个会列出商品，另一个不会，而分类数据里没有任何东西能区分它们。

所以 **`browse` 会直接告诉你**。对一个没有商品列表页的类目发起请求，返回的是

```json
{ "success": false, "error": { "code": "CATEGORY_NOT_BROWSABLE", "message": "Walmart category 4171 is a navigation hub…" } }
```

而不是一个空列表——因为售罄的类目返回的同样是空列表，你将无从分辨"请下钻到子类目"与"这里没有在售商品"。
请把这个代码当作一条指令：用同一个 id 调用 `categories`，再浏览它的某个子类目。它是 400，而不是请求失败：
重试不会改变答案。

## ShippingQuote

[`shipping`](/docs/api-reference/aliexpress/shipping) 返回的内容：一件商品发往一个地区的全部配送方式，最便宜的在前。

```json
{
  "marketplace": "aliexpress",
  "productId": "1005012143419303",
  "skuId": "12000057632572864",
  "region": "US",
  "quantity": 1,
  "available": true,
  "options": [
    { "carrier": "YunExpress Standard Shipping", "cost": { "currency": "USD", "amount": 3.13 },
      "free": false, "minDays": 7, "maxDays": 15, "guaranteedDays": 60,
      "estimate": "Oct. 30 - Nov. 07", "tracking": true, "shipsFrom": "CN" }
  ]
}
```

`options` 是 [DeliveryOption](#deliveryoption)。`available: false` 且没有选项，表示平台不向该地区发货——
这是一个答案，而不是错误。

## 错误

```json
{ "success": false, "error": { "code": "INVALID_PARAMS", "message": "…" } }
```

| 状态 | 代码 | 含义 |
|---|---|---|
| 400 | `INVALID_PARAMS` | 参数缺失、格式错误，或不是该平台接受的参数 |
| 400 | `CATEGORY_NOT_BROWSABLE` | 类目存在但没有商品列表页——用 `categories` 下钻并浏览某个子类目 |
| 404 | `NOT_FOUND` | 该 id 不存在 |
| 404 | `UNKNOWN_MARKETPLACE` / `UNKNOWN_OPERATION` | 请查看 `GET /v1/marketplaces` |
| 429 | 触发限流 | 附带 `Retry-After` |
| 501 | `UNSUPPORTED` | 数据存在，但本 API 无法提供——不是你的请求有问题 |
| 502 | 上游故障 | 平台不可用；请重试 |

501 有意不是 404："这些评价无法提供"绝不能被读成"这个商品没有评价"。
