---
title: "Filters"
description: "所有平台搜索共用的一套过滤器词汇——名称、单位，以及各平台分别接受哪些。"
---

搜索在各处都以同样的方式收窄。一个概念一个名称、一个单位；平台要么支持某个过滤器，要么指名拒绝它。

所有过滤器都是可选的，而且**过滤本身不额外收费**——只有Amazon的 `brands` 与 `facets` 例外，它们计 2 个
单位（见下）。

## 词汇

| 参数 | 类型 | 含义 |
|---|---|---|
| `priceMin` | number | 返回结果的最低价，以**十进制主单位**表示（`40`、`10.5`） |
| `priceMax` | number | 返回结果的最高价，单位相同 |
| `brands` | string list | 按平台自己的写法给出的品牌名：`brands=Nike,Adidas` |
| `ratingMin` | number | 最低平均星级，按平台的 0–5 分制 |
| `condition` | enum | `new` · `used` · `refurbished` |
| `freeShipping` | boolean | 只返回平台标注为包邮的商品 |
| `inStock` | boolean | 只返回平台声明当前可购买的商品 |
| `sort` | enum | 结果排序——见下 |
| `categoryId` | string | 平台自己的类目 id——见下 |
| `facets` | list | 取自结果自带筛选栏的收窄条件：`facets=Sock Rise:Ankle,Color:Black` |

`priceMin`/`priceMax` 与响应所报的单位一致。`Money.amount` 是十进制主单位，所以 `priceMin=40` 与返回的
`{"currency":"USD","amount":40}` 指的是同一件事。需要最小单位的平台会在内部转换，你永远看不到分。

列表在查询字符串里用逗号分隔，在 JSON 请求体里则是真正的数组。两者是同一个请求：

```
?brands=Nike,Adidas          {"brands": ["Nike", "Adidas"]}
```

布尔值接受 `true`/`false`/`1`/`0`。`freeShipping=false` 的确表示 false。

## 各平台接受哪些

| | amazon | shopify | walmart | aliexpress |
|---|:---:|:---:|:---:|:---:|
| `priceMin` / `priceMax` | ✅ | ✅ | ✅ | ✅ |
| `categoryId` | ✅ | ✅ | ✅ | ✅ |
| `sort` | ✅ | — | ✅ | ✅ |
| `brands` | ✅ *(计 2 个单位)* | — | ✅ | — |
| `ratingMin` | ⚠️ 仅 `4` | ✅ | ✅ | — |
| `condition` | — | ⚠️ 无 `refurbished` | — | — |
| `inStock` | — | ✅ | — | — |
| `facets` | ✅ *(计 2 个单位)* | — | ✅ | — |

`freeShipping` 属于共用词汇，但目前提供服务的平台都不接受它。

破折号表示会返回一个指名该参数的 400，而不是悄无声息地忽略它。

这些空缺属于平台，而不属于我们。某个过滤器缺席，是因为平台本身没有：AliExpress的搜索没有评分筛选；Shopify 没有
翻新这一档（所以 `condition=refurbished` 会被拒绝，而不是被并入 `used`——那是两个不同的断言），而且完全不
提供排序。

Amazon的评分筛选栏只有一个阈值"4 Stars & Up"，因此只接受 `ratingMin=4`，其余一律 400——而不是对
`ratingMin=3` 的请求悄悄返回 4 星以上的结果。

## `categoryId`

并不存在一套共用的类目体系，所以这个 id 是平台自己的：Amazon是 browse 节点（`679416011`），Walmart是
`cat_id`（`4171`），AliExpress是类目 id（`200000345`），Shopify 是 Standard Product Taxonomy id（`aa-1-13-8`）。

**这些 id 从哪来。** 问来就好：`categories` 会列出平台自有分类的某一层，而 `browse` 接受它返回的内容。
不带 `categoryId` 调用即可拿到顶层并逐层下钻，或者从某个 `Product.category[]` 里的 id 出发。

各端点页面点明了两点注意事项：同一个 id 未必在每个操作上都能用（Walmart的 `search` 接受纯 `cat_id`，而它的
`browse` 需要 `categories` 返回的下划线链），并且少数层级只用于导航，会被标为 `"browsable": false`。

在 `search` 上，它**收窄**关键词查询而不是取代它，`query` 仍然是必填。

若要**完全不带关键词**地列出一个类目，请改用 `browse` 操作：那里 `categoryId` 是必填，而 `query` 会被拒绝。
除 Shopify 外每个平台都有它，并且接受与 `search` 相同的过滤器。调用前值得知道它与 `search` 的两点不同：

- **id 可能是另一种东西。** Walmart的 `browse` 要的是下划线连接的链
  （`3944_133251_1095191_1230614_1230478`），而不是它 `search` 接受的纯 `cat_id`。
- **错误 id 不一定报错。** AliExpress返回空的 `items`——它不区分未知类目与空类目。

## facets——平台自己的筛选栏

上面那些类型化过滤器是所有平台的公约数，它们只是站点实际提供内容的一小部分：在Walmart搜一次袜子会渲染出 25
个筛选分组，其中只有四个能对应到类型化参数。其余二十一个——Sock Rise、Sleeve Length、Fabric Material、
Weather Suitability——都是这次查询特有的。换成搜笔记本电脑，它们就变成了 Screen Size 与 RAM。

因为筛选栏是按**查询**变化的，所以它靠发现而非声明。搜索响应会把它带上：

```json
{
  "items": [ "…" ],
  "facets": [
    { "id": "sock_rise", "name": "Sock Rise", "kind": "select",
      "values": [ { "value": "Ankle", "name": "Ankle", "count": 412 } ] },
    { "id": "brand", "name": "Brand", "kind": "select", "maps_to": "brands",
      "values": [ { "value": "Hanes", "name": "Hanes" } ] },
    { "id": "price", "name": "Price", "kind": "range", "maps_to": "priceMin", "min": 0, "max": 45 }
  ]
}
```

用 `facets` 把取值回传：

```bash
curl "https://gateway.apinoa.com/v1/walmart/search?query=socks&facets=sock_rise:Ankle,color:Black" \
  -H "x-api-key: $APINOA_API_KEY"
```

当存在对应关系时，`maps_to` 会指出覆盖同一概念的类型化过滤器。**优先使用类型化参数**——它在每个平台上含义
相同，而 `facets` 并非如此。

### 哪些平台有筛选栏

| | 发现 | 应用 | 备注 |
|---|:---:|:---:|---|
| **walmart** | ✅ | ✅ | 两边都免费 |
| **amazon** | ✅ `include=facets` | ✅ | 发现计 1 个单位，应用计 2 个——见下 |
| **shopify** | `[]` | — | Shopify 搜索没有筛选栏 |
| **aliexpress** | — | — | 没有筛选栏 |

`facets` **缺失**表示没有返回筛选栏；**为空**表示这次查询没有筛选栏。Shopify 返回 `[]`。

### Amazon：发现计 1 个单位，应用计 2 个

这是两件不同的事，只有其中一件额外收费。

**`include=facets` 计 1 个单位**——与不带它的搜索相同。

**`brands` 与 `facets` 计 2 个单位。** `cost.search` 是 `1..2`。Amazon的其余一切仍是 1 个单位。

筛选栏里没有的标签会得到一个指名它的 400，并附上筛选栏实际提供了哪些取值的示例。Amazon会截断自己的筛选栏，
因此一个匹配不上的标签是真正含糊的——返回未过滤的结果会掩盖这一点，什么都不返回则等于猜测。

## 排序

| 取值 | 含义 | amazon | walmart | aliexpress |
|---|---|:---:|:---:|:---:|
| `best_match` | 平台自己的相关度 | ✅ | ✅ | — |
| `price_asc` | 价格从低到高 | ✅ | ✅ | ✅ |
| `price_desc` | 价格从高到低 | ✅ | ✅ | ✅ |
| `newly_listed` | 最近上架优先 | ✅ | ✅ | — |
| `best_seller` | 按销量 | — | ✅ | ✅ |
| `avg_review` | 按平均评分 | ✅ | — | — |

这些名称是我们的，各平台自己的编码属于内部实现。平台做不到的取值会得到 400。

## 示例

```bash
# under $40, four stars and up
curl "https://gateway.apinoa.com/v1/amazon/search?query=running+shoes&priceMax=40&ratingMin=4" \
  -H "x-api-key: $APINOA_API_KEY"

# Nike only, rated 4+, cheapest first
curl "https://gateway.apinoa.com/v1/walmart/search?query=running+shoes&brands=Nike&ratingMin=4&sort=price_asc" \
  -H "x-api-key: $APINOA_API_KEY"

# in stock, $20-$60, rated 4+
curl "https://gateway.apinoa.com/v1/shopify/search?query=wool+socks&priceMin=20&priceMax=60&ratingMin=4&inStock=true" \
  -H "x-api-key: $APINOA_API_KEY"
```

## 一个需要知道的注意点

价格条件过滤的是**商品条目**，而有些条目带的是价格区间而不是单一价格。一个变体价格在 $28–$50 之间的商品，
会因为它 $50 的变体而满足 `priceMin=40` 的请求，但它的卡片报出的是"起价" $28。

因此少数返回行可能落在你所要求的区间之外一点。实测 90–95% 落在区间内。如果你需要严格边界，请自行对返回的
`price` 再过滤一遍——平台就是这样运作的，而我们选择如实报告它们返回的内容，而不是把行藏起来。
