# Apinoa Marketplace API > The Marketplace API: search results, products, reviews and categories from Amazon, Shopify stores, Walmart, TikTok Shop and AliExpress, as normalized JSON. Every page of its documentation, in one file. Requests go to https://gateway.apinoa.com with the API key in an `x-api-key` header. A JSON answer is `{"success": true, "data": ...}`; an error is `{"success": false, "error": {"code", "message"}, "requestId"}`, and a request that fails is not billed. More: [Getting Started](https://apinoa.com/en/docs-raw/getting-started), [Authentication](https://apinoa.com/en/docs-raw/authentication), [Errors](https://apinoa.com/en/docs-raw/errors), [SDKs and MCP](https://apinoa.com/en/docs-raw/sdks); every operation as OpenAPI 3.1: https://apinoa.com/openapi.json. The other API families: https://apinoa.com/llms.txt. ## Contents - [Marketplace API](https://apinoa.com/en/docs-raw/api-reference/marketplaces): Amazon, Shopify, Walmart, TikTok Shop and AliExpress through one set of operations and one response schema. - [Response Schema](https://apinoa.com/en/docs-raw/api-reference/marketplaces/response-schema): SearchResult, Product, ReviewPage, CategoryList and ShippingQuote — the normalized types every marketplace returns, field by field. - [Filters](https://apinoa.com/en/docs-raw/api-reference/marketplaces/filters): The filter vocabulary shared by every marketplace search — names, units, and which marketplace accepts which. - [Amazon](https://apinoa.com/en/docs-raw/api-reference/amazon): Amazon on sixteen marketplaces, through the shared marketplace schema. Pick a marketplace with `region`; prices come back in its own currency. - [Search](https://apinoa.com/en/docs-raw/api-reference/amazon/search): Search Amazon by keyword, with price, rating, brand and category filters, on any of sixteen Amazon marketplaces. - [Image Search](https://apinoa.com/en/docs-raw/api-reference/amazon/image-search): Find Amazon products from a photo. Each match is the same item a keyword search returns. - [Browse](https://apinoa.com/en/docs-raw/api-reference/amazon/browse): List the products in one Amazon category, with no keyword. - [Categories](https://apinoa.com/en/docs-raw/api-reference/amazon/categories): List one level of Amazon's category tree: the departments, or the children of one category. - [Product](https://apinoa.com/en/docs-raw/api-reference/amazon/product): Get one Amazon product by ASIN: price, variants, images, specifications, delivery and the competing-offer summary. - [Shopify](https://apinoa.com/en/docs-raw/api-reference/shopify): Shopify stores through the shared marketplace schema: search across stores, list one store's catalog, and read a product and its reviews. - [Search](https://apinoa.com/en/docs-raw/api-reference/shopify/search): Search products across Shopify stores by keyword, with price, rating, condition and stock filters. - [Image Search](https://apinoa.com/en/docs-raw/api-reference/shopify/image-search): Find products across Shopify stores from a photo, optionally narrowed by keyword. - [Catalog](https://apinoa.com/en/docs-raw/api-reference/shopify/catalog): List every product of one Shopify store, or of one of its collections. - [Product](https://apinoa.com/en/docs-raw/api-reference/shopify/product): Get one Shopify product from its store: price, every variant, options, images and description. - [Reviews](https://apinoa.com/en/docs-raw/api-reference/shopify/reviews): Get a Shopify product's reviews from the review app its store uses. - [Walmart](https://apinoa.com/en/docs-raw/api-reference/walmart): Walmart US through the shared marketplace schema. Prices are in USD. - [Search](https://apinoa.com/en/docs-raw/api-reference/walmart/search): Search Walmart by keyword, with price, rating, brand and category filters. - [Browse](https://apinoa.com/en/docs-raw/api-reference/walmart/browse): List the products in one Walmart category, with no keyword. - [Categories](https://apinoa.com/en/docs-raw/api-reference/walmart/categories): List one level of Walmart's category tree, and turn a category id into one Browse accepts. - [Product](https://apinoa.com/en/docs-raw/api-reference/walmart/product): Get one Walmart product: price, variants, images, specifications, seller and delivery. - [Reviews](https://apinoa.com/en/docs-raw/api-reference/walmart/reviews): Get a Walmart product's reviews, one page at a time. - [TikTok Shop](https://apinoa.com/en/docs-raw/api-reference/tiktokshop): TikTok Shop US through the shared marketplace schema: search, categories, a store's whole catalog, and a product with its first reviews. Prices are in USD. - [Search](https://apinoa.com/en/docs-raw/api-reference/tiktokshop/search): Search TikTok Shop US by keyword. - [Browse](https://apinoa.com/en/docs-raw/api-reference/tiktokshop/browse): List the products in one TikTok Shop category, with no keyword. - [Categories](https://apinoa.com/en/docs-raw/api-reference/tiktokshop/categories): List TikTok Shop's categories: the departments with their subcategories, or the children of one category. - [Catalog](https://apinoa.com/en/docs-raw/api-reference/tiktokshop/catalog): List one TikTok Shop store's products, 30 at a time. - [Product](https://apinoa.com/en/docs-raw/api-reference/tiktokshop/product): Get one TikTok Shop product: price, variants with stock, images and video, shipping and seller. - [Reviews](https://apinoa.com/en/docs-raw/api-reference/tiktokshop/reviews): Get a TikTok Shop product's first five reviews and its star breakdown. - [AliExpress](https://apinoa.com/en/docs-raw/api-reference/aliexpress): AliExpress through the shared marketplace schema: the same `SearchResult` and `Product` every marketplace returns, plus shipping quotes. - [Search](https://apinoa.com/en/docs-raw/api-reference/aliexpress/search): Search AliExpress by keyword, with price and category filters, in the currency and language you choose. - [Image Search](https://apinoa.com/en/docs-raw/api-reference/aliexpress/image-search): Find AliExpress products from a photo. - [Browse](https://apinoa.com/en/docs-raw/api-reference/aliexpress/browse): List the products in one AliExpress category, with no keyword. - [Categories](https://apinoa.com/en/docs-raw/api-reference/aliexpress/categories): List AliExpress's category tree: the top level, or the children of one category. - [Product](https://apinoa.com/en/docs-raw/api-reference/aliexpress/product): Get one AliExpress product: price, every SKU with its stock, images, specifications, seller and, on request, shipping. - [Shipping](https://apinoa.com/en/docs-raw/api-reference/aliexpress/shipping): Quote what shipping one AliExpress item to a country costs, by every carrier, and how long it takes. --- # Marketplace API > Amazon, Shopify, Walmart, TikTok Shop and AliExpress through one set of operations and one response schema. Source: https://apinoa.com/en/docs-raw/api-reference/marketplaces The Marketplace API serves four marketplaces through **the same operations and one response schema**. A product from Amazon and a product from Shopify arrive as the same `Product`, so the code that reads one reads all of them. The marketplace is a segment of the path: ```bash curl "https://gateway.apinoa.com/v1/amazon/search?query=wool+socks" -H "x-api-key: $APINOA_API_KEY" curl "https://gateway.apinoa.com/v1/walmart/search?query=wool+socks" -H "x-api-key: $APINOA_API_KEY" ``` ## Operations | Operation | Returns | What It Does | |---|---|---| | `search` | `SearchResult` | Products matching a keyword, with filters | | `image-search` | `SearchResult` | Products matching a photo | | `browse` | `SearchResult` | The products in one category, with no keyword | | `categories` | `CategoryList` | One level of the category tree, to find ids `browse` accepts | | `catalog` | `SearchResult` | Every product of one store | | `product` | `Product` | One product in full: variants, images, specifications, seller, delivery | | `reviews` | `ReviewPage` | A product's reviews, one page at a time | | `shipping` | `ShippingQuote` | What shipping one item to a country costs, by carrier | Every type is described field by field in the [Response Schema](/docs/api-reference/marketplaces/response-schema). ## Which Marketplace Has Which | | search | image-search | browse | categories | catalog | product | reviews | shipping | |---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:| | [Amazon](/docs/api-reference/amazon) | ✓ | ✓ | ✓ | ✓ | | ✓ | | | | [Shopify](/docs/api-reference/shopify) | ✓ | ✓ | | | ✓ | ✓ | ✓ | | | [Walmart](/docs/api-reference/walmart) | ✓ | | ✓ | ✓ | | ✓ | ✓ | | | [TikTok Shop](/docs/api-reference/tiktokshop) | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | | | [AliExpress](/docs/api-reference/aliexpress) | ✓ | ✓ | ✓ | ✓ | | ✓ | | ✓ | An empty cell means the marketplace has no surface for that operation, not that it is planned. Amazon reviews are not available. Shopify has no `browse` or `categories` because it is millions of independent stores with no shared category tree; list one store with `catalog` instead. eBay is **temporarily unavailable**. Its endpoints answer `404 UNKNOWN_MARKETPLACE` until it returns. ## What Is the Same, and What Differs | | Identical Everywhere | Differs per Marketplace | |---|---|---| | Response **types** | ✓ `SearchResult`, `Product`, `ReviewPage` | | | Field **names and meanings** | ✓ `price` is always what a buyer pays today | | | Filter **names and units** | ✓ `priceMin` is always a decimal amount in major units | | | Which **fields are filled** | | Only Amazon and Walmart return `reviewSummary`; only AliExpress states `stock` as a number | | Which **operations** exist | | See the table above | | Which **filters** are accepted | | Amazon charges 2 units for `brands`; AliExpress takes no `ratingMin` | | **Paging** | | Shopify pages by `cursor`; the others by `page` | | **Product id** format | | An ASIN, a numeric id, or `shop/handle` | Write your parser once, but read the marketplace's page before you write the request: each lists exactly what it accepts and how often each optional field actually arrives. The shared filter vocabulary is in [Filters](/docs/api-reference/marketplaces/filters). ## Categories and Browsing `browse` lists a category with no keyword; `categories` tells you which ids exist. Call `categories` with no `categoryId` for the top level, and pass an id to list its children. What differs is the kind of id: Amazon's departments are store slugs, Walmart's are numeric ids that browse as underscore-joined chains, and AliExpress's are ordinary category ids. Not every category lists products: many are navigation levels. `categories?browsableOnly=true` returns only ids `browse` accepts. Where it is not known whether a category browses, `browsable` is absent rather than `false`. A marketplace without that data answers `400 BROWSABLE_ONLY_UNAVAILABLE`. ## Finding Out at Runtime `GET /v1/marketplaces` publishes the whole catalog: every platform, its operations, what each costs, and the **JSON Schema of each operation's input**. Generate against it rather than hard-coding these pages. Shortened: ```json { "success": true, "data": { "marketplaces": [ { "id": "walmart", "kind": "marketplace", "operations": ["search", "browse", "categories", "product", "reviews"], "regions": ["US"], "cost": { "search": { "base": 1, "max": 1 }, "product": { "base": 1, "max": 1 } }, "params": { "search": { "type": "object", "required": ["query"], "properties": { "query": { "type": "string", "minLength": 1, "description": "Keywords to search for. …" } } } } } ], "pricing": { "currency": "USD", "unitPrice": { "marketplace": "0.001000", "image-translate": "0.003000" }, "dayBoundary": "UTC", "dailyBrackets": [{ "fromUnits": 0, "ratePct": 100 }, { "fromUnits": 1000, "ratePct": 90 }] } } } ``` Operation names are written exactly as in the URL (`image-search`), so each drops straight into `/v1/{platform}/{operation}`. `kind` is `marketplace` or `sns`. ## Pricing **$0.001 per unit** at list price, stepping down as the day's volume grows (see [Pricing](/pricing)). Most calls are one unit. These can cost more, each up to a ceiling: | Operation | Price | What Adds to It | |---|---|---| | Amazon `search`, `browse` | $0.001, up to $0.002 | `brands` or `facets` make it 2 units | | Amazon `image-search` | $0.001, up to $0.002 | 2 units when the picture matches; 1 when it matches nothing | | Amazon `product` | $0.002, up to $0.032 | Always 2 units: the product page and its lower half (specs, Best Sellers Rank, rating breakdown). `include=offers`: one more unit per page of ten offers, up to 30 pages | | Shopify `reviews` | $0.001, up to $0.007 | A first page can take extra units; a `cursor` page does not | | AliExpress `product` | $0.001, up to $0.002 | `include=shipping` makes it 2 units | | AliExpress `shipping` | $0.001, up to $0.002 | 2 units unless `sku` is a numeric SKU id | The catalog states the same figures in units: `cost.base` is what a call charges and `cost.max` the most it can, and one unit is `pricing.unitPrice`. A call that fails is not billed. ## Reference - [Response Schema](/docs/api-reference/marketplaces/response-schema): every field of every type - [Filters](/docs/api-reference/marketplaces/filters): the filter vocabulary and which marketplace accepts what - [Errors](/docs/errors): every error code --- # Response Schema > SearchResult, Product, ReviewPage, CategoryList and ShippingQuote — the normalized types every marketplace returns, field by field. Source: https://apinoa.com/en/docs-raw/api-reference/marketplaces/response-schema 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 ```json { "marketplace": "amazon", "query": "running shoes", "page": { "index": 1, "size": 48, "total": 1200 }, "items": [ /* SearchItem */ ], "cursor": "…" } ``` | Field | Type | Notes | |---|---|---| | `marketplace` | string | which marketplace answered | | `query` | string? | absent on a visual search — the anchor was a picture | | `page.index` | number | 1-based | | `page.size` | number | items actually returned, not the size asked for | | `page.total` | number? | 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 | | `items` | SearchItem[] | | | `cursor` | string? | opaque; only marketplaces that paginate by cursor emit one | | `facets` | Facet[]? | the marketplace's own filter rail for this query — see below | | `relatedSearches` | string[]? | the marketplace's related or "refine your search" queries for this query, in page order | | `ext` | object? | a marketplace-only fact about the results page itself (a spelling correction, a buyer's guide) | ### Facet The site's own filter rail, for this query. Absent means no rail was returned; `[]` means there is no rail for this query. | Field | Type | Notes | |---|---|---| | `id` | string | the group's key: `brand`, `sock_rise` | | `name` | string | its label: "Brand", "Sock Rise" | | `kind` | `select` \| `range` | `select` lists values; `range` states bounds | | `values[]` | FacetValue | `{value, name, selected?}` | | `min` `max` | number? | for a `range`; money ranges are in the same units as `Money.amount` | | `maps_to` | string? | 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 | Field | Type | Notes | |---|---|---| | `marketplace` `id` `title` | string | always present | | `url` | string? | the listing's own page | | `price` | Money? | what a buyer pays today | | `listPrice` | Money? | the crossed-out reference it is discounted from | | `subscriptionPrice` | Money? | the recurring-delivery price: Subscribe & Save and equivalents | | `description` | string? | as the marketplace gives it; may be HTML on a product page, a short blurb on a search card | | `condition` | string? | as worded: `"Pre-Owned"`, `"Used: Very Good"`, `"New"` | | `unitPrice` | object? | `{price, unit}` — `"$1.00 / count"` | | `brand` | string? | lifted out of the specification table; a "Brand" row is not repeated in `attributes` | | `identifiers` | object? | `{gtin?, mpn?, model?}` — barcode (UPC/EAN/ISBN), part/model number, model name | | `rating` | Rating? | `{average, count?, distribution?, starCounts?, grouped?}` — see below | | `seller` | Seller? | `{id?, name?, url?, logoUrl?, rating?, country?, positivePercent?, productCount?}` — see below | | `fulfilledBy` | string? | who ships it, when the marketplace states it separately from the seller: `"Amazon"`, `"TikTok"` | | `salesCount` | number? | normalized units sold; a **floor**, since marketplaces round | | `salesLabel` | string? | the blurb verbatim: `"3,000+ sold"` | | `inStock` | boolean? | | | `stock` | number? | units available, where the marketplace states a number | | `status` | string? | the availability line as worded: `"Only 3 left in stock"` | | `delivery` | DeliveryOption? | the default way it ships — see below | | `shippingOptions` | DeliveryOption[]? | every option shown or quoted, the default first | | `returnsPolicy` | string? | | | `badges` | string[] | merchandising badges: `["Amazon's Choice"]` | | `promotions` | string[] | savings: `["5% off coupon applied"]` | | `paymentOptions` | string[]? | ways to pay: `["Pay in 4 payments at 0% interest"]` | | `listingFormat` | string? | how it sells: `"Buy It Now"`, `"Auction"` | | `groupId` | string? | the variation family this belongs to | | `attributes` | Attribute[] | `{name, value}` pairs — the specification table, minus rows lifted into typed fields | | `offersSummary` | object? | `{count?, fromPrice?}` — the roll-up, not the list | | `priceRange` | object? | `{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 | | `endsAt` | string? | when the current offer ends: an auction's close or a time-limited deal's end. ISO 8601 | | `deliveryTo` | object? | `{label?, country?, postalCode?}` — the place the `delivery` and `shippingOptions` promises were quoted for | | `ext` | object? | 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, category nodes, delivery options and protection plans can carry one, and so can the two pages: a search result and a category list. ### DeliveryOption One shape for every delivery — `delivery`, each of `shippingOptions`, an offer's `delivery`, and each option of a [ShippingQuote](#shippingquote). | Field | Type | Notes | |---|---|---| | `estimate` | string? | the arrival estimate as worded: `"FREE delivery Wednesday, October 28"` | | `cost` | Money? | the shipping fee | | `free` | boolean? | the marketplace says shipping is free | | `minDays` `maxDays` | number? | transit window in days, where stated as numbers | | `handlingDays` | number? | days before the seller dispatches | | `cutoff` | string? | order-by countdown for this option: `"Order within 3 hrs 22 mins"` | | `condition` | string? | who or what qualifies: `"on orders shipped by Amazon over $35"` | | `available` | boolean? | `false` — the marketplace does not ship to the region asked | | `minimumOrder` | Money? | the order total its `cost`/`free` needs — Amazon's "on $35 of items" | | `carrier` `code` | string? | the service, as the marketplace names it | | `shipsFrom` | string? | country the parcel leaves from | | `ext` | object? | a marketplace-only fact about this option: an exact arrival date, pickup details, the regular fee before a waiver, the membership a member-only option needs (`membership: "Prime"`), AliExpress's delivery guarantee (`guaranteedDays`) and `tracking` | 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 `ext.membership`, never presented as everyone's. ### Seller | Field | Type | Notes | |---|---|---| | `id` `name` `url` | string? | | | `logoUrl` | string? | the store's logo, as the marketplace shows it beside the seller | | `rating` | Rating? | the seller's feedback score, in the same shape as a product's `rating` | | `country` | string? | where the seller is based | | `positivePercent` | number? | the share of the seller's feedback that is positive, as a percent from 0 to 100 ("99.8% positive"); not a star score | | `productCount` | number? | how many products the store has on sale, exactly as the marketplace states it; absent where it states none or only a capped figure | `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". `rating.distribution` is the percent of reviews at each star, as the marketplace publishes it. `rating.starCounts` is the exact number of reviews at each star (`{"5": 348147, "4": 31377}`), present only where the marketplace states exact counts — never worked out from the percentages, which are rounded. A marketplace that states both fills both. ### SearchItem Only | Field | Type | Notes | |---|---|---| | `imageUrl` | string? | the card's primary image | | `sponsored` | boolean? | a paid placement rather than an organic result | | `variantCount` | number? | how many combinations the card says the item comes in ("+12 colors"); `product` lists them in `variants` | | `category` | CategoryStep[]? | where the result sits in the taxonomy, leaf last; a card usually carries only the leaf | | `matchScore` | number? | image search only: how closely the result matches your picture, 0–1 (1 = the marketplace's best match); comparable within one result set | | `variants` | Variant[]? | the combinations the card shows — its swatches, or the options it lists with prices. Same shape as on a product; a card usually names each by `name` alone. `variantCount` stays the number the card states, which can exceed the ones it shows | | `offers` | Offer[]? | other ways to buy that the card itself shows ("Another way to buy", another condition at its own price). Same shape as on a product | ### Product Only | Field | Type | Notes | |---|---|---| | `features` | string[] | the seller's bulleted selling points, in page order | | `highlights` | string[]? | the marketplace's own key points about the product, in page order — not the seller's `features`, and not a digest of reviews (that is `reviewSummary`) | | `media` | object | `{images: string[], videos: []}` | | `category` | CategoryStep[] | full path, leaf last; a step has an `id`, a `name`, or both | | `options` | Option[] | the axes: Colour, Size | | `variants` | Variant[] | the combinations — see below | | `offers` | Offer[] | competing sellers, where the marketplace enumerates them | | `protectionPlans` | ProtectionPlan[]? | add-on protection plans and extended warranties sold with the item — see below | | `reviewSummary` | string? | the marketplace's own generated digest | | `reviewAspects` | array | labelled sentiment: `{name, mentions?, positive?, negative?}` | | `boxContents` | string[] | "what's in the box" | | `brandUrl` `replacedBy` | string? | | | `package` | object? | the shipped parcel: `{weightKg?}`; its measurements, where stated, are its `ext` | | `reviews` | Review[] | top reviews carried by the product page itself | | `conditionNote` | string? | the seller's own note about the item's `condition` | | `purchaseLimit` | number? | most units one buyer may order of this item ("Limit 2 per customer") | | `listedAt` | string? | when the listing was first published on the marketplace (not when we fetched it). ISO 8601 date or time | ### 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 field | Type | Notes | |---|---|---| | `id` | string | the marketplace's id for the combination | | `productId` | string? | present ⇒ fetchable with `product` (above) | | `name` | string? | the combination's label as the marketplace shows it in one string: `"Waterfall blue"`, `"Red / M"`. Where `selections` are known they stay the structured answer | | `images` | string[]? | this combination's own pictures, main first | | `price` `listPrice` | Money? | | | `purchaseLimit` | number? | most units one buyer may order | | `inStock` | boolean? | | | `stock` | number? | units available of this variant | | `identifiers` | object? | `{gtin?, mpn?, model?}` — this combination's own barcode and part number, where published per variant | | `weightKg` | number? | this combination's weight in kilograms | | `selections` | array | `{optionId, valueId, optionName, valueName}` — which option values it is | ### Offer A competing seller, or another way to buy, where the marketplace enumerates them: `{seller?, price, listPrice?, condition?, conditionNote?, fulfilledBy?, inStock?, status?, delivery?, shippingOptions?, unitPrice?, promotions?, paymentOptions?}`. `delivery` is a DeliveryOption, because a cheaper offer that ships later is a different offer. `shippingOptions`, `unitPrice`, `promotions` and `paymentOptions` mean what the item's own fields mean, for this offer. ### ProtectionPlan An add-on protection plan or extended warranty the marketplace sells with the item. | Field | Type | Notes | |---|---|---| | `id` | string? | the marketplace's id for the plan | | `name` | string | as the page names it: `"2-Year Protection Plan"` | | `price` | Money? | what the plan costs | | `provider` | string? | who underwrites it, as stated: `"Asurion, LLC"` | A plan whose `price` recurs states its billing period in `ext.period` (`"month"`); without one, the price is a one-off. ## 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 } ``` | Field | Type | Notes | |---|---|---| | `summary` | Rating? | describes whatever set `reviews` was drawn from | | `page.total` | number? | 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?, authorAvatarUrl?, rating, title?, body?, date?, country?, verified?, helpfulCount?, unhelpfulCount?, recommended?, badges?, syndicatedFrom?, productId?, productImageUrl?, variant?, variantId?, language?, images[], incentivized?, reply?}` | | `grouped` | boolean? | these are the whole family's reviews, not only this product's | | `reviewSummary` | string? | the marketplace's generated review digest, where the reviews page shows one — the same field as on a product | | `reviewAspects` | array? | what reviewers mention and how it splits, where the reviews page shows it — the same shape as on a product | | `cursor` | string? | | ### A Product with No Reviews It answers `200` with an explicit zero, so you can show "0 reviews" with confidence: ```json { "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.total` | Means | |---|---| | `0` | this 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". `review.variantId` is the marketplace's id for that sibling, where it states one per review: `variant` is the label, `variantId` the handle. `review.language` is the language the review was written in (BCP-47), as the marketplace states it. On a translated review it is still the original's language, and `body` is the translation the marketplace showed. `review.unhelpfulCount` is the readers who marked it not helpful, the other side of `helpfulCount`. `review.recommended` is the reviewer's own answer to "would you recommend this product?", where they gave one. `review.badges` are the review's other labels as the marketplace words them ("Walmart Associate"). `review.syndicatedFrom` names the other site the review was first written on. `review.productId` and `review.productImageUrl` say what the reviewer bought when the page mixes products — a category's reviews, a grouped page; `productImageUrl` is the catalogue picture, never a reviewer's photo (those are `images`). `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`. ```json { "marketplace": "aliexpress", "path": [ { "id": "200000345", "name": "Women's Clothing" } ], "categories": [ { "id": "349", "name": "Skirts", "parentId": "200000345" }, { "id": "201531101", "name": "Matching Sets", "parentId": "200000345" } ] } ``` | Field | Type | Notes | |---|---|---| | `marketplace` | string | | | `path` | CategoryStep[] | Ancestors of the category asked for, leaf last. Empty at the root. | | `categories` | CategoryNode[] | | | `ext` | object? | A marketplace-only fact about the listed category itself, such as its best sellers | ### CategoryNode | Field | Type | Notes | |---|---|---| | `id` | string | **The id you pass back** — to `categories` to descend, and to `browse` when `browsable` allows | | `name` | string | | | `parentId` | string? | Absent at a root | | `hasChildren` | boolean? | Absent means **unknown**, not "no" — most marketplaces do not say | | `browsable` | boolean? | See below | | `imageUrl` | string? | The category's picture, as the marketplace shows it on the category's tile | 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 | |---|---| | Amazon | departments (they are store slugs, not browse nodes) | | Walmart | some 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](/docs/api-reference/marketplaces) 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 ```json { "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`](/docs/api-reference/aliexpress/shipping) returns: every way one item ships to one region, cheapest first. ```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, "estimate": "Oct. 30 - Nov. 07", "shipsFrom": "CN", "ext": { "guaranteedDays": 60, "tracking": true } } ] } ``` `options` are [DeliveryOption](#deliveryoption)s. `available: false` with no options means the marketplace does not ship there — an answer, not an error. ## Errors ```json { "success": false, "error": { "code": "INVALID_PARAMS", "message": "…" } } ``` | Status | Code | Means | |---|---|---| | 400 | `INVALID_PARAMS` | a parameter is missing, malformed, or not one this marketplace accepts | | 400 | `CATEGORY_NOT_BROWSABLE` | the category exists but has no listing page — descend with `categories` and browse a child | | 404 | `NOT_FOUND` | that id does not exist | | 404 | `UNKNOWN_MARKETPLACE` / `UNKNOWN_OPERATION` | check `GET /v1/marketplaces` | | 429 | rate limited | carries `Retry-After` | | 501 | `UNSUPPORTED` | the data exists but is not available through this API — not a problem with your request | | 502 | upstream | the 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". --- # Filters > The filter vocabulary shared by every marketplace search — names, units, and which marketplace accepts which. Source: https://apinoa.com/en/docs-raw/api-reference/marketplaces/filters Search narrows the same way everywhere. One name per concept, one unit per concept, and a marketplace either honours a filter or rejects it by name. Every filter is optional, and **filtering costs nothing extra** — except `brands` and `facets` on Amazon, which cost 2 units (see below). ## The Vocabulary | Parameter | Type | Meaning | |---|---|---| | `priceMin` | number | Lowest price to return, in **decimal major units** (`40`, `10.5`) | | `priceMax` | number | Highest price to return, same unit | | `brands` | string list | Brand names as the marketplace spells them: `brands=Nike,Adidas` | | `ratingMin` | number | Lowest average star rating, on the marketplace's 0–5 scale | | `condition` | enum | `new` · `used` · `refurbished` | | `freeShipping` | boolean | Only listings the marketplace advertises as shipping free | | `inStock` | boolean | Only listings the marketplace reports as purchasable now | | `sort` | enum | Result ordering — see below | | `categoryId` | string | The marketplace's own category id — see below | | `facets` | list | Narrowings from the result's own filter rail: `facets=Sock Rise:Ankle,Color:Black` | `priceMin`/`priceMax` are in the same unit the response reports. `Money.amount` is decimal major units, so `priceMin=40` and a returned `{"currency":"USD","amount":40}` mean the same thing. Marketplaces that want minor units convert internally; you never see cents. A list is comma-separated over a query string and a real array over a JSON body. Both are the same request: ``` ?brands=Nike,Adidas {"brands": ["Nike", "Adidas"]} ``` Booleans accept `true`/`false`/`1`/`0`. `freeShipping=false` really means false. ## Who Accepts What | | Amazon | Shopify | Walmart | Aliexpress | |---|:---:|:---:|:---:|:---:| | `priceMin` / `priceMax` | ✅ | ✅ | ✅ | ✅ | | `categoryId` | ✅ | ✅ | ✅ | ✅ | | `sort` | ✅ | — | ✅ | ✅ | | `brands` | ✅ *(costs 2)* | — | ✅ | — | | `ratingMin` | ⚠️ `4` only | ✅ | ✅ | — | | `condition` | — | ⚠️ no `refurbished` | — | — | | `inStock` | — | ✅ | — | — | | `facets` | ✅ *(costs 2)* | — | ✅ | — | `freeShipping` is part of the shared vocabulary, but no marketplace currently served accepts it. A dash means a 400 naming the parameter, not a quiet no-op. The gaps are the marketplaces', not ours. Where a filter is missing it is because the marketplace has none: AliExpress's search has no rating refinement, Shopify sells no refurbished tier (so `condition=refurbished` is refused rather than folded into `used` — different claims) and offers no sort at all. Amazon's rating rail has exactly one threshold, "4 Stars & Up" — hence `ratingMin=4` and a 400 for anything else, rather than quietly answering a `ratingMin=3` request with 4-and-up results. ## `categoryId` There is no shared category taxonomy, so the id is the marketplace's own: a browse node on Amazon (`679416011`), a `cat_id` on Walmart (`4171`), a category id on AliExpress (`200000345`), a Standard Product Taxonomy id on Shopify (`aa-1-13-8`). **Where the ids come from.** Ask for them: `categories` lists a level of the marketplace's own taxonomy, and `browse` takes what it returns. Call it with no `categoryId` for the top level and walk down, or start from an id in a `Product.category[]`. Two cautions the endpoint pages spell out: an id is not always usable on every operation (Walmart's `search` takes a bare `cat_id`, its `browse` needs the underscore chain that `categories` returns), and a few levels are navigational only, marked `"browsable": false`. On `search` it **narrows** a keyword query rather than replacing it, and `query` stays required. To list a category with **no keyword at all**, use the `browse` operation instead, where `categoryId` is required and `query` is rejected. Every marketplace except Shopify has it, and it takes the same filters `search` does. Two things differ from `search` and are worth knowing before you call it: - **The id can be a different kind of thing.** Walmart's `browse` wants the underscore-joined chain (`3944_133251_1095191_1230614_1230478`), not the bare `cat_id` its `search` takes. - **A bad id is not always an error.** AliExpress returns an empty `items` — it does not distinguish an unknown category from an empty one. ## Facets — the Marketplace's Own Filter Rail The typed filters above are the ones every marketplace has in common. They are a small slice of what a site actually offers: one Walmart socks search renders 25 filter groups, of which four map onto a typed parameter. The other twenty-one — Sock Rise, Sleeve Length, Fabric Material, Weather Suitability — are specific to that query. Search a laptop and they are replaced by Screen Size and RAM. Because the rail is per-**query**, it is discovered rather than declared. A search response carries it: ```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 } ] } ``` Send a value back with `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` names the typed filter covering the same concept, where there is one. **Prefer the typed parameter** — it means the same thing on every marketplace, and `facets` does not. ### Who Has a Rail | | Discover | Apply | Notes | |---|:---:|:---:|---| | **walmart** | ✅ | ✅ | free both ways | | **amazon** | ✅ `include=facets` | ✅ | discovery is 1 unit; applying is 2 — see below | | **shopify** | `[]` | — | Shopify search has no filter rail | | **aliexpress** | — | — | no rail | An **absent** `facets` means no rail was returned. An **empty** one means there is no rail for this query. Shopify reports `[]`. ### Amazon: Discovery Is 1 Unit, Applying Is 2 Two different things, and only one of them costs extra. **`include=facets` costs 1 unit** — the same as a search without it. **`brands` and `facets` cost 2 units.** `cost.search` is `1..2`. Everything else on Amazon still costs 1. A label the rail does not carry is a 400 naming it, with a sample of what the rail did offer. Amazon truncates its rail, so an unmatched label is genuinely ambiguous — returning unfiltered results would hide that, and returning none would be a guess. ## Sort | Value | Meaning | Amazon | Walmart | Aliexpress | |---|---|:---:|:---:|:---:| | `best_match` | the marketplace's own relevance | ✅ | ✅ | — | | `price_asc` | price, low to high | ✅ | ✅ | ✅ | | `price_desc` | price, high to low | ✅ | ✅ | ✅ | | `newly_listed` | most recently listed | ✅ | ✅ | — | | `best_seller` | by units sold | — | ✅ | ✅ | | `avg_review` | by average rating | ✅ | — | — | The names are ours; each marketplace's own code is internal. A value a marketplace cannot do is a 400. ## Examples ```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" ``` ## One Caveat Worth Knowing A price bound filters the **listing**, and some listings carry a price range rather than a price. A product whose variants run $28–$50 satisfies a `priceMin=40` request through its $50 variant, while its card reports the "from" price of $28. So a few returned rows can sit just outside the band you asked for. Measured, 90–95% land inside it. If you need a hard bound, filter the returned `price` yourself — the marketplaces behave this way and we report what they return rather than hiding rows. --- # Amazon > Amazon on sixteen marketplaces, through the shared marketplace schema. Pick a marketplace with `region`; prices come back in its own currency. Source: https://apinoa.com/en/docs-raw/api-reference/amazon ## Operations | Operation | Endpoint | Returns | Price | |---|---|---|---| | [Search](/docs/api-reference/amazon/search) | `GET \| POST /v1/amazon/search` | `SearchResult` | $0.001–$0.002 | | [Image Search](/docs/api-reference/amazon/image-search) | `POST /v1/amazon/image-search` | `SearchResult` | $0.001–$0.002 | | [Browse](/docs/api-reference/amazon/browse) | `GET \| POST /v1/amazon/browse` | `SearchResult` | $0.001–$0.002 | | [Categories](/docs/api-reference/amazon/categories) | `GET \| POST /v1/amazon/categories` | `CategoryList` | $0.001 | | [Product](/docs/api-reference/amazon/product) | `GET \| POST /v1/amazon/product` | `Product` | $0.002–$0.032 | No `reviews`: Amazon reviews are not available. ## Marketplaces | `region` | site | currency | |---|---|---| | `US` (default) | amazon.com | USD | | `CA` | amazon.ca | CAD | | `MX` | amazon.com.mx | MXN | | `BR` | amazon.com.br | BRL | | `FR` | amazon.fr | EUR | | `IT` | amazon.it | EUR | | `ES` | amazon.es | EUR | | `NL` | amazon.nl | EUR | | `SE` | amazon.se | SEK | | `TR` | amazon.com.tr | TRY | | `AE` | amazon.ae | AED | | `SA` | amazon.sa | SAR | | `IN` | amazon.in | INR | | `JP` | amazon.co.jp | JPY | | `SG` | amazon.sg | SGD | | `AU` | amazon.com.au | AUD | Every operation works on every marketplace, with three differences worth knowing: - **Language.** Text fields (titles, delivery and stock lines) come back in English wherever the site offers it. amazon.com.mx, amazon.com.br and amazon.com.tr serve only Spanish, Portuguese and Turkish, so their text fields arrive in that language; prices, ratings, review counts and free-delivery flags are normalized the same as everywhere else. - **amazon.fr** does not accept `ratingMin`, `brands`, `facets` or `include=facets` (a 400 names the parameter), and its `browse` results carry no `facets`. - **Category ids are per marketplace.** A browse node from `categories` on one marketplace means nothing on another; walk `categories` with the same `region` you browse with. The fill rates below were measured on amazon.com. ## What Actually Arrives The schema says these are optional. Here is how often Amazon fills them: **search items** | Field | Fill | |---|---| | `title` `url` | ~99% | | `imageUrl` | ~98% | | `price` | ~90% | | `rating.average` `rating.count` | ~90% | | `delivery` | ~80% | | `shippingOptions` (the second, faster option) | ~70% | | `salesLabel` | ~50% | **product** | Field | Fill | |---|---| | `title` | 100% | | `media.images` | ~95% | | `brand` `rating.average` `rating.distribution` `features` `description` `attributes` `reviews` | ~90% | | `price` `status` `seller.name` | ~85% | | `category` `delivery` `groupId` `offers` | ~80% | | `reviewSummary` `returnsPolicy` | ~70% | | `reviewAspects` `brandUrl` | ~60% | Amazon is the richest of the marketplaces on the detail view — `reviewSummary`, `reviewAspects`, `rating.distribution` and `offers` arrive here and mostly nowhere else. `price` is not 100% because a listing can genuinely have no current offer; that is an absent `price`, never a zero. ## Things Specific to Amazon **Variants are separately fetchable.** Each colour or size is its own ASIN, so every `variant` carries a `productId` you can pass back to `product`, and `groupId` names the family. Shopify and Walmart work the other way. **A rating may describe the family, not the variant.** Every sibling of a variation family reports the same aggregate — measured, 62 variants of one shirt all reporting 4.6 / 334,685. Where that is detected the response sets `rating.grouped: true`. **Brand-store pages carry no category.** Apple and Amazon-device pages have no breadcrumb and no Best Sellers Rank, so `category` is legitimately `[]` rather than missing. --- # Search > Search Amazon by keyword, with price, rating, brand and category filters, on any of sixteen Amazon marketplaces. Source: https://apinoa.com/en/docs-raw/api-reference/amazon/search ```http GET | POST /v1/amazon/search ``` Send parameters as a query string with `GET`, or as a JSON body with `POST`. Both are equivalent. ## Request ```bash tab="cURL" curl "https://gateway.apinoa.com/v1/amazon/search?query=wool+socks&priceMax=30" \ -H "x-api-key: $APINOA_API_KEY" ``` ```js tab="JavaScript" const params = new URLSearchParams({ query: "wool socks", priceMax: "30" }); const res = await fetch(`https://gateway.apinoa.com/v1/amazon/search?${params}`, { headers: { "x-api-key": process.env.APINOA_API_KEY }, }); const { success, data, error } = await res.json(); ``` ```python tab="Python" import os import requests res = requests.get( "https://gateway.apinoa.com/v1/amazon/search", params={"query": "wool socks", "priceMax": "30"}, headers={"x-api-key": os.environ["APINOA_API_KEY"]}, ) body = res.json() ``` ## Parameters | Parameter | Type | Required | Notes | |---|---|---|---| | `query` | string | yes | Keywords to search for. Required here; to search by picture use `image-search`. | | `brands` | string list | | Brand names as Amazon spells them, e.g. brands=Nike,Adidas. A call that uses `brands` costs 2 units instead of 1. Not available on amazon.fr (`region=FR`). | | `categoryId` | string | | Narrow to one category, by the marketplace's own id. Opaque and per-marketplace; take it from Product.category[].id or from a result's `facets`. | | `facets` | string \| object list | | Narrowings taken from a result's `facets`, e.g. facets=Color:Black. A call that uses `facets` costs 2 units instead of 1. Not available on amazon.fr (`region=FR`). | | `include` | string list | | Expansions that cost more than the default answer, e.g. include=facets. | | `page` | integer | | 1-based page number; each page is charged separately. There is no `pageSize` (Amazon fixes the count per page by keyword, 13-47 in practice) and no `cursor` — paging is by number. | | `priceMax` | number ≥ 0 | | Highest price to return, in decimal major units. | | `priceMin` | number ≥ 0 | | Lowest price to return, in decimal major units. | | `ratingMin` | number | | `4` only. Amazon's rating filter offers exactly one threshold, "4 Stars & Up"; any other value is a 400 rather than a silently different search. Not available on amazon.fr (`region=FR`). | | `region` | enum | | Which Amazon marketplace to query, by buyer country: US, CA, MX, BR, FR, IT, ES, NL, SE, TR, AE, SA, IN, JP, SG, AU. Defaults to US. Prices come back in that marketplace's own currency (JP in JPY, FR in EUR). `US` `CA` `MX` `BR` `FR` `IT` `ES` `NL` `SE` `TR` `AE` `SA` `IN` `JP` `SG` `AU` | | `sort` | enum | | Result ordering: `best_match`, `price_asc`, `price_desc`, `newly_listed` or `avg_review`. Defaults to `best_match`, Amazon's own relevance ordering. `best_match` `price_asc` `price_desc` `newly_listed` `avg_review` | The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json ## Response Returns [`SearchResult`](/docs/api-reference/marketplaces/response-schema#searchresult) in `data`. A real response, shortened: arrays keep their first entries and long text is cut. ```json { "success": true, "data": { "marketplace": "amazon", "query": "wool socks", "page": { "index": 1, "size": 48, "total": 13179 }, "items": [ { "marketplace": "amazon", "id": "B07F3FFTMK", "title": "Alvada Mens Merino Wool Crew Socks Thermal and Warm Socks for Winter Work Hiking Running 3 Pairs", "attributes": [], "price": { "currency": "USD", "amount": 13.49 }, "url": "https://www.amazon.com/dp/B07F3FFTMK", "imageUrl": "https://m.media-amazon.com/images/I/91aOC3G1W6L.jpg", "variantCount": 24, "listPrice": { "currency": "USD", "amount": 14.99 }, "unitPrice": { "price": { "currency": "USD", "amount": 4.5 }, "unit": "count" }, "rating": { "average": 4.5, "count": 37581 }, "salesLabel": "10K+ bought in past month", "salesCount": 10000, "delivery": { "estimate": "Non-members get FREE delivery Thu, Oct 29 on $35 of items shipped by Amazon", "free": true, "minimumOrder": { "currency": "USD", "amount": 35 }, "minDays": 5, "maxDays": 5 }, "shippingOptions": [ { "estimate": "Non-members get FREE delivery Thu, Oct 29 on $35 of items shipped by Amazon", "free": true, "minimumOrder": { "currency": "USD", "amount": 35 }, "minDays": 5, "maxDays": 5 }, { "estimate": "Join Prime to get FREE delivery Tomorrow, Oct 25", "free": true, "minDays": 1, "maxDays": 1, "ext": { "membership": "Prime" } } ], "badges": [ "Overall Pick", "Amazon's Choice" ], "promotions": [] } ] } } ``` ## Errors | Status | Code | Meaning | |---|---|---| | 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. | | 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. | | 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. | | 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. | | 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. | Every code is listed in [Errors](/docs/errors). Failed calls are not billed. ## Pricing **Price** $0.001 per call, plus $0.001 for each extra call it makes — at most 1 more, so $0.002 in total. Only `brands` and rail facets add one: either makes the call 2 units. Every other filter costs nothing extra. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day. ## Notes - **`region` picks the marketplace.** `region=JP` searches amazon.co.jp and prices in yen. The sixteen countries are listed on the [Amazon](/docs/api-reference/amazon) page. Omitted, it is amazon.com. - **Paging is by `page`.** Amazon fixes the page size by keyword (13 to 47 in practice), so there is no `pageSize` and no `cursor`. - **`ratingMin` is `4` or a 400.** Amazon offers exactly one rating threshold, "4 Stars & Up". Any other value is refused rather than answered with a different search. - **`brands` and `facets` cost 2 units.** Every other filter costs nothing extra. See [Filters](/docs/api-reference/marketplaces/filters). - **Not supported:** `condition`, `freeShipping`, `inStock`. --- # Image Search > Find Amazon products from a photo. Each match is the same item a keyword search returns. Source: https://apinoa.com/en/docs-raw/api-reference/amazon/image-search ```http POST /v1/amazon/image-search ``` `POST` only, with a JSON body. ## Request ```bash tab="cURL" curl -X POST "https://gateway.apinoa.com/v1/amazon/image-search" \ -H "x-api-key: $APINOA_API_KEY" \ -H "content-type: application/json" \ -d "{\"image\": {\"contentType\": \"image/jpeg\", \"data\": \"$(base64 < photo.jpg | tr -d '\n')\"}}" ``` ```js tab="JavaScript" import { readFile } from "node:fs/promises"; const data = (await readFile("photo.jpg")).toString("base64"); const res = await fetch("https://gateway.apinoa.com/v1/amazon/image-search", { method: "POST", headers: { "x-api-key": process.env.APINOA_API_KEY, "content-type": "application/json" }, body: JSON.stringify({ image: { contentType: "image/jpeg", data } }), }); const { success, data: result, error } = await res.json(); ``` ```python tab="Python" import base64 import os import requests with open("photo.jpg", "rb") as f: data = base64.b64encode(f.read()).decode() res = requests.post( "https://gateway.apinoa.com/v1/amazon/image-search", json={"image": {"contentType": "image/jpeg", "data": data}}, headers={"x-api-key": os.environ["APINOA_API_KEY"]}, ) body = res.json() ``` ## Parameters | Parameter | Type | Required | Notes | |---|---|---|---| | `image` | object | yes | The picture to search by: `{contentType, data}` with `data` base64-encoded. POST only — a base64 image is tens of kilobytes and a URL caps out around 8 KB. | | `categoryId` | string | | Narrow to one category, by the marketplace's own id. Opaque and per-marketplace; take it from Product.category[].id or from a result's `facets`. | | `page` | integer | | 1-based page number over the visual matches, about 48 per page; each page is charged separately. No `pageSize` and no `cursor`. | | `priceMax` | number ≥ 0 | | Highest price to return, in decimal major units. | | `priceMin` | number ≥ 0 | | Lowest price to return, in decimal major units. | | `query` | string | | Optional words that narrow the visual matches, e.g. the product type. Supplying it is usually much more precise. | | `ratingMin` | number | | `4` only. Amazon's rating filter offers exactly one threshold, "4 Stars & Up"; any other value is a 400 rather than a silently different search. Not available on amazon.fr (`region=FR`). | | `region` | enum | | Which Amazon marketplace to query, by buyer country: US, CA, MX, BR, FR, IT, ES, NL, SE, TR, AE, SA, IN, JP, SG, AU. Defaults to US. Prices come back in that marketplace's own currency (JP in JPY, FR in EUR). `US` `CA` `MX` `BR` `FR` `IT` `ES` `NL` `SE` `TR` `AE` `SA` `IN` `JP` `SG` `AU` | | `sort` | enum | | Result ordering: `best_match`, `price_asc`, `price_desc`, `newly_listed` or `avg_review`. Defaults to `best_match`, Amazon's own relevance ordering. `best_match` `price_asc` `price_desc` `newly_listed` `avg_review` | The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json ## Response Returns [`SearchResult`](/docs/api-reference/marketplaces/response-schema#searchresult) in `data`. A real response, shortened: arrays keep their first entries and long text is cut. ```json { "success": true, "data": { "marketplace": "amazon", "page": { "index": 1, "size": 48, "total": 90 }, "items": [ { "marketplace": "amazon", "id": "B07F3FFTMK", "title": "Alvada Mens Merino Wool Crew Socks Thermal and Warm Socks for Winter Work Hiking Running 3 Pairs", "attributes": [], "price": { "currency": "USD", "amount": 13.49 }, "url": "https://www.amazon.com/dp/B07F3FFTMK", "imageUrl": "https://m.media-amazon.com/images/I/91aOC3G1W6L.jpg", "category": [ { "name": "Women's Hiking Socks" } ], "variantCount": 24, "listPrice": { "currency": "USD", "amount": 14.99 }, "unitPrice": { "price": { "currency": "USD", "amount": 4.5 }, "unit": "count" }, "rating": { "average": 4.5, "count": 37581 }, "salesLabel": "10K+ bought in past month", "salesCount": 10000, "delivery": { "estimate": "Non-members get FREE delivery Thu, Oct 29 on $35 of items shipped by Amazon", "free": true, "minimumOrder": { "currency": "USD", "amount": 35 }, "minDays": 5, "maxDays": 5 }, "shippingOptions": [ { "estimate": "Non-members get FREE delivery Thu, Oct 29 on $35 of items shipped by Amazon", "free": true, "minimumOrder": { "currency": "USD", "amount": 35 }, "minDays": 5, "maxDays": 5 }, { "estimate": "Join Prime to get FREE delivery Tomorrow, Oct 25", "free": true, "minDays": 1, "maxDays": 1, "ext": { "membership": "Prime" } } ], "badges": [ "Best Seller in Women's Hiking Socks", "Limited time deal" ], "promotions": [], "matchScore": 0.70708364 } ] } } ``` ## Errors | Status | Code | Meaning | |---|---|---| | 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. | | 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. | | 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. | | 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. | | 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. | Every code is listed in [Errors](/docs/errors). Failed calls are not billed. ## Pricing **Price** $0.001 per call, plus $0.001 for each extra call it makes — at most 1 more, so $0.002 in total. 2 units when the picture matches; 1 unit when it matches nothing. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day. ## Notes - **POST only.** A base64 image is tens of kilobytes; a query string caps out around 8 KB. - **`query` narrows the picture.** The image decides what the results are about, and words narrow them. A photo of a figure returned 48 matches on its own and 22 with `query: "plush"`. Price, rating, category filters and `sort` apply as they do on search. - **JPEG, PNG or GIF.** WebP and HEIC are refused with a 400; convert them first. - **No match is not an error.** A picture Amazon recognises nothing in returns an empty `items` and costs 1 unit; a match costs 2. - **Paging.** About 48 matches per page; ask for the next `page` until `items` comes back empty. Each page is billed. --- # Browse > List the products in one Amazon category, with no keyword. Source: https://apinoa.com/en/docs-raw/api-reference/amazon/browse ```http GET | POST /v1/amazon/browse ``` Send parameters as a query string with `GET`, or as a JSON body with `POST`. Both are equivalent. ## Request ```bash tab="cURL" curl "https://gateway.apinoa.com/v1/amazon/browse?categoryId=679416011&priceMax=50&sort=price_asc" \ -H "x-api-key: $APINOA_API_KEY" ``` ```js tab="JavaScript" const params = new URLSearchParams({ categoryId: "679416011", priceMax: "50", sort: "price_asc" }); const res = await fetch(`https://gateway.apinoa.com/v1/amazon/browse?${params}`, { headers: { "x-api-key": process.env.APINOA_API_KEY }, }); const { success, data, error } = await res.json(); ``` ```python tab="Python" import os import requests res = requests.get( "https://gateway.apinoa.com/v1/amazon/browse", params={"categoryId": "679416011", "priceMax": "50", "sort": "price_asc"}, headers={"x-api-key": os.environ["APINOA_API_KEY"]}, ) body = res.json() ``` ## Parameters | Parameter | Type | Required | Notes | |---|---|---|---| | `categoryId` | string | yes | REQUIRED. An Amazon browse NODE — the digits from a category URL, e.g. `172541`. The department slugs `categories` returns at its root (`electronics`) are NOT browse nodes and are refused with `CATEGORY_NOT_BROWSABLE`: call `categories` with the slug to get its child nodes and browse one of those. | | `brands` | string list | | Brand names as Amazon spells them, e.g. brands=Nike,Adidas. A call that uses `brands` costs 2 units instead of 1. Not available on amazon.fr (`region=FR`). | | `facets` | string \| object list | | Narrowings taken from a result's `facets`, e.g. facets=Color:Black. A call that uses `facets` costs 2 units instead of 1. Not available on amazon.fr (`region=FR`). | | `page` | integer | | 1-based page number; each page is charged separately. There is no `pageSize` (Amazon fixes the count per page, 13-47 in practice) and no `cursor` — paging is by number. | | `priceMax` | number ≥ 0 | | Highest price to return, in decimal major units. | | `priceMin` | number ≥ 0 | | Lowest price to return, in decimal major units. | | `ratingMin` | number | | `4` only. Amazon's rating filter offers exactly one threshold, "4 Stars & Up"; any other value is a 400 rather than a silently different search. Not available on amazon.fr (`region=FR`). | | `region` | enum | | Which Amazon marketplace to query, by buyer country: US, CA, MX, BR, FR, IT, ES, NL, SE, TR, AE, SA, IN, JP, SG, AU. Defaults to US. Prices come back in that marketplace's own currency (JP in JPY, FR in EUR). `US` `CA` `MX` `BR` `FR` `IT` `ES` `NL` `SE` `TR` `AE` `SA` `IN` `JP` `SG` `AU` | | `sort` | enum | | Result ordering: `best_match`, `price_asc`, `price_desc`, `newly_listed` or `avg_review`. Defaults to `best_match`, Amazon's own relevance ordering. `best_match` `price_asc` `price_desc` `newly_listed` `avg_review` | The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json ## Response Returns [`SearchResult`](/docs/api-reference/marketplaces/response-schema#searchresult) in `data`. A real response, shortened: arrays keep their first entries and long text is cut. ```json { "success": true, "data": { "marketplace": "amazon", "page": { "index": 1, "size": 0 }, "items": [], "facets": [] } } ``` ## Errors | Status | Code | Meaning | |---|---|---| | 400 | `CATEGORY_NOT_BROWSABLE` | The category is a navigation level with no products. Browse one of its children from `categories`. | | 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. | | 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. | | 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. | | 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. | | 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. | Every code is listed in [Errors](/docs/errors). Failed calls are not billed. ## Pricing **Price** $0.001 per call, plus $0.001 for each extra call it makes — at most 1 more, so $0.002 in total. Only `brands` and rail facets add one: either makes the call 2 units. Every other filter costs nothing extra. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day. ## Notes - **Same filters as `search`, minus the keyword.** A `query` is a 400, not silently dropped. Use [Search](/docs/api-reference/amazon/search) when you have words. - **`categoryId` is a browse node** (`679416011`). Get one from [Categories](/docs/api-reference/amazon/categories); every node below department level lists products. - The response has no `query` key and otherwise looks exactly like a search response. --- # Categories > List one level of Amazon's category tree: the departments, or the children of one category. Source: https://apinoa.com/en/docs-raw/api-reference/amazon/categories ```http GET | POST /v1/amazon/categories ``` Send parameters as a query string with `GET`, or as a JSON body with `POST`. Both are equivalent. ## Request ```bash tab="cURL" curl "https://gateway.apinoa.com/v1/amazon/categories" \ -H "x-api-key: $APINOA_API_KEY" ``` ```js tab="JavaScript" const res = await fetch("https://gateway.apinoa.com/v1/amazon/categories", { headers: { "x-api-key": process.env.APINOA_API_KEY }, }); const { success, data, error } = await res.json(); ``` ```python tab="Python" import os import requests res = requests.get( "https://gateway.apinoa.com/v1/amazon/categories", headers={"x-api-key": os.environ["APINOA_API_KEY"]}, ) body = res.json() ``` ## Parameters | Parameter | Type | Required | Notes | |---|---|---|---| | `categoryId` | string | | Omit for the 40 departments; give a browse node (`172541`) or a department slug (`electronics`) to list that category's children. This is the one endpoint that takes either kind of id. | | `region` | enum | | Which Amazon marketplace to query, by buyer country: US, CA, MX, BR, FR, IT, ES, NL, SE, TR, AE, SA, IN, JP, SG, AU. Defaults to US. Prices come back in that marketplace's own currency (JP in JPY, FR in EUR). `US` `CA` `MX` `BR` `FR` `IT` `ES` `NL` `SE` `TR` `AE` `SA` `IN` `JP` `SG` `AU` | The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json ## Response Returns [`CategoryList`](/docs/api-reference/marketplaces/response-schema#categorylist) in `data`. A real response, shortened: arrays keep their first entries and long text is cut. ```json { "success": true, "data": { "marketplace": "amazon", "path": [], "categories": [ { "id": "amazon-devices", "name": "Amazon Devices & Accessories", "browsable": false }, { "id": "amazon-renewed", "name": "Amazon Renewed", "browsable": false } ] } } ``` ## Errors | Status | Code | Meaning | |---|---|---| | 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. | | 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. | | 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. | | 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. | | 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. | Every code is listed in [Errors](/docs/errors). Failed calls are not billed. ## Pricing **Price** $0.001 per call. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day. ## Notes - **Omit `categoryId` for the departments.** Pass one to list its children, and walk down. - **Two kinds of id.** A department is a store slug (`electronics`); everything below it is a numeric browse node (`172541`). Departments come back with `"browsable": false` because [Browse](/docs/api-reference/amazon/browse) needs a node. Descend one level and you have ids it accepts. - Below the departments, `browsable` is not reported and defaults to yes; a minority of nodes list nothing (Camera & Photo is one). - The response carries `path` (the ancestors, leaf last) and `categories`. --- # Product > Get one Amazon product by ASIN: price, variants, images, specifications, delivery and the competing-offer summary. Source: https://apinoa.com/en/docs-raw/api-reference/amazon/product ```http GET | POST /v1/amazon/product ``` Send parameters as a query string with `GET`, or as a JSON body with `POST`. Both are equivalent. ## Request ```bash tab="cURL" curl "https://gateway.apinoa.com/v1/amazon/product?id=B07F3FFTMK" \ -H "x-api-key: $APINOA_API_KEY" ``` ```js tab="JavaScript" const params = new URLSearchParams({ id: "B07F3FFTMK" }); const res = await fetch(`https://gateway.apinoa.com/v1/amazon/product?${params}`, { headers: { "x-api-key": process.env.APINOA_API_KEY }, }); const { success, data, error } = await res.json(); ``` ```python tab="Python" import os import requests res = requests.get( "https://gateway.apinoa.com/v1/amazon/product", params={"id": "B07F3FFTMK"}, headers={"x-api-key": os.environ["APINOA_API_KEY"]}, ) body = res.json() ``` ## Parameters | Parameter | Type | Required | Notes | |---|---|---|---| | `id` | string | yes | The marketplace's own product id, as carried by a search item. | | `include` | string | | `offers` · `offers:5` · `offers:all` — the enumerated competing-seller offers, which are fetched separately and so opt-in. Depth counts pages of offers (10 offers each) and each page is charged; `all` walks to the end, capped at 30 pages. | | `region` | enum | | Which Amazon marketplace to query, by buyer country: US, CA, MX, BR, FR, IT, ES, NL, SE, TR, AE, SA, IN, JP, SG, AU. Defaults to US. Prices come back in that marketplace's own currency (JP in JPY, FR in EUR). `US` `CA` `MX` `BR` `FR` `IT` `ES` `NL` `SE` `TR` `AE` `SA` `IN` `JP` `SG` `AU` | The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json ## Response Returns [`Product`](/docs/api-reference/marketplaces/response-schema#searchitem-and-product) in `data`. A real response, shortened: arrays keep their first entries and long text is cut. ```json { "success": true, "data": { "marketplace": "amazon", "id": "B07F3FFTMK", "url": "https://www.amazon.com/dp/B07F3FFTMK", "title": "Mens Merino Wool Crew Socks Thermal and Warm Sock…", "groupId": "B0BGFR76CF", "deliveryTo": { "label": "Edison 08817", "country": "US", "postalCode": "08817" }, "features": [ "MAKE HIKING A COMFORTABLE EXPERIENCE These Merino…" ], "brand": "Alvada", "status": "In Stock", "inStock": true, "unitPrice": { "price": { "currency": "USD", "amount": 4.5 }, "unit": "count" }, "reviewAspects": [ { "name": "quality", "mentions": 4534, "positive": 3976, "negative": 558 } ], "boxContents": [], "returnsPolicy": "FREE 30-day refund/replacement", "brandUrl": "https://www.amazon.com/stores/Alvada/page/C127B0C…", "promotions": [], "fulfilledBy": "Amazon", "delivery": { "estimate": "FREE delivery Thursday, October 29 on orders ship…", "free": true, "minimumOrder": { "currency": "USD", "amount": 35 }, "condition": "on orders shipped by Amazon over $35", "minDays": 5, "maxDays": 5 }, "shippingOptions": [ { "estimate": "FREE delivery Thursday, October 29 on orders ship…", "free": true, "minimumOrder": { "currency": "USD", "amount": 35 }, "condition": "on orders shipped by Amazon over $35", "minDays": 5, "maxDays": 5 } ], "badges": [ "Amazon's Choice" ], "salesLabel": "10K+ bought in past month", "salesCount": 10000, "media": { "images": [ "https://m.media-amazon.com/images/I/91aOC3G1W6L._…" ], "videos": [ { "url": "https://m.media-amazon.com/images/S/vse-vms-trans…", "thumbnailUrl": "https://m.media-amazon.com/images/I/A16M+pgi4GL.S…", "title": "Alvada wool socks: exceptional warmth and quality…", "duration": "00:14" } ] }, "price": { "currency": "USD", "amount": 13.49 }, "listPrice": { "currency": "USD", "amount": 14.99 }, "rating": { "average": 4.5, "count": 37581, "grouped": true, "distribution": { "1": 4, "2": 2, "3": 5, "4": 11, "5": 78 } }, "category": [ { "id": "7141123011", "name": "Clothing, Shoes & Jewelry" } ], "seller": { "name": "Alvada", "id": "A12H3CME8AOYX8", "url": "https://www.amazon.com/gp/help/seller/at-a-glance…" }, "attributes": [ { "name": "Fabric type", "value": "80% Merino Wool, 15% Polyester, 4% Nylon, 1% Span…" } ], "options": [ { "id": "size_name", "name": "Size", "values": [ { "id": "size_name:Small-Medium", "name": "Small-Medium" } ] } ], "variants": [ { "id": "B0GKGJZ323", "productId": "B0GKGJZ323", "selections": [ { "optionId": "size_name", "valueId": "size_name:Medium-Large", "optionName": "Size", "valueName": "Medium-Large" } ] } ], "offers": [ { "price": { "currency": "USD", "amount": 13.49 }, "listPrice": { "currency": "USD", "amount": 14.99 }, "seller": { "name": "Alvada", "id": "A12H3CME8AOYX8", "url": "https://www.amazon.com/gp/help/seller/at-a-glance…" }, "fulfilledBy": "Amazon", "status": "In Stock", "inStock": true, "delivery": { "estimate": "FREE delivery Thursday, October 29 on orders ship…", "free": true, "minimumOrder": { "currency": "USD", "amount": 35 }, "condition": "on orders shipped by Amazon over $35", "minDays": 5, "maxDays": 5 } } ], "reviews": [], "identifiers": { "gtin": "753610373339", "mpn": "43528-95629", "model": "Merino Wool Socks" }, "reviewSummary": "Customers find these wool socks to be of good qua…", "listedAt": "2022-08-10" } } ``` ## Errors | Status | Code | Meaning | |---|---|---| | 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. | | 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. | | 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. | | 404 | `PRODUCT_NOT_FOUND` | The marketplace has no such product. | | 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. | | 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. | Every code is listed in [Errors](/docs/errors). Failed calls are not billed. ## Pricing **Price** $0.002 per call, plus $0.001 for each extra call it makes — at most 30 more, so $0.032 in total. Only `include=offers` adds them: one unit per page of ten offers, up to 30 pages. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day. ## Notes - **A search item's `id` is the ASIN**, so search chains straight into product. Pass the same `region` you searched with: an ASIN is looked up on that marketplace, in its currency. - **`include=offers` lists competing sellers.** The product always carries a roll-up (`offersSummary`, e.g. "New & Used (305) from $2.07"). The full list is opt-in and billed per page of ten offers, up to 30 pages: | `include` | Price | |---|---| | omitted | $0.002 | | `offers` | $0.003 | | `offers:5` | up to $0.007 | | `offers:all` | up to $0.032 (30 pages) | A deeper request is clamped, not refused. The response's `ext.included` says which expansions were actually fetched. --- # Shopify > Shopify stores through the shared marketplace schema: search across stores, list one store's catalog, and read a product and its reviews. Source: https://apinoa.com/en/docs-raw/api-reference/shopify ## Operations | Operation | Endpoint | Returns | Price | |---|---|---|---| | [Search](/docs/api-reference/shopify/search) | `GET \| POST /v1/shopify/search` | `SearchResult` | $0.001 | | [Image Search](/docs/api-reference/shopify/image-search) | `POST /v1/shopify/image-search` | `SearchResult` | $0.001 | | [Catalog](/docs/api-reference/shopify/catalog) | `GET \| POST /v1/shopify/catalog` | `SearchResult` | $0.001 | | [Product](/docs/api-reference/shopify/product) | `GET \| POST /v1/shopify/product` | `Product` | $0.001 | | [Reviews](/docs/api-reference/shopify/reviews) | `GET \| POST /v1/shopify/reviews` | `ReviewPage` | $0.001–$0.007 | **No `browse`.** The other marketplaces let you list a category with no keyword; Shopify cannot. Shopify search requires `query`. And there is no category to browse *by*: Shopify is millions of independent stores rather than one taxonomy, so a collection id means something only inside a single shop. `/v1/shopify/browse` therefore 404s rather than serving an empty page that would read as "this category is out of stock". To list **one store's** products — its whole catalog or one collection — use [`catalog`](/docs/api-reference/shopify/catalog). **No `categories` either.** It exists to hand you ids for `browse`; with no `browse` there is nothing for them to do, and no cross-merchant tree to build them from. Shopify is not one store. Search results span many independent shops; `catalog` lists one shop's products, and `product` and `reviews` address one shop's product. **Shopify search is always live.** ## The ID Format Shopify ids are composite — `shop/handle`: ``` us.giesswein.com/wool-sneaker-women ``` A search item's `id` is already in this form, so search chains straight into `product` and `reviews` with no store-discovery step. A full product URL is accepted too. ## What Actually Arrives **search items** | Field | Fill | |---|---| | `title` | ~99% | | `price` `url` | ~95% | | `imageUrl` `seller` | ~90% | | `rating` | ~85% | **product** | Field | Fill | |---|---| | `title` `price` `brand` | ~100% | | `inStock` `variants` `seller` `media.images` | ~98% | | `options` | ~60–78% | `seller` is effectively always present because it is derived from the shop host. `options` is lower because single-variant products carry a placeholder axis that is excluded rather than reported. Shopify is the densest marketplace here for `brand`, `inStock` and `variants`, and the thinnest for the detail-page extras Amazon carries — there is no `reviewSummary`, `reviewAspects` or `offers`. ## Things Specific to Shopify **Variants are not separately fetchable.** A colour is a SKU inside one product, not its own product, so `variant.productId` is absent — you already have everything after one call. **Results span many merchants.** Two items in one search result can come from two unrelated shops with different shipping, returns and currencies. `seller` tells you which. **Shipping is not priced until checkout**, so `delivery.cost` is absent. That is "not stated", not free. --- # Search > Search products across Shopify stores by keyword, with price, rating, condition and stock filters. Source: https://apinoa.com/en/docs-raw/api-reference/shopify/search ```http GET | POST /v1/shopify/search ``` Send parameters as a query string with `GET`, or as a JSON body with `POST`. Both are equivalent. ## Request ```bash tab="cURL" curl "https://gateway.apinoa.com/v1/shopify/search?query=running+shoes" \ -H "x-api-key: $APINOA_API_KEY" ``` ```js tab="JavaScript" const params = new URLSearchParams({ query: "running shoes" }); const res = await fetch(`https://gateway.apinoa.com/v1/shopify/search?${params}`, { headers: { "x-api-key": process.env.APINOA_API_KEY }, }); const { success, data, error } = await res.json(); ``` ```python tab="Python" import os import requests res = requests.get( "https://gateway.apinoa.com/v1/shopify/search", params={"query": "running shoes"}, headers={"x-api-key": os.environ["APINOA_API_KEY"]}, ) body = res.json() ``` ## Parameters | Parameter | Type | Required | Notes | |---|---|---|---| | `query` | string | yes | Keywords to search for. Required: Shopify search always needs a keyword, which is also why Shopify has no keyword-less `browse`. | | `categoryId` | string | | Narrow to one category, by the marketplace's own id. Opaque and per-marketplace; take it from Product.category[].id or from a result's `facets`. | | `condition` | enum | | Item condition: `new` or `used`. `refurbished` is refused by name rather than folded into `used` — the catalog sells new and secondhand, and those are different claims. `new` `used` | | `cursor` | string | | Opaque; comes from the previous response and carries the page index. Shopify pages by cursor rather than by number — this is the only paging knob, and `page` is refused by name. | | `inStock` | boolean \| enum | | Only listings the marketplace reports as purchasable now. | | `priceMax` | number ≥ 0 | | Highest price to return, in decimal major units. | | `priceMin` | number ≥ 0 | | Lowest price to return, in decimal major units. | | `ratingMin` | number 0–5 | | Lowest average star rating to return, on the marketplace's own 0-5 scale. | | `region` | string | | Buyer country as ISO 3166-1 alpha-2; sets availability, shipping and price. | The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json ## Response Returns [`SearchResult`](/docs/api-reference/marketplaces/response-schema#searchresult) in `data`. A real response, shortened: arrays keep their first entries and long text is cut. ```json { "success": true, "data": { "marketplace": "shopify", "query": "running shoes", "page": { "index": 1, "size": 50, "total": 235 }, "items": [ { "marketplace": "shopify", "id": "startfitness.co.uk/adidas-adizero-evo-sl-mens-running-shoes-white", "attributes": [ { "name": "Weight", "value": "224 g ± 9 g (size UK 8.5)" }, { "name": "Midsole Drop", "value": "6 mm" } ], "title": "adidas Adizero EVO SL Mens Running Shoes - White", "url": "https://startfitness.co.uk/products/adidas-adizero-evo-sl-mens-running-shoes-white?variant=44383311691984", "price": { "currency": "USD", "amount": 171 }, "rating": { "average": 4.8 }, "seller": { "id": "startfitness.co.uk", "name": "Start Fitness", "url": "https://startfitness.co.uk" }, "inStock": true, "imageUrl": "https://cdn.shopify.com/s/files/1/0564/9521/0704/files/adidas-Adizero-Evo-SL-JH6206.jpg", "badges": [], "promotions": [] } ], "cursor": "eyJjIjoiZXlKdlptWnpaWFFpT2pVd0xDSjBiM1JoYkY5amIzVnVkQ0k2TWpNMUxDSnlaWFIxY201bFpDSTZOVEI5IiwiaSI6MiwiaCI6IkhnMUNENHV3REJ…", "facets": [] } } ``` ## Errors | Status | Code | Meaning | |---|---|---| | 400 | `INVALID_CURSOR` | The `cursor` is not one this operation returned. Start again without it. | | 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. | | 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. | | 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. | | 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. | | 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. | Every code is listed in [Errors](/docs/errors). Failed calls are not billed. ## Pricing **Price** $0.001 per call. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day. ## Notes - **Results span many independent stores.** Each item's `id` is `shop/handle` and chains into [Product](/docs/api-reference/shopify/product) and [Reviews](/docs/api-reference/shopify/reviews). - **Paging is by `cursor`.** Pass the previous response's `cursor`; `page` is refused. Every page holds up to 50 items, so there is no `pageSize`. - **No `sort`, no `brands`.** Shopify search offers neither. - **No `currency`.** Prices follow `region`. - **`condition=refurbished` is refused** rather than mapped onto `used`: the catalog distinguishes `new` and `secondhand` only. --- # Image Search > Find products across Shopify stores from a photo, optionally narrowed by keyword. Source: https://apinoa.com/en/docs-raw/api-reference/shopify/image-search ```http POST /v1/shopify/image-search ``` `POST` only, with a JSON body. ## Request ```bash tab="cURL" curl -X POST "https://gateway.apinoa.com/v1/shopify/image-search" \ -H "x-api-key: $APINOA_API_KEY" \ -H "content-type: application/json" \ -d "{\"image\": {\"contentType\": \"image/jpeg\", \"data\": \"$(base64 < photo.jpg | tr -d '\n')\"}}" ``` ```js tab="JavaScript" import { readFile } from "node:fs/promises"; const data = (await readFile("photo.jpg")).toString("base64"); const res = await fetch("https://gateway.apinoa.com/v1/shopify/image-search", { method: "POST", headers: { "x-api-key": process.env.APINOA_API_KEY, "content-type": "application/json" }, body: JSON.stringify({ image: { contentType: "image/jpeg", data } }), }); const { success, data: result, error } = await res.json(); ``` ```python tab="Python" import base64 import os import requests with open("photo.jpg", "rb") as f: data = base64.b64encode(f.read()).decode() res = requests.post( "https://gateway.apinoa.com/v1/shopify/image-search", json={"image": {"contentType": "image/jpeg", "data": data}}, headers={"x-api-key": os.environ["APINOA_API_KEY"]}, ) body = res.json() ``` ## Parameters | Parameter | Type | Required | Notes | |---|---|---|---| | `image` | object | yes | Base64 image input for visual search; the image is the anchor and `query` narrows it — the reverse of `search`. Up to ~6MB of base64; supported types are jpeg, png, webp, gif and avif. | | `categoryId` | string | | Narrow to one category, by the marketplace's own id. Opaque and per-marketplace; take it from Product.category[].id or from a result's `facets`. | | `condition` | enum | | Item condition: `new` or `used`. `refurbished` is refused by name rather than folded into `used` — the catalog sells new and secondhand, and those are different claims. `new` `used` | | `cursor` | string | | Opaque; comes from the previous response and carries the page index. Shopify pages by cursor rather than by number — this is the only paging knob, and `page` is refused by name. | | `inStock` | boolean \| enum | | Only listings the marketplace reports as purchasable now. | | `priceMax` | number ≥ 0 | | Highest price to return, in decimal major units. | | `priceMin` | number ≥ 0 | | Lowest price to return, in decimal major units. | | `query` | string | | Optional text that refines the image — the reverse of `search`, where the query anchors the result. For example, a sneaker photo with `query=wool` returns wool sneakers rather than any sneaker of that shape. | | `ratingMin` | number 0–5 | | Lowest average star rating to return, on the marketplace's own 0-5 scale. | | `region` | string | | Buyer country as ISO 3166-1 alpha-2; sets availability, shipping and price. | The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json ## Response Returns [`SearchResult`](/docs/api-reference/marketplaces/response-schema#searchresult) in `data`. A real response, shortened: arrays keep their first entries and long text is cut. ```json { "success": true, "data": { "marketplace": "shopify", "page": { "index": 1, "size": 50, "total": 403 }, "items": [ { "marketplace": "shopify", "id": "killcrew.co/flame-high-socks-black-orange", "attributes": [ { "name": "Material", "value": "Cotton blend" }, { "name": "Length", "value": "Crew high" } ], "title": "FLAME HIGH SOCKS - BLACK / ORANGE", "url": "https://killcrew.co/products/flame-high-socks-black-orange?variant=40760148623458", "price": { "currency": "USD", "amount": 15 }, "rating": { "average": 4.8 }, "seller": { "id": "killcrew.co", "name": "Kill Crew", "url": "https://killcrew.co" }, "inStock": true, "imageUrl": "https://cdn.shopify.com/s/files/1/1955/8565/files/FLAMEHIGHSOCKS-BLACK_ORANGE.jpg", "badges": [], "promotions": [] } ], "cursor": "eyJjIjoiZXlKdlptWnpaWFFpT2pVd0xDSjBiM1JoYkY5amIzVnVkQ0k2TkRBekxDSnlaWFIxY201bFpDSTZOVEI5IiwiaSI6MiwiaCI6IjdOa2tvb1l6bjV…", "facets": [] } } ``` ## Errors | Status | Code | Meaning | |---|---|---| | 400 | `INVALID_CURSOR` | The `cursor` is not one this operation returned. Start again without it. | | 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. | | 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. | | 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. | | 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. | | 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. | Every code is listed in [Errors](/docs/errors). Failed calls are not billed. ## Pricing **Price** $0.001 per call. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day. ## Notes - **POST only.** The image is sent as base64 in a JSON body. - **The image is the anchor and `query` narrows it.** A wool-sneaker photo alone returns anything of that shape; the word "wool" alone returns anything made of wool; together they return wool sneakers. - **Images up to about 6 MB of base64,** as JPEG, PNG, WebP, GIF or AVIF. - Same filters and paging as [Search](/docs/api-reference/shopify/search). --- # Catalog > List every product of one Shopify store, or of one of its collections. Source: https://apinoa.com/en/docs-raw/api-reference/shopify/catalog ```http GET | POST /v1/shopify/catalog ``` Send parameters as a query string with `GET`, or as a JSON body with `POST`. Both are equivalent. ## Request ```bash tab="cURL" curl "https://gateway.apinoa.com/v1/shopify/catalog?id=thedoodlebot.com" \ -H "x-api-key: $APINOA_API_KEY" ``` ```js tab="JavaScript" const params = new URLSearchParams({ id: "thedoodlebot.com" }); const res = await fetch(`https://gateway.apinoa.com/v1/shopify/catalog?${params}`, { headers: { "x-api-key": process.env.APINOA_API_KEY }, }); const { success, data, error } = await res.json(); ``` ```python tab="Python" import os import requests res = requests.get( "https://gateway.apinoa.com/v1/shopify/catalog", params={"id": "thedoodlebot.com"}, headers={"x-api-key": os.environ["APINOA_API_KEY"]}, ) body = res.json() ``` ## Parameters | Parameter | Type | Required | Notes | |---|---|---|---| | `id` | string | yes | The store: its host (comfrt.com) or any URL on it — a product, collection or home page link. | | `collection` | string | | Only this collection: its handle (best-sellers) or its URL. Omitted means the whole store — unless `id` is itself a collection page link, which lists that collection. | | `currency` | string | | Presentment currency, ISO 4217. | | `cursor` | string | | The previous page's `cursor`. Pass it for the next page; it also carries the store's total forward. | | `page` | integer ≥ 1 | | 1-based page number. | | `pageSize` | integer 1–250 | | Products per page, 1–250 (default 50). | | `region` | string | | Buyer country as ISO 3166-1 alpha-2; sets availability, shipping and price. | The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json ## Response Returns [`SearchResult`](/docs/api-reference/marketplaces/response-schema#searchresult) in `data`. A real response, shortened: arrays keep their first entries and long text is cut. ```json { "success": true, "data": { "marketplace": "shopify", "page": { "index": 1, "size": 4, "total": 4 }, "items": [ { "marketplace": "shopify", "id": "thedoodlebot.com/doodle-bot-1", "url": "https://thedoodlebot.com/products/doodle-bot-1", "title": "Doodle Bot", "price": { "currency": "USD", "amount": 60 }, "listPrice": { "currency": "USD", "amount": 120 }, "brand": "Doodle Bot", "listedAt": "2025-11-11T21:04:21.000Z", "inStock": true, "description": "
Doodle Bot is the fun, interactive robot that helps ki…",
"badges": [],
"promotions": [],
"features": [],
"media": {
"images": [
"https://cdn.shopify.com/s/files/1/0785/4070/1921/files/01803622-2C47-4C92-AB09-18AF6FB97168.jpg?v=1762895019",
"https://cdn.shopify.com/s/files/1/0785/4070/1921/files/530150FC-A189-4EE9-8A39-6775D4EE9A5D.jpg?v=1762895186"
],
"videos": []
},
"category": [],
"attributes": [],
"options": [
{
"id": "1",
"name": "Color",
"values": [
{
"id": "BLUE",
"name": "BLUE",
"imageUrl": "https://cdn.shopify.com/s/files/1/0785/4070/1921/files/530150FC-A189-4EE9-8A39-6775D4EE9A5D.jpg?v=1762895186"
},
{
"id": "PINK",
"name": "PINK",
"imageUrl": "https://cdn.shopify.com/s/files/1/0785/4070/1921/files/01803622-2C47-4C92-AB09-18AF6FB97168.jpg?v=1762895019"
}
]
}
],
"variants": [
{
"id": "47433841967329",
"price": {
"currency": "USD",
"amount": 60
},
"listPrice": {
"currency": "USD",
"amount": 120
},
"inStock": true,
"selections": [
{
"optionId": "1",
"valueId": "BLUE",
"optionName": "Color",
"valueName": "BLUE"
}
]
},
{
"id": "47433842000097",
"price": {
"currency": "USD",
"amount": 60
},
"listPrice": {
"currency": "USD",
"amount": 120
},
"inStock": true,
"selections": [
{
"optionId": "1",
"valueId": "PINK",
"optionName": "Color",
"valueName": "PINK"
}
]
}
],
"offers": [],
"reviewAspects": [],
"boxContents": [],
"reviews": [],
"seller": {
"id": "thedoodlebot.com",
"url": "https://thedoodlebot.com",
"name": "Doodle Bot",
"country": "US"
},
"imageUrl": "https://cdn.shopify.com/s/files/1/0785/4070/1921/files/01803622-2C47-4C92-AB09-18AF6FB97168.jpg?v=1762895019"
}
]
}
}
```
## Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | `INVALID_CURSOR` | The `cursor` is not one this operation returned. Start again without it. |
| 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. |
| 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. |
| 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. |
| 404 | `NOT_FOUND` | The store has no such collection. |
| 404 | `NOT_SHOPIFY_STORE` | The site is not a Shopify store. |
| 404 | `STORE_CLOSED` | The store is password-protected or closed. |
| 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. |
| 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. |
Every code is listed in [Errors](/docs/errors). Failed calls are not billed.
## Pricing
**Price** $0.001 per call. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day.
## Notes
- **`id` is the store's host or any link on it:** a product page, a collection page or the home page. A collection page link lists that collection; add `collection` to narrow any other link to one.
- **Each item is the full product,** the same object [Product](/docs/api-reference/shopify/product) returns for that handle (every variant, option, image and the description), plus `imageUrl`, the main image.
- **`page.total` is the store's product count,** on every page, so the first page tells you whether a store is worth walking.
- **Follow the `cursor`, not the item count.** A page can hold fewer products than `pageSize` and still not be the last: Shopify leaves unlisted products out of a page rather than filling the gap. For the same reason a full walk can come to slightly fewer products than `page.total`.
---
# Product
> Get one Shopify product from its store: price, every variant, options, images and description.
Source: https://apinoa.com/en/docs-raw/api-reference/shopify/product
```http
GET | POST /v1/shopify/product
```
Send parameters as a query string with `GET`, or as a JSON body with `POST`. Both are equivalent.
## Request
```bash tab="cURL"
curl "https://gateway.apinoa.com/v1/shopify/product?id=thedoodlebot.com%2Fdoodle-bot" \
-H "x-api-key: $APINOA_API_KEY"
```
```js tab="JavaScript"
const params = new URLSearchParams({ id: "thedoodlebot.com/doodle-bot" });
const res = await fetch(`https://gateway.apinoa.com/v1/shopify/product?${params}`, {
headers: { "x-api-key": process.env.APINOA_API_KEY },
});
const { success, data, error } = await res.json();
```
```python tab="Python"
import os
import requests
res = requests.get(
"https://gateway.apinoa.com/v1/shopify/product",
params={"id": "thedoodlebot.com/doodle-bot"},
headers={"x-api-key": os.environ["APINOA_API_KEY"]},
)
body = res.json()
```
## Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
| `id` | string | yes | A Shopify id rather than a global identifier: the composite `shop/handle`, e.g. us.giesswein.com/wool-sneaker-women. A search item's `id` is already in this form, and a full product URL is accepted too. |
| `currency` | string | | Presentment currency, ISO 4217. |
| `region` | string | | Buyer country as ISO 3166-1 alpha-2; sets availability, shipping and price. |
The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json
## Response
Returns [`Product`](/docs/api-reference/marketplaces/response-schema#searchitem-and-product) in `data`. A real response, shortened: arrays keep their first entries and long text is cut.
```json
{
"success": true,
"data": {
"marketplace": "shopify",
"id": "thedoodlebot.com/doodle-bot",
"url": "https://thedoodlebot.com/products/doodle-bot",
"title": "Doodle Bot Limited Edition",
"price": {
"currency": "USD",
"amount": 80
},
"listPrice": {
"currency": "USD",
"amount": 140
},
"brand": "Doodle Bot",
"listedAt": "2025-10-06T02:01:18.000Z",
"inStock": true,
"description": " Doodle Bot is the fun, interactive robot tha…",
"badges": [],
"promotions": [],
"features": [],
"media": {
"images": [
"https://cdn.shopify.com/s/files/1/0785/4070/1921/files/IMG_2729.jpg?v=1759715594",
"https://cdn.shopify.com/s/files/1/0785/4070/1921/files/IMG_2728.jpg?v=1759715764"
],
"videos": []
},
"category": [],
"attributes": [],
"options": [
{
"id": "1",
"name": "Color",
"values": [
{
"id": "PINK",
"name": "PINK"
},
{
"id": "GREEN",
"name": "GREEN"
}
]
}
],
"variants": [
{
"id": "47235322806497",
"price": {
"currency": "USD",
"amount": 80
},
"listPrice": {
"currency": "USD",
"amount": 140
},
"inStock": true,
"selections": [
{
"optionId": "1",
"valueId": "PINK",
"optionName": "Color",
"valueName": "PINK"
}
]
},
{
"id": "47235322839265",
"price": {
"currency": "USD",
"amount": 80
},
"listPrice": {
"currency": "USD",
"amount": 140
},
"inStock": false,
"selections": [
{
"optionId": "1",
"valueId": "GREEN",
"optionName": "Color",
"valueName": "GREEN"
}
]
}
],
"offers": [],
"reviewAspects": [],
"boxContents": [],
"reviews": [],
"seller": {
"id": "thedoodlebot.com",
"url": "https://thedoodlebot.com"
}
}
}
```
## Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. |
| 400 | `INVALID_PRODUCT_ID` | The id is not `shop/handle` or a product URL. |
| 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. |
| 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. |
| 404 | `PRODUCT_NOT_FOUND` | The marketplace has no such product. |
| 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. |
| 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. |
Every code is listed in [Errors](/docs/errors). Failed calls are not billed.
## Pricing
**Price** $0.001 per call. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day.
## Notes
- **`id` is `shop/handle`** (`thedoodlebot.com/doodle-bot`), exactly as a search or catalog item carries it. A full product URL works too.
- **`currency` pins the store's currency** (default USD). The response states the currency the store actually charged in, per price.
---
# Reviews
> Get a Shopify product's reviews from the review app its store uses.
Source: https://apinoa.com/en/docs-raw/api-reference/shopify/reviews
```http
GET | POST /v1/shopify/reviews
```
Send parameters as a query string with `GET`, or as a JSON body with `POST`. Both are equivalent.
## Request
```bash tab="cURL"
curl "https://gateway.apinoa.com/v1/shopify/reviews?id=www.orthofeet.com%2Fcoral-black" \
-H "x-api-key: $APINOA_API_KEY"
```
```js tab="JavaScript"
const params = new URLSearchParams({ id: "www.orthofeet.com/coral-black" });
const res = await fetch(`https://gateway.apinoa.com/v1/shopify/reviews?${params}`, {
headers: { "x-api-key": process.env.APINOA_API_KEY },
});
const { success, data, error } = await res.json();
```
```python tab="Python"
import os
import requests
res = requests.get(
"https://gateway.apinoa.com/v1/shopify/reviews",
params={"id": "www.orthofeet.com/coral-black"},
headers={"x-api-key": os.environ["APINOA_API_KEY"]},
)
body = res.json()
```
## Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
| `id` | string | yes | The composite Shopify `shop/handle` — the same id `product` takes, not a global identifier. This is the one Shopify operation whose cost varies: a first page can cost more than 1, while a page fetched with `cursor` costs 1. |
| `cursor` | string | | Opaque cursor from the previous response; the only paging knob that works on every shop, and it wins if `page` is sent too. A page fetched with a cursor costs 1. |
| `page` | integer | | 1-based starting page. Not every shop supports jumping to a page number; `cursor` works on every shop, so prefer it for paging. |
The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json
## Response
Returns [`ReviewPage`](/docs/api-reference/marketplaces/response-schema#reviewpage) in `data`. A real response, shortened: arrays keep their first entries and long text is cut.
```json
{
"success": true,
"data": {
"marketplace": "shopify",
"productId": "www.orthofeet.com/coral-black",
"page": {
"index": 1,
"size": 30,
"total": 990
},
"reviews": [
{
"id": "df4c25ec-c5c8-41e5-8fd2-8c72cec3ab88",
"author": "Karen Michel",
"rating": 3,
"title": "Not as expected",
"body": "I needed good shoes to take on a tour in italy. I used these shoes thinking they would support my feet. But they did no…",
"date": "2026-10-24T15:13:07.824Z",
"verified": true,
"helpfulCount": 0,
"variant": "Coral - Beige Leopard",
"images": []
},
{
"id": "0788f651-a9a4-4c14-9c4d-25ca08b88951",
"author": "Anna Prigozhin",
"rating": 5,
"body": "Coral - White",
"date": "2026-10-24T15:05:48.452Z",
"verified": true,
"helpfulCount": 0,
"variant": "Coral - White",
"images": []
}
],
"summary": {
"average": 4.1,
"count": 38998,
"distribution": {
"1": 11,
"2": 6,
"3": 9,
"4": 10,
"5": 64
}
},
"grouped": true,
"cursor": "eyJhIjoianVkZ2VtZSIsImkiOnsibXlzaG9waWZ5Ijoib3J0aG9mZWV0Lm15c2hvcGlmeS5jb20iLCJwcm9kdWN0SWQiOiI2NzAyNDYxNzQ4OCJ9LCJjIjo…"
}
}
```
## Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | `INVALID_CURSOR` | The `cursor` is not one this operation returned. Start again without it. |
| 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. |
| 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. |
| 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. |
| 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. |
| 501 | `REVIEWS_UNREACHABLE` | The store shows reviews, but none could be read. |
| 501 | `REVIEW_APP_UNSUPPORTED` | The store's reviews come from an app this API does not read yet. |
| 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. |
Every code is listed in [Errors](/docs/errors). Failed calls are not billed.
## Pricing
**Price** $0.001 per call, plus $0.001 for each extra call it makes — at most 6 more, so $0.007 in total. A first page usually costs one unit more than the base; a `cursor` page costs only the base. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day.
## Notes
- **Use `cursor` for the next page.** `page` is not supported on every store; `cursor` always works. A first page usually costs one unit more than the base; a `cursor` page costs only the base.
- **No reviews is an answer.** A product with none returns `200` with `reviews: []` and `page.total: 0`, and the zero is only sent when it is really zero. Star ratings without text count as zero reviews.
- **A 501 means the reviews exist but could not be read** — deliberately not a 404 and not an empty list.
- **Reviews are often grouped.** A store can answer one colour's page with the whole product family's reviews. Check `grouped` before computing a per-product rating.
---
# Walmart
> Walmart US through the shared marketplace schema. Prices are in USD.
Source: https://apinoa.com/en/docs-raw/api-reference/walmart
## Operations
| Operation | Endpoint | Returns | Price |
|---|---|---|---|
| [Search](/docs/api-reference/walmart/search) | `GET \| POST /v1/walmart/search` | `SearchResult` | $0.001 |
| [Browse](/docs/api-reference/walmart/browse) | `GET \| POST /v1/walmart/browse` | `SearchResult` | $0.001 |
| [Categories](/docs/api-reference/walmart/categories) | `GET \| POST /v1/walmart/categories` | `CategoryList` | $0.001 |
| [Product](/docs/api-reference/walmart/product) | `GET \| POST /v1/walmart/product` | `Product` | $0.001 |
| [Reviews](/docs/api-reference/walmart/reviews) | `GET \| POST /v1/walmart/reviews` | `ReviewPage` | $0.001 |
No `image-search`: Walmart offers no visual search.
All five operations cost one call each, including `reviews`.
## What Actually Arrives
**search items**
| Field | Fill |
|---|---|
| `title` `url` | 100% |
| `price` `imageUrl` `seller.name` | ~85% |
| `delivery` | ~80% |
| `badges` | ~35% |
**product**
| Field | Fill |
|---|---|
| `title` | 100% |
| `price` `media.images` `attributes` | ~90% |
| `brand` `rating.average` `seller.name` `description` `category` | ~85% |
| `brandUrl` `returnsPolicy` | ~80% |
| `reviews` | ~70% |
| `reviewAspects` | ~50% |
**reviews**
| Field | Fill |
|---|---|
| `rating` | 100% |
| `id` `date` `author` `helpfulCount` | ~85% |
`price` is not 100% on search because Walmart's marketplace-seller listings sometimes show a range
rather than a price. `badges` is low because most items simply have none.
Walmart is one of only two marketplaces here that returns `reviewAspects` (Amazon is the other), and it
carries `returnsPolicy` and `brandUrl` more reliably than any of the others.
## The Filter Rail
Every Walmart search returns `facets` — the site's own filter groups for that query — and facets are
free. One socks search carries 25 groups: Price, Brand and
Customer Rating map onto typed parameters, and the rest (Sock Rise, Sleeve Length, Fabric Material,
Colour…) are reachable only through `facets`. See
[Filters](/docs/api-reference/marketplaces/filters#facets--the-marketplaces-own-filter-rail).
## Things Specific to Walmart
**Variants are not separately fetchable.** Sizes and colours are SKUs inside one product, so
`variant.productId` is absent.
**Both Walmart and marketplace sellers appear.** `seller.name` distinguishes them; a Walmart-fulfilled
item and a third-party one carry different delivery promises.
**Grocery items carry `unitPrice`.** Walmart and Amazon are the two that state price per unit of
measure, which is the only way to compare a 12-pack against a 24-pack.
---
# Search
> Search Walmart by keyword, with price, rating, brand and category filters.
Source: https://apinoa.com/en/docs-raw/api-reference/walmart/search
```http
GET | POST /v1/walmart/search
```
Send parameters as a query string with `GET`, or as a JSON body with `POST`. Both are equivalent.
## Request
```bash tab="cURL"
curl "https://gateway.apinoa.com/v1/walmart/search?query=wool+socks&priceMax=30" \
-H "x-api-key: $APINOA_API_KEY"
```
```js tab="JavaScript"
const params = new URLSearchParams({ query: "wool socks", priceMax: "30" });
const res = await fetch(`https://gateway.apinoa.com/v1/walmart/search?${params}`, {
headers: { "x-api-key": process.env.APINOA_API_KEY },
});
const { success, data, error } = await res.json();
```
```python tab="Python"
import os
import requests
res = requests.get(
"https://gateway.apinoa.com/v1/walmart/search",
params={"query": "wool socks", "priceMax": "30"},
headers={"x-api-key": os.environ["APINOA_API_KEY"]},
)
body = res.json()
```
## Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
| `query` | string | yes | Keywords to search for. Required: image search is not available on Walmart, so a keyword query is the only way to search. |
| `brands` | string list | | Brand names as the marketplace spells them, e.g. brands=Nike,Adidas. |
| `categoryId` | string | | Narrow to one category, by the marketplace's own id. Opaque and per-marketplace; take it from Product.category[].id or from a result's `facets`. |
| `facets` | string \| object list | | Narrowings taken from a result's `facets`, e.g. facets=Color:Black. The escape hatch for filters the typed vocabulary has no word for; prefer a typed parameter where one exists. |
| `page` | integer | | 1-based results page. There is no `pageSize` — the results page is server-fixed at about 50 organic items — and no `cursor`, since paging here is by number. |
| `priceMax` | number ≥ 0 | | Highest price to return, in decimal major units. |
| `priceMin` | number ≥ 0 | | Lowest price to return, in decimal major units. |
| `ratingMin` | number 0–5 | | Lowest average star rating to return, on the marketplace's own 0-5 scale. |
| `region` | string | | Buyer country as ISO 3166-1 alpha-2; sets availability, shipping and price. |
| `sort` | enum | | Result ordering: `best_match`, `price_asc`, `price_desc`, `newly_listed` or `best_seller`. Defaults to `best_match`, Walmart's own relevance ordering. `best_match` `price_asc` `price_desc` `newly_listed` `best_seller` |
The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json
## Response
Returns [`SearchResult`](/docs/api-reference/marketplaces/response-schema#searchresult) in `data`. A real response, shortened: arrays keep their first entries and long text is cut.
```json
{
"success": true,
"data": {
"marketplace": "walmart",
"query": "wool socks",
"page": {
"index": 1,
"size": 47,
"total": 4888
},
"items": [
{
"marketplace": "walmart",
"id": "344144554",
"title": "Genuine Dickies Men's Wool Thermal Crew Sock, 3 Pack, Shoe Size 6-12",
"price": {
"currency": "USD",
"amount": 15.98
},
"brand": "Dickies",
"url": "https://www.walmart.com/ip/Genuine-Dickies-Men-s-Wool-Thermal-Crew-Sock-3-Pack-Shoe-Size-6-12/344144554",
"imageUrl": "https://i5.walmartimages.com/seo/Genuine-Dickies-Men-s-Wool-Thermal-Crew-Sock-3-Pack-Shoe-Size-6-12_7f83249a-fb07-4602-…",
"seller": {
"name": "Walmart.com",
"id": "F55CDC31AB754BB68FE0B39041159D63"
},
"rating": {
"average": 4.7,
"count": 552
},
"status": "In stock",
"inStock": true,
"sponsored": true,
"category": [
{
"id": "5438",
"name": "Clothing"
},
{
"id": "133197",
"name": "Mens Clothing"
}
],
"badges": [],
"promotions": [],
"attributes": []
}
],
"facets": [
{
"id": "suggested",
"name": "Suggested",
"kind": "select",
"values": [
{
"value": "Merino Wool",
"name": "Merino Wool"
},
{
"value": "Crew",
"name": "Crew"
}
]
},
{
"id": "brand",
"name": "Brand",
"kind": "select",
"values": [
{
"value": "Loritta",
"name": "Loritta"
},
{
"value": "Wrangler",
"name": "Wrangler"
}
],
"maps_to": "brands"
}
]
}
}
```
## Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. |
| 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. |
| 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. |
| 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. |
| 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. |
Every code is listed in [Errors](/docs/errors). Failed calls are not billed.
## Pricing
**Price** $0.001 per call. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day.
## Notes
- **`ratingMin` snaps to a band.** Walmart filters by rating band ("4 – 5 Stars", "3 – 3.9 Stars"), so a request picks the lowest band that still satisfies it: `3.5` selects 4–5, and `4.5` sends no rating filter because no band can satisfy it.
- **Paging is by `page`.** The page size is fixed at about 50 items, so there is no `pageSize` and no `cursor`.
- **Not supported:** `condition`, `freeShipping`, `inStock`.
---
# Browse
> List the products in one Walmart category, with no keyword.
Source: https://apinoa.com/en/docs-raw/api-reference/walmart/browse
```http
GET | POST /v1/walmart/browse
```
Send parameters as a query string with `GET`, or as a JSON body with `POST`. Both are equivalent.
## Request
```bash tab="cURL"
curl "https://gateway.apinoa.com/v1/walmart/browse?categoryId=3944_133251_1095191_1230614_1230478&ratingMin=4" \
-H "x-api-key: $APINOA_API_KEY"
```
```js tab="JavaScript"
const params = new URLSearchParams({ categoryId: "3944_133251_1095191_1230614_1230478", ratingMin: "4" });
const res = await fetch(`https://gateway.apinoa.com/v1/walmart/browse?${params}`, {
headers: { "x-api-key": process.env.APINOA_API_KEY },
});
const { success, data, error } = await res.json();
```
```python tab="Python"
import os
import requests
res = requests.get(
"https://gateway.apinoa.com/v1/walmart/browse",
params={"categoryId": "3944_133251_1095191_1230614_1230478", "ratingMin": "4"},
headers={"x-api-key": os.environ["APINOA_API_KEY"]},
)
body = res.json()
```
## Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
| `categoryId` | string | yes | REQUIRED. The underscore-joined chain from a Walmart category URL, e.g. 3944_133251_1095191_1230614_1230478. A bare department or leaf id is refused with CATEGORY_NOT_BROWSABLE — use `categories` to walk down to a chain. |
| `brands` | string list | | Brand names as the marketplace spells them, e.g. brands=Nike,Adidas. |
| `facets` | string \| object list | | Narrowings taken from a result's `facets`, e.g. facets=Color:Black. The escape hatch for filters the typed vocabulary has no word for; prefer a typed parameter where one exists. |
| `page` | integer | | 1-based results page. There is no `pageSize` — the results page is server-fixed at about 50 organic items — and no `cursor`, since paging here is by number. |
| `priceMax` | number ≥ 0 | | Highest price to return, in decimal major units. |
| `priceMin` | number ≥ 0 | | Lowest price to return, in decimal major units. |
| `ratingMin` | number 0–5 | | Lowest average star rating to return, on the marketplace's own 0-5 scale. |
| `region` | string | | Buyer country as ISO 3166-1 alpha-2; sets availability, shipping and price. |
| `sort` | enum | | Result ordering: `best_match`, `price_asc`, `price_desc`, `newly_listed` or `best_seller`. Defaults to `best_match`, Walmart's own relevance ordering. `best_match` `price_asc` `price_desc` `newly_listed` `best_seller` |
The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json
## Response
Returns [`SearchResult`](/docs/api-reference/marketplaces/response-schema#searchresult) in `data`. A real response, shortened: arrays keep their first entries and long text is cut.
```json
{
"success": true,
"data": {
"marketplace": "walmart",
"page": {
"index": 1,
"size": 39,
"total": 2269
},
"items": [
{
"marketplace": "walmart",
"id": "11381374703",
"title": "Apple AirPods 4",
"price": {
"currency": "USD",
"amount": 99
},
"listPrice": {
"currency": "USD",
"amount": 129.99
},
"url": "https://www.walmart.com/ip/Apple-AirPods-4/11381374703",
"imageUrl": "https://i5.walmartimages.com/seo/Apple-AirPods-4_43ffea38-e233-4fee-8a6f-7ff92ad713ae.97d6d24546e5cbdfcdc0e2fa52c969b1.…",
"seller": {
"name": "Walmart.com",
"id": "F55CDC31AB754BB68FE0B39041159D63"
},
"rating": {
"average": 4.3,
"count": 88450
},
"status": "In stock",
"inStock": true,
"delivery": {
"carrier": "Shipping",
"code": "SHIPPING",
"estimate": "Free shipping, arrives today",
"free": true
},
"shippingOptions": [
{
"carrier": "Delivery",
"code": "DELIVERY",
"estimate": "Delivery as soon as 13 mins"
},
{
"carrier": "Shipping",
"code": "SHIPPING",
"estimate": "Free shipping, arrives today",
"free": true
}
],
"deliveryTo": {
"label": "Sacramento, CA 95829",
"country": "US",
"postalCode": "95829"
},
"offersSummary": {
"count": 10
},
"category": [
{
"id": "3944",
"name": "Electronics"
},
{
"id": "133251",
"name": "Audio"
}
],
"badges": [
"Rollback"
],
"promotions": [
"Save with Walmart+"
],
"attributes": [],
"ext": {
"optionsFromPrice": {
"currency": "USD",
"amount": 68
}
}
}
],
"facets": [
{
"id": "price",
"name": "Price",
"kind": "range",
"values": [],
"min": 0,
"max": 160,
"maps_to": "priceMin"
},
{
"id": "brand",
"name": "Brand",
"kind": "select",
"values": [
{
"value": "JLab",
"name": "JLab"
},
{
"value": "JBL",
"name": "JBL"
}
],
"maps_to": "brands"
}
]
}
}
```
## Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | `CATEGORY_NOT_BROWSABLE` | The category is a navigation hub with no products. Browse one of its children from `categories`. |
| 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. |
| 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. |
| 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. |
| 429 | `MARKETPLACE_SATURATED` | Too many concurrent calls. Retry after the `Retry-After` seconds. |
| 502 | `UPSTREAM_ERROR` | The platform answered with something unusable. Retry with backoff. |
Every code is listed in [Errors](/docs/errors). Failed calls are not billed.
## Pricing
**Price** $0.001 per call. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day.
## Notes
- **`categoryId` is the underscore-joined chain** from a Walmart category URL: `walmart.com/browse/
Quote what shipping one AliExpress item to a country costs, by every carrier, and how long it takes.
Source: https://apinoa.com/en/docs-raw/api-reference/aliexpress/shipping
```http
GET | POST /v1/aliexpress/shipping
```
Send parameters as a query string with `GET`, or as a JSON body with `POST`. Both are equivalent.
## Request
```bash tab="cURL"
curl "https://gateway.apinoa.com/v1/aliexpress/shipping?id=1005012583726882®ion=US" \
-H "x-api-key: $APINOA_API_KEY"
```
```js tab="JavaScript"
const params = new URLSearchParams({ id: "1005012583726882", region: "US" });
const res = await fetch(`https://gateway.apinoa.com/v1/aliexpress/shipping?${params}`, {
headers: { "x-api-key": process.env.APINOA_API_KEY },
});
const { success, data, error } = await res.json();
```
```python tab="Python"
import os
import requests
res = requests.get(
"https://gateway.apinoa.com/v1/aliexpress/shipping",
params={"id": "1005012583726882", "region": "US"},
headers={"x-api-key": os.environ["APINOA_API_KEY"]},
)
body = res.json()
```
## Parameters
| Parameter | Type | Required | Notes |
|---|---|---|---|
| `id` | string | yes | AliExpress product id. |
| `currency` | string | | Currency for the shipping fee. Default USD. |
| `quantity` | integer ≥ 1 | | How many units are shipped together. Default 1. |
| `region` | string | | Destination country (ISO 3166-1 alpha-2). Default US. |
| `sku` | string | | The variant to quote: a variant `id` from the product, or a numeric sku id. Omit for the product's listed variant. |
The same parameters, and every response type, are in the OpenAPI document: https://apinoa.com/openapi.json
## Response
Returns [`ShippingQuote`](/docs/api-reference/marketplaces/response-schema#shippingquote) in `data`. A real response, shortened: arrays keep their first entries and long text is cut.
```json
{
"data": {
"marketplace": "aliexpress",
"productId": "1005012583726882",
"skuId": "12000058743753115",
"region": "US",
"quantity": 1,
"available": true,
"options": [
{
"carrier": "AliExpress Selection Standard",
"code": "CAINIAO_FULFILLMENT_STD",
"estimate": "Oct. 31 - Nov. 06",
"cost": {
"currency": "USD",
"amount": 1.99
},
"free": false,
"minDays": 8,
"maxDays": 14,
"shipsFrom": "CN",
"ext": {
"guaranteedDays": 60,
"tracking": true
}
}
]
},
"requestId": "079c568500e94ee7abaf4bfd01c142cc",
"success": true
}
```
## Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | `INVALID_PARAMS` | A parameter is missing, unknown or out of range. The message names it. |
| 401 | `UNAUTHORIZED` | The `x-api-key` header is missing, or the key is revoked or unknown. |
| 402 | `INSUFFICIENT_BALANCE` | The balance does not cover this call. Top up in the dashboard. |
| 404 | `ITEM_NOT_FOUND` | AliExpress does not know this product or variant. |
| 503 | `TOKEN_EXPIRED` | AliExpress is temporarily unavailable. Retry later. |
Every code is listed in [Errors](/docs/errors). Failed calls are not billed.
## Pricing
**Price** $0.001 per call, plus $0.001 for each extra call it makes — at most 1 more, so $0.002 in total. [List price](https://apinoa.com/en/pricing); the rate steps down above 1,000 calls a day.
## Notes
- **Every option, cheapest first:** `carrier`, `cost` (absent with `free: true` when shipping is free), `minDays`/`maxDays`, the arrival `estimate` as worded, and where it ships from.
- **`available: false` with no options** means AliExpress does not ship this item to `region`. That is an answer, not an error.
- **Shipping can differ by variant.** Pass `sku` as a variant `id` from the product response; omit it for the variant whose price the product reports. With a numeric SKU id the call is 1 unit, otherwise 2.