Apinoa Docs

Response Schema

SearchResult、Product、ReviewPage、CategoryList 与 ShippingQuote——所有平台统一返回的归一化类型,逐字段说明。

查看原始 .mdx

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

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

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

SearchResult

{
  "marketplace": "amazon",
  "query": "running shoes",
  "page": { "index": 1, "size": 48, "total": 1200 },
  "items": [ /* SearchItem */ ],
  "cursor": "…"
}
字段类型说明
marketplacestring由哪个平台作答
querystring?以图搜图时没有——依据的是一张图片
page.indexnumber从 1 开始
page.sizenumber实际返回的条数,而非请求的条数
page.totalnumber?整个列表的条目总数,与平台给出的确切数值一致;若平台只给出约数("260,000+")则省略。它不保证每一条都能取到:没有下一页时翻页即结束
itemsSearchItem[]
cursorstring?不透明值;只有按游标翻页的平台才会给出
facetsFacet[]?平台针对本次查询给出的筛选栏——见下

Facet

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

字段类型说明
idstring该分组的键:brand、sock_rise
namestring它的标签:"Brand"、"Sock Rise"
kindselect | rangeselect 列出取值;range 给出上下界
values[]FacetValue{value, name, count?, selected?}
min maxnumber?用于 range;金额区间的单位与 Money.amount 相同
maps_tostring?覆盖同一概念的类型化过滤器,例如 brands

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

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

SearchItem 与 Product

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

两者共有

字段类型说明
marketplace id titlestring始终存在
urlstring?该商品自身的页面
priceMoney?买家今天支付的金额
listPriceMoney?被划掉的参考价,折扣由此计算
conditionstring?按原文表述:"Pre-Owned"、"Used: Very Good"、"New"
conditionNotestring?卖家对该成色的自述
unitPriceobject?{price, unit}——"$1.00 / count"
brandstring?从规格表中提取;"Brand" 行不会在 attributes 中重复出现
identifiersobject?{gtin?, mpn?, model?}——条码(UPC/EAN/ISBN)、零件/型号编号、型号名称
ratingRating?{average, count?, distribution?, grouped?}
sellerSeller?{id?, name?, url?, rating?, country?, detailedRatings?, positivePercent?}——见下
salesCountnumber?归一化后的销量;由于平台会取整,这是一个下限
salesLabelstring?原样的销量文案:"3,000+ sold"
inStockboolean?
stocknumber?可售数量,平台给出数字时才有
statusstring?按原文表述的库存说明:"Only 3 left in stock"
deliveryDeliveryOption?默认的配送方式——见下
shippingOptionsDeliveryOption[]?展示或报价的全部配送选项,默认选项在前
returnsPolicystring?
badgesstring[]营销徽章:["Amazon's Choice"]
promotionsstring[]优惠:["5% off coupon applied"]
paymentOptionsstring[]?付款方式:["Pay in 4 payments at 0% interest"]
listingFormatstring?销售方式:"Buy It Now"、"Auction"
groupIdstring?它所属的变体系列
attributesAttribute[]{name, value} 键值对——规格表,去掉已提取到类型化字段的行
offersSummaryobject?{count?, fromPrice?, url?}——汇总,而不是列表
priceRangeobject?以 Money 表示的 {min, max}——平台给出的各变体价格区间("$12.99 – $24.99"),绝不根据部分变体推算
endsAtstring?当前报价的截止时间:拍卖结束或限时优惠结束。ISO 8601
purchaseLimitnumber?每位买家最多可购买的数量("每人限购 2 件")
listedAtstring?商品首次在平台上架的时间(不是抓取时间)。ISO 8601 日期或时间
deliveryToobject?{label?, country?, postalCode?}——delivery 和 shippingOptions 的配送承诺所针对的收货地
extobject?共享字段无法容纳、仅该平台才有的信息——见下

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

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

DeliveryOption

所有配送信息都是这一种形状——delivery、shippingOptions 的每一项、报价的 delivery,以及 ShippingQuote 的每个选项。

字段类型说明
estimatestring?按原文表述的送达预估:"FREE delivery Wednesday, October 28"
costMoney?运费
freeboolean?平台声明包邮
minDays maxDaysnumber?以数字给出时的运输天数区间
handlingDaysnumber?卖家发货前所需的天数
guaranteedDaysnumber?平台自己的送达保证
cutoffstring?该选项的下单倒计时:"Order within 3 hrs 22 mins"
conditionstring?适用条件:"on orders shipped by Amazon over $35"
availableboolean?false——平台不向所请求的地区发货
membershipstring?此选项所需的会员资格,例如 "Prime";缺省 ⇒ 适用于所有买家
minimumOrderMoney?cost/free 生效所需的订单金额——如Amazon的 "on $35 of items"
carrier codestring?平台所称的配送服务
trackingboolean?
shipsFromstring?包裹发出的国家

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

Seller

字段类型说明
id name urlstring?
ratingRating?卖家的评分,与商品的 rating 形状相同
countrystring?卖家所在地
detailedRatingsobject?{itemAsDescribed?, communication?, shippingSpeed?},采用平台自己的 0–5 分制
positivePercentnumber?卖家反馈中好评所占的比例,以 0–100 的百分数表示("99.8% positive"),不是星级评分

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

仅 SearchItem

字段类型说明
imageUrlstring?卡片的主图
sponsoredboolean?付费展示位,而非自然结果
variantCountnumber?卡片上标明的组合数量("+12 colors");product 会在 variants 中逐一列出
categoryCategoryStep[]?该结果在类目体系中的位置,叶子类目在最后;卡片通常只带叶子类目
matchScorenumber?仅限以图搜索:与所传图片的相似程度,取值 0–1(1 = 平台认定的最佳匹配),只能在同一组结果内比较

仅 Product

字段类型说明
descriptionstring?可能是 HTML
featuresstring[]页面顺序排列的要点
mediaobject{images: string[], videos: []}
categoryCategoryStep[]完整路径,末级在最后;每一级有 id、name,或两者
optionsOption[]各个维度:颜色、尺码
variantsVariant[]各种组合——见下
offersOffer[]平台会逐条列出时的竞争卖家
subscriptionPriceMoney?Subscribe & Save 及同类
reviewSummarystring?平台自己生成的评价摘要
reviewAspectsarray带标签的情感分析:{name, mentions?, positive?, negative?}
boxContentsstring[]包装清单
brandUrl fulfilledBy replacedBystring?
packageobject?寄出的包裹:{lengthCm?, widthCm?, heightCm?, weightKg?, soldInLot?}
wholesaleboolean?该商品提供批发价(见 variants[].bulkPrice)
reviewsReview[]商品页自身携带的热门评价
includedstring[]?所请求的扩展中实际取到的部分

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

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

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

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

变体字段类型说明
idstring平台对该组合的 id
productIdstring?存在 ⇒ 可以用 product 获取(见上)
skustring?商家自己的库存编码
price listPriceMoney?
bulkPriceMoney?批量购买时的单价,仅在与 price 不同时给出
purchaseLimitnumber?单个买家最多可购买的数量
taxIncludedboolean?price 是否含税
inStockboolean?
stocknumber?该变体的可售数量
identifiersobject?{gtin?, mpn?, model?}——平台按变体公布时,该组合自己的条码和零件号
weightKgnumber?该组合的重量(千克)
selectionsarray{optionId, valueId, optionName, valueName}——它对应哪些选项取值

Offer

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

ReviewPage

{
  "marketplace": "walmart",
  "productId": "996425081",
  "summary": { "average": 4.6, "count": 1204, "distribution": { "5": 78 } },
  "page": { "index": 1, "size": 10, "total": 990 },
  "reviews": [ /* Review */ ],
  "grouped": true
}
字段类型说明
summaryRating?描述 reviews 所取自的那个集合
page.totalnumber?平台给出的列表中的评价条数,可能少于统计全部评分的 summary.count(Walmart 只列出带文字的评价)。0 表示该商品没有评价
reviews[]Review{id?, author?, rating, title?, body?, date?, country?, verified?, helpfulCount?, variant?, images[], incentivized?, reply?}
groupedboolean?这些是整个系列的评价,而不只是这个商品的
cursorstring?

没有评价的商品

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

{ "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。

{
  "marketplace": "aliexpress",
  "path": [ { "id": "200000345", "name": "Women's Clothing" } ],
  "categories": [
    { "id": "349", "name": "Skirts", "parentId": "200000345" },
    { "id": "201531101", "name": "Matching Sets", "parentId": "200000345" }
  ]
}
字段类型说明
marketplacestring
pathCategoryStep[]所请求类目的各级祖先,末级在最后。位于根时为空。
categoriesCategoryNode[]

CategoryNode

字段类型说明
idstring你要回传的 id——下钻时传给 categories,browsable 允许时传给 browse
namestring
parentIdstring?位于根时没有
hasChildrenboolean?缺失表示未知,而不是"没有"——多数平台并不说明
browsableboolean?见下
productCountnumber?仅当平台给出时;绝不从页面上自行统计

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

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

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

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

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

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

给 categories 传 browsableOnly=true,只会得到 browse 接受的 id——费用与何时返回 400,见 平台概览。

你不必信任这个标志

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

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

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

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

ShippingQuote

shipping 返回的内容:一件商品发往一个地区的全部配送方式,最便宜的在前。

{
  "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。available: false 且没有选项,表示平台不向该地区发货—— 这是一个答案,而不是错误。

错误

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

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

本页内容