Reviews
Get a Shopify product's reviews from the review app its store uses.
GET | POST /v1/shopify/reviewsSend parameters as a query string with GET, or as a JSON body with POST. Both are equivalent.
Request
curl "https://gateway.apinoa.com/v1/shopify/reviews?id=www.orthofeet.com%2Fcoral-black" \
-H "x-api-key: $APINOA_API_KEY"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.
Response
Returns ReviewPage in data. A real response, shortened: arrays keep their first entries and long text is cut.
{
"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. 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; the rate steps down above 1,000 calls a day.
Notes
- Use
cursorfor the next page.pageis not supported on every store;cursoralways works. A first page usually costs one unit more than the base; acursorpage costs only the base. - No reviews is an answer. A product with none returns
200withreviews: []andpage.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
groupedbefore computing a per-product rating.