Apinoa Docs

Response Schema

SearchResult, Product, ReviewPage, CategoryList and ShippingQuote — the normalized types every marketplace returns, field by field.

View raw .mdx

Five types cover every marketplace operation. A field means the same thing wherever it appears, and every concept has exactly one name — a delivery is the same shape on a search card, a product, an offer and a shipping quote.

Optional fields are genuinely optional: absent means the marketplace did not say, never a default. An absent price is a listing with no current offer, not a free one. An absent delivery.cost is shipping that has not been priced yet, not free shipping — that is delivery.free: true, a different claim.

Which optional fields a given marketplace actually fills varies a lot; each marketplace page has the measured rates.

SearchResult

{
  "marketplace": "amazon",
  "query": "running shoes",
  "page": { "index": 1, "size": 48, "total": 1200 },
  "items": [ /* SearchItem */ ],
  "cursor": "…"
}
FieldTypeNotes
marketplacestringwhich marketplace answered
querystring?absent on a visual search — the anchor was a picture
page.indexnumber1-based
page.sizenumberitems actually returned, not the size asked for
page.totalnumber?how many items the whole list holds, exactly as the marketplace states it; omitted for a rounded figure ("260,000+"). Not a promise that every one is reachable: paging ends when the next page does
itemsSearchItem[]
cursorstring?opaque; only marketplaces that paginate by cursor emit one
facetsFacet[]?the marketplace's own filter rail for this query — see below

Facet

The site's own filter rail, for this query. Absent means no rail was returned; [] means there is no rail for this query.

FieldTypeNotes
idstringthe group's key: brand, sock_rise
namestringits label: "Brand", "Sock Rise"
kindselect | rangeselect lists values; range states bounds
values[]FacetValue{value, name, count?, selected?}
min maxnumber?for a range; money ranges are in the same units as Money.amount
maps_tostring?the typed filter covering the same concept, e.g. brands

value is what you send back in the facets request parameter. It is the human-readable label rather than an internal id.

Where maps_to is present, prefer that typed parameter: it means the same thing on every marketplace.

SearchItem and Product

Both extend one item shape, so a field means the same in a result card and on a product page. Product adds what only a detail view has.

Shared by Both

FieldTypeNotes
marketplace id titlestringalways present
urlstring?the listing's own page
priceMoney?what a buyer pays today
listPriceMoney?the crossed-out reference it is discounted from
conditionstring?as worded: "Pre-Owned", "Used: Very Good", "New"
conditionNotestring?the seller's own note about that condition
unitPriceobject?{price, unit} — "$1.00 / count"
brandstring?lifted out of the specification table; a "Brand" row is not repeated in attributes
identifiersobject?{gtin?, mpn?, model?} — barcode (UPC/EAN/ISBN), part/model number, model name
ratingRating?{average, count?, distribution?, grouped?}
sellerSeller?{id?, name?, url?, rating?, country?, detailedRatings?, positivePercent?} — see below
salesCountnumber?normalized units sold; a floor, since marketplaces round
salesLabelstring?the blurb verbatim: "3,000+ sold"
inStockboolean?
stocknumber?units available, where the marketplace states a number
statusstring?the availability line as worded: "Only 3 left in stock"
deliveryDeliveryOption?the default way it ships — see below
shippingOptionsDeliveryOption[]?every option shown or quoted, the default first
returnsPolicystring?
badgesstring[]merchandising badges: ["Amazon's Choice"]
promotionsstring[]savings: ["5% off coupon applied"]
paymentOptionsstring[]?ways to pay: ["Pay in 4 payments at 0% interest"]
listingFormatstring?how it sells: "Buy It Now", "Auction"
groupIdstring?the variation family this belongs to
attributesAttribute[]{name, value} pairs — the specification table, minus rows lifted into typed fields
offersSummaryobject?{count?, fromPrice?, url?} — the roll-up, not the list
priceRangeobject?{min, max} in Money — the span across the variants, as the marketplace states it ("$12.99 – $24.99"); never computed from a partial variant list
endsAtstring?when the current offer ends: an auction's close or a time-limited deal's end. ISO 8601
purchaseLimitnumber?most units one buyer may order of this item ("Limit 2 per customer")
listedAtstring?when the listing was first published on the marketplace (not when we fetched it). ISO 8601 date or time
deliveryToobject?{label?, country?, postalCode?} — the place the delivery and shippingOptions promises were quoted for
extobject?a marketplace-only fact no shared field holds — see below

Money is {currency, amount} with amount in decimal major units.

ext is a last resort: a fact goes into a shared typed field whenever one means the same thing, and only a fact no other marketplace has rides in ext, under that marketplace's own name. Items, variants, sellers, offers, reviews, review pages and category nodes can carry one.

DeliveryOption

One shape for every delivery — delivery, each of shippingOptions, an offer's delivery, and each option of a ShippingQuote.

FieldTypeNotes
estimatestring?the arrival estimate as worded: "FREE delivery Wednesday, October 28"
costMoney?the shipping fee
freeboolean?the marketplace says shipping is free
minDays maxDaysnumber?transit window in days, where stated as numbers
handlingDaysnumber?days before the seller dispatches
guaranteedDaysnumber?the marketplace's own delivery guarantee
cutoffstring?order-by countdown for this option: "Order within 3 hrs 22 mins"
conditionstring?who or what qualifies: "on orders shipped by Amazon over $35"
availableboolean?false — the marketplace does not ship to the region asked
membershipstring?the membership this option needs, e.g. "Prime"; absent ⇒ every buyer
minimumOrderMoney?the order total its cost/free needs — Amazon's "on $35 of items"
carrier codestring?the service, as the marketplace names it
trackingboolean?
shipsFromstring?country the parcel leaves from

An absent cost is unstated, not free; free is free: true. available: false is an answer, never a zero cost. delivery is always the option that applies to every buyer when the marketplace shows one — a member-only free (Prime) is in shippingOptions with its membership, never presented as everyone's.

Seller

FieldTypeNotes
id name urlstring?
ratingRating?the seller's feedback score, in the same shape as a product's rating
countrystring?where the seller is based
detailedRatingsobject?{itemAsDescribed?, communication?, shippingSpeed?} on the marketplace's 0–5 scale
positivePercentnumber?the share of the seller's feedback that is positive, as a percent from 0 to 100 ("99.8% positive"); not a star score

rating.grouped: true means the aggregate covers the whole variation family rather than the one variant asked for. Its absence means not known to be grouped, which is weaker than "known to be per-variant".

SearchItem Only

FieldTypeNotes
imageUrlstring?the card's primary image
sponsoredboolean?a paid placement rather than an organic result
variantCountnumber?how many combinations the card says the item comes in ("+12 colors"); product lists them in variants
categoryCategoryStep[]?where the result sits in the taxonomy, leaf last; a card usually carries only the leaf
matchScorenumber?image search only: how closely the result matches your picture, 0–1 (1 = the marketplace's best match); comparable within one result set

Product Only

FieldTypeNotes
descriptionstring?may be HTML
featuresstring[]the bulleted selling points, in page order
mediaobject{images: string[], videos: []}
categoryCategoryStep[]full path, leaf last; a step has an id, a name, or both
optionsOption[]the axes: Colour, Size
variantsVariant[]the combinations — see below
offersOffer[]competing sellers, where the marketplace enumerates them
subscriptionPriceMoney?Subscribe & Save and equivalents
reviewSummarystring?the marketplace's own generated digest
reviewAspectsarraylabelled sentiment: {name, mentions?, positive?, negative?}
boxContentsstring[]"what's in the box"
brandUrl fulfilledBy replacedBystring?
packageobject?the shipped parcel: {lengthCm?, widthCm?, heightCm?, weightKg?, soldInLot?}
wholesaleboolean?the listing offers a bulk price (see variants[].bulkPrice)
reviewsReview[]top reviews carried by the product page itself
includedstring[]?which requested expansions were actually fetched

Variants, and the One Thing to Check

Two variation models exist in the wild, and one field tells them apart:

  • variant.productId present — the combination is its own fetchable product. Pass that id back to product. Amazon works this way.
  • variant.productId absent — the combination only exists inside this product; you already have everything. Shopify, Walmart and AliExpress work this way.

You never need to know which marketplace you are talking to. Check the field.

Variant fieldTypeNotes
idstringthe marketplace's id for the combination
productIdstring?present ⇒ fetchable with product (above)
skustring?the merchant's own stock-keeping code
price listPriceMoney?
bulkPriceMoney?per-unit price when buying in bulk, only where it differs from price
purchaseLimitnumber?most units one buyer may order
taxIncludedboolean?whether price includes tax
inStockboolean?
stocknumber?units available of this variant
identifiersobject?{gtin?, mpn?, model?} — this combination's own barcode and part number, where published per variant
weightKgnumber?this combination's weight in kilograms
selectionsarray{optionId, valueId, optionName, valueName} — which option values it is

Offer

A competing seller, where the marketplace enumerates them: {seller?, price, listPrice?, condition?, conditionNote?, fulfilledBy?, inStock?, status?, delivery?}. delivery is a DeliveryOption, because a cheaper offer that ships later is a different offer.

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
}
FieldTypeNotes
summaryRating?describes whatever set reviews was drawn from
page.totalnumber?how many reviews the list holds, as the marketplace states it — can be fewer than summary.count, which counts every rating (Walmart lists only reviews with text). 0 means the product has no reviews
reviews[]Review{id?, author?, rating, title?, body?, date?, country?, verified?, helpfulCount?, variant?, images[], incentivized?, reply?}
groupedboolean?these are the whole family's reviews, not only this product's
cursorstring?

A Product with No Reviews

It answers 200 with an explicit zero, so you can show "0 reviews" with confidence:

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

page.total: 0 is only ever sent when the product genuinely has none. If there is evidence that the product has reviews that are not available — a count or an average is reported, or the product page shows them — you get an error instead, never a zero. So an empty reviews array tells you one of two things, and page.total says which:

page.totalMeans
0this product has no reviews
anything else (a number, or absent)you have paged past the end of the reviews

There is no summary on a zero answer: there is no average to report.

grouped is a required disclosure rather than a detail. A merchant can answer one colour's page with the family's 9,000 reviews and nothing in the reviews themselves says so — computing "this product's rating" from a grouped page gives you the family's.

review.incentivized: true marks a review written in exchange for something (a free product, a sample, a reward), as the marketplace labels it; absent means not marked, never "known to be unpaid". review.reply is the merchant's public response: {body, date?, author?}.

review.variant names the sibling a review was written for, when the marketplace says. Inside a grouped page an empty variant means the marketplace would not say, never "this one is for the product you asked for".

page.total and summary.count count different things: a product can report 7,654 ratings while its review list holds 990 written reviews, so the count stays true to the product and the total to the list. Neither promises that every review is reachable: marketplaces cap how far a list pages, and the list ends when the cursor does.

CategoryList

What categories returns: one flat list, every node naming its parent. Flat rather than nested because the common request is "the whole taxonomy", and nesting makes that both larger on the wire and harder to page. Rebuilding the tree is a groupBy on parentId.

{
  "marketplace": "aliexpress",
  "path": [ { "id": "200000345", "name": "Women's Clothing" } ],
  "categories": [
    { "id": "349", "name": "Skirts", "parentId": "200000345" },
    { "id": "201531101", "name": "Matching Sets", "parentId": "200000345" }
  ]
}
FieldTypeNotes
marketplacestring
pathCategoryStep[]Ancestors of the category asked for, leaf last. Empty at the root.
categoriesCategoryNode[]

CategoryNode

FieldTypeNotes
idstringThe id you pass back — to categories to descend, and to browse when browsable allows
namestring
parentIdstring?Absent at a root
hasChildrenboolean?Absent means unknown, not "no" — most marketplaces do not say
browsableboolean?See below
productCountnumber?Only when the marketplace states one; never counted from a page

Unlike most of this schema, id and name are both required. A category listing exists to be acted on: an entry without an id is unusable and one without a name unreadable.

browsable — the One Field Worth Reading Carefully

Absent means yes, which is the normal case. false marks a level that is real and navigable but has no listing page behind it.

Every marketplace here has such levels, and they are not an edge case:

What is not browsable
Amazondepartments (they are store slugs, not browse nodes)
Walmartsome departments — Electronics lists products, Toys is a hub with no grid

When you meet one, descend one more level with categories and the ids there will browse.

Pass browsableOnly=true to categories to get only the ids browse accepts — see the marketplaces overview for what it costs and when it 400s.

You Do Not Have to Trust the Flag

browsable is populated only where it has been established, so a level that cannot be browsed can still reach you with the field absent — that is, optimistically. On Walmart this is unavoidable: Electronics (3944) and Toys (4171) are both departments with children, and one lists products while the other does not, with nothing in the taxonomy to tell them apart.

So browse tells you outright. Asking it for a category that has no listing page returns

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

rather than an empty list — because an empty list is also what a sold-out category returns, and you would have no way to tell "descend to the children" from "nothing for sale here". Treat the code as the instruction to call categories with that same id and browse one of its children. It is a 400, not a failure of the request: retrying will not change the answer.

ShippingQuote

What shipping returns: every way one item ships to one region, cheapest first.

{
  "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 are DeliveryOptions. available: false with no options means the marketplace does not ship there — an answer, not an error.

Errors

{ "success": false, "error": { "code": "INVALID_PARAMS", "message": "…" } }
StatusCodeMeans
400INVALID_PARAMSa parameter is missing, malformed, or not one this marketplace accepts
400CATEGORY_NOT_BROWSABLEthe category exists but has no listing page — descend with categories and browse a child
404NOT_FOUNDthat id does not exist
404UNKNOWN_MARKETPLACE / UNKNOWN_OPERATIONcheck GET /v1/marketplaces
429rate limitedcarries Retry-After
501UNSUPPORTEDthe data exists but is not available through this API — not a problem with your request
502upstreamthe marketplace was unavailable; retry

A 501 is never a 404 on purpose: "these reviews are not available" must not read as "this product has no reviews".

On this page