Google Shopping Product API
Fetch the full product page (sellers, variants, specs, reviews, videos) for a single Google Shopping product as structured JSON
The Google Shopping Product API is the companion endpoint to /plugin/google/shopping. It fetches the full product page for a single Google Shopping product — every seller offer, variants, specs, reviews, videos, and related discussions — as structured JSON.
The recommended flow is three steps: call /plugin/google/shopping to find a product, capture its catalog_id, hit this endpoint to get the product page with its first sellers, then replay the stores_next_page_token against /plugin/google/shopping/product/stores to walk the rest of the seller list.
Credit Usage: Each successful request costs 10 credits. For bulk processing, use the Async API with plugins.
Identifier choice: catalog_id (paired with q) is the durable input — persist it in your DB and replay it weeks or months later. immersive_product_page_token is short-lived (a few hours) and best used inside the same workflow that produced it. product_id always works but returns a lean response without variants / specs / reviews.
Catalog IDs are region-specific. Google maintains a separate catalog graph per market, so the same product has a different catalog_id in each region. If you need US, DE, and FR sellers for the same product, run /plugin/google/shopping once per region (passing gl=us / gl=de / gl=fr) and persist the catalog_id you receive in each market.
Key Features
- Durable Catalog Input: Pass
catalog_id+qto look up a product by its long-lived Google catalog identifier. Catalog IDs are stable per region, so you can save them in your own storage and replay them later. - Rich Product Data: Title, gallery, brand, rating, ratings histogram, thumbnails, variants, specs, videos, customer review images, top insights, related discussions — all parsed into structured fields.
- Complete Seller List: Default page returns the headline ~5 sellers and a continuation token (
stores_next_page_token). Replay it against/plugin/google/shopping/product/storesto walk every remaining seller. Passload_all_stores=trueto have the gateway walk the pagination for you in a single request. - Per-seller Detail: Price, shipping, tax, total, payment methods, delivery / returns details, merchant ID, thumbnail, badge text, sale & strikethrough prices, and carrier-style monthly installment plans (
installment_months,installment_down_now,total) when applicable. - Three Response Modes:
catalog_oapv(catalog mode, recommended),oapv(token mode, rich short-lived payload), andhtml(legacy product-id-only fallback). The chosen mode is echoed inproduct_results.source. - Worldwide Coverage: Locale via
gl+hl+location/uule; carrier financing and EU comma-decimal formatting handled automatically. - Device Emulation: Desktop, mobile, or tablet layout with the
deviceparameter. - No Blocks or CAPTCHAs: All anti-bot measures are handled automatically by Scrape.do.
Endpoint
GET https://api.scrape.do/plugin/google/shopping/productParameter keys are case-insensitive. You can use product_id, Product_ID, gl, GL, etc.
Request Parameters
Required
| Parameter | Type | Description |
|---|---|---|
token | string | Your Scrape.do API authentication token |
Identity (choose one)
Provide exactly one of the input combinations below. When you supply more than one, the priority is catalog_id > immersive_product_page_token > product_id.
| Input | Type | Description |
|---|---|---|
catalog_id + q | string + string | Recommended. Durable catalog identifier (digit-only) plus the search query. q is required when catalog_id is supplied — Google's catalog page returns nothing for an empty q. Returns the catalog response (source: "catalog_oapv"): full product page + the first ~5 sellers + a stores_next_page_token when more sellers remain. |
immersive_product_page_token | string | Short-lived opaque base64 token from a shopping_results[] entry on /plugin/google/shopping. Returns the rich token response (source: "oapv"): variants, specs, reviews, videos, full seller list. Best used inside the same workflow that produced it. |
page_token | string | Alias for immersive_product_page_token. Behaves identically. |
product_id | string | Google product catalog ID (digit-only). Returns the HTML fallback (source: "html"): title, product_id, sellers. Required when you want sort_by. |
Do not send next_page_token to this endpoint. Continuation cursors go to /plugin/google/shopping/product/stores. Sending one here returns HTTP 400 with a redirect message.
Device
| Parameter | Type | Default | Description |
|---|---|---|---|
device | string | desktop | Device type. One of desktop, mobile, tablet. |
Routing
| Parameter | Type | Default | Description |
|---|---|---|---|
google_domain | string | google.com | Google domain to query (e.g., google.de, google.com.tr). |
Sort
| Parameter | Type | Default | Description |
|---|---|---|---|
sort_by | string | — | Seller sort order on the HTML fallback path. One of base_price, total_price, promotion, seller_rating. |
sort_by is only honored on the HTML fallback path. Passing it alongside immersive_product_page_token forces fallback and you lose variants, specs, reviews, and videos. To re-sort sellers without losing the rich payload, sort stores[] client-side by extracted_total or extracted_price.
Seller list expansion
| Parameter | Type | Default | Description |
|---|---|---|---|
load_all_stores | boolean | false | Catalog mode only. When true, the gateway walks the continuation cursor internally and returns every seller in one response. The walk is hard-capped (default ~10 pages, ~100 sellers); when the cap trips before the list is exhausted you get a stores_next_page_token for manual continuation plus the Scrape.do-Auto-Page-Truncated: true response header. Each additional upstream hop counts toward the request's credit cost. |
more_stores | boolean | false | Token mode only (legacy). When true, asks the legacy token flow to expand the seller list inline. Catalog mode uses stores_next_page_token + /plugin/google/shopping/product/stores instead. |
Cursor opacity: stores_next_page_token is an opaque, signed string the gateway emits in the response. Replay it verbatim against /plugin/google/shopping/product/stores — do not parse, transform, or generate cursors yourself. The cursor is omitted from the response when the seller list is exhausted — that's your signal that there are no more sellers to fetch.
Localization
| Parameter | Type | Default | Description |
|---|---|---|---|
hl | string | en | Host language. Overrides the language embedded in immersive_product_page_token when both are present. |
gl | string | us | Country perspective. Overrides the country embedded in immersive_product_page_token when both are present. |
location | string | — | Location name (e.g., Austin,Texas,United States). Auto-encoded to UULE when uule is not set. |
uule | string | — | Google-encoded location parameter. Overrides location when both are set. |
See Localization for the full list of supported hl, gl, and google_domain values.
Example Usage
By catalog_id (recommended — durable input)
# 1. Look up the product and grab its catalog_id
CID=$(curl -s "https://api.scrape.do/plugin/google/shopping?q=PodTrak+P8+ZP8&hl=en&gl=us&token=YOUR_TOKEN" \
| jq -r '.shopping_results[0].catalog_id')
# 2. Fetch the product page + first sellers + continuation token
curl "https://api.scrape.do/plugin/google/shopping/product?catalog_id=$CID&q=PodTrak+P8+ZP8&hl=en&gl=us&token=YOUR_TOKEN"Catalog mode in another market
# Same product, German market — re-search per region to get the region-scoped catalog_id
CID=$(curl -s "https://api.scrape.do/plugin/google/shopping?q=iPhone+17+Pro&hl=de&gl=de&token=YOUR_TOKEN" \
| jq -r '.shopping_results[0].catalog_id')
curl "https://api.scrape.do/plugin/google/shopping/product?catalog_id=$CID&q=iPhone+17+Pro&hl=de&gl=de&token=YOUR_TOKEN"Get every seller in one call
curl "https://api.scrape.do/plugin/google/shopping/product?catalog_id=$CID&q=PodTrak+P8+ZP8&load_all_stores=true&token=YOUR_TOKEN"By immersive_product_page_token (rich short-lived response)
# 1. Mint a token from the search endpoint
TOKEN=$(curl -s "https://api.scrape.do/plugin/google/shopping?q=Sony+WH-1000XM5&token=YOUR_TOKEN" \
| jq -r '.shopping_results[0].immersive_product_page_token')
# 2. Replay it against the product endpoint (variants/specs/reviews/videos returned)
curl "https://api.scrape.do/plugin/google/shopping/product?immersive_product_page_token=$TOKEN&token=YOUR_TOKEN"By product_id (HTML fallback)
curl "https://api.scrape.do/plugin/google/shopping/product?product_id=7000474567848623813&token=YOUR_TOKEN"Response
Top-Level Structure
{
"search_parameters": { ... },
"product_results": { ... }
}search_parameters
Echo of the request parameters sent by the client.
{
"engine": "google_shopping_product",
"product_id": "7000474567848623813",
"page_token": "eyJlaSI6ImFhX1Nh...",
"google_domain": "google.com",
"hl": "en",
"gl": "us",
"device": "desktop",
"more_stores": false,
"next_page_token": ""
}Optional fields appear only when sent: location, uule, sort_by.
product_results (rich path)
Returned when you pass immersive_product_page_token and do not pass sort_by. source is "oapv".
{
"title": "Sony WH-1000XM5 Wireless Noise Canceling Headphones",
"product_id": "7000474567848623813",
"brand": "Sony",
"rating": 4.6,
"reviews": 4800,
"reviews_label": "4,800 reviews",
"ratings": [
{ "stars": 5, "count": 3400 },
{ "stars": 4, "count": 900 }
],
"thumbnails": [ "https://encrypted-tbn0.gstatic.com/...", "..." ],
"reviews_images": [ "https://encrypted-tbn0.gstatic.com/...", "..." ],
"stores": [ { /* see stores[] table below */ } ],
"stores_next_page_token": "eyJzb3JpIjo1LCJtbm8iOjEwfQ",
"variants": [ { "type": "Color", "options": [ { "text": "Black", "selected": true } ] } ],
"specs": [ { "section": "General", "items": [ { "label": "Wireless", "value": "Yes" } ] } ],
"videos": [ { "title": "Sony WH-1000XM5 Review", "source": "youtube.com", "thumbnail": "...", "link": "..." } ],
"user_reviews": [ { "rating": 5, "title": "Best headphones", "body": "...", "author": "John D." } ],
"top_insights": [ { "section": "Sound quality", "items": [ "Crystal-clear audio", "Strong bass" ] } ],
"about_the_product": { "description": "..." },
"more_options": [ { "title": "Sony WH-1000XM4", "link": "...", "thumbnail": "..." } ],
"discussions_and_forums": [ { "title": "...", "source": "reddit.com", "link": "...", "snippet": "..." } ],
"extension_ids": { "catalog_id": "...", "gpc_id": "..." },
"source": "oapv"
}product_results (HTML fallback)
Returned when you pass only product_id, or when you pass sort_by alongside the token. source is "html". Lean shape — no variants, specs, videos, or reviews — but the seller list works.
{
"title": "Sony WH-1000XM5 Wireless Noise Canceling Headphones",
"product_id": "7000474567848623813",
"stores": [ { /* see stores[] table below */ } ],
"source": "html"
}stores[] Fields
Each entry in product_results.stores[] describes a single merchant offer for the product.
| Field | Type | Always present | Description |
|---|---|---|---|
position | int | yes | 1-based position in the seller list |
name | string | yes | Merchant name (e.g., "Best Buy", "Amazon.com") |
logo | string | Merchant favicon URL | |
link | string | Direct link to the merchant's product page (Google redirect unwrapped) | |
title | string | Product title as listed on the merchant's site (may differ slightly from product_results.title) | |
rating | float | Merchant rating out of 5 | |
reviews | float | Merchant review count (suffix-aware, e.g., 1.2K is parsed as 1200) | |
price | string | Formatted base price ("$248.00", "1.299,00 TL") | |
extracted_price | float | Numeric base price | |
currency | string | ISO currency code when detected ("USD", "EUR", "TRY") | |
shipping | string | Formatted shipping cost ("Free", "$8.99") | |
shipping_extracted | float | Numeric shipping cost (0 when free) | |
tax | string | Formatted tax line when shown | |
extracted_tax | float | Numeric tax value | |
tax_hint | string | Tax hint shown by Google when an exact tax line isn't available (e.g., "+tax") | |
total | string | Formatted total (price + shipping + tax) | |
extracted_total | float | Numeric total | |
payment_methods | string | Accepted payment methods (e.g., "PayPal, Affirm accepted") | |
details_and_offers | string[] | Per-offer notes — delivery window, stock, return policy (e.g., ["Free delivery between May 21 – 27", "In stock", "90-day returns"]) | |
merchant_id | string | Google's internal merchant ID | |
thumbnail | string | Merchant-specific product thumbnail when distinct from the canonical image | |
tag | string | Badge text Google renders on the offer, in the page locale (e.g. "Best price", "Bester Preis", "Meilleur prix"). Text is carried verbatim, not normalized. | |
original_price | string | Pre-discount price as displayed, when Google renders a strikethrough | |
extracted_original_price | float | Numeric pre-discount price | |
discount | string | Discount badge text (e.g., "20% OFF") | |
installment_months | int | Number of monthly installments for carrier-style or BNPL offers (Verizon, AT&T, T-Mobile, congstar, MediaMarkt mit Vertrag, …). When set, price is the monthly figure, not the lump sum — read total / extracted_total for the lifetime cost. | |
installment_down_now | string | Upfront payment for the installment plan, as displayed (e.g., "$0", "60 €"). |
Three Response Modes
The plugin picks the response mode automatically based on what you sent:
| You sent | Mode | source | What you get |
|---|---|---|---|
catalog_id + q (no sort_by) | Catalog | "catalog_oapv" | Full product page (title, gallery, description, ratings) + the first ~5 sellers + stores_next_page_token when more sellers exist |
immersive_product_page_token (no sort_by) | Token | "oapv" | Full payload — variants, specs, reviews, videos, all sellers |
product_id only | HTML fallback | "html" | Title, product_id, sellers |
Any input and sort_by | HTML fallback | "html" | Sorted sellers, no variants / specs / reviews / videos |
Inspect product_results.source to know which mode served the response. Catalog mode is the recommended default for any workflow that persists product identifiers.
Paginating every seller
Catalog mode returns up to ~5 sellers inline plus a stores_next_page_token when more remain. Use it in one of two ways:
Option 1 — Manual pagination via /plugin/google/shopping/product/stores
The dedicated continuation endpoint accepts the signed token and returns the next batch of sellers (up to ~10 per call). It's the most cost-efficient path because each call is a single hop when the embedded session is still fresh.
GET https://api.scrape.do/plugin/google/shopping/product/stores| Parameter | Type | Description |
|---|---|---|
token | string | Your Scrape.do API authentication token |
catalog_id | string | The same catalog_id you used on the matching /plugin/google/shopping/product call. Must match the catalog_id embedded inside next_page_token. |
next_page_token | string | The stores_next_page_token value from a prior response. Replay verbatim. The locale (q, gl, hl) is carried inside the token and reused server-side — you do not pass them again. |
# After the catalog call:
NEXT=$(echo "$RESP" | jq -r '.product_results.stores_next_page_token // empty')
while [ -n "$NEXT" ]; do
RESP=$(curl -s "https://api.scrape.do/plugin/google/shopping/product/stores?catalog_id=$CID&next_page_token=$NEXT&token=YOUR_TOKEN")
echo "$RESP" | jq '.product_results.stores | length'
NEXT=$(echo "$RESP" | jq -r '.product_results.stores_next_page_token // empty')
doneResponse shape is lean — only product_id, stores[], and (when more sellers remain) stores_next_page_token. Title, gallery, variants, etc. are not re-emitted; you already have them from the catalog call. source is "catalog_oapv_more".
The token is signed and bound to the supplied catalog_id. Sending a different catalog_id returns HTTP 400 ("catalog_id mismatch").
Option 2 — One-shot load_all_stores=true
For workflows that want every seller in a single response, pass load_all_stores=true on the catalog call. The gateway walks the continuation cursor for you and returns the merged seller list with no continuation token. Billed per upstream hop and capped (~10 pages by default); when the cap trips, you still get a stores_next_page_token for manual continuation plus the Scrape.do-Auto-Page-Truncated: true response header.
curl "https://api.scrape.do/plugin/google/shopping/product?catalog_id=$CID&q=iPhone+17+Pro&load_all_stores=true&token=YOUR_TOKEN"Legacy more_stores on the token path
The legacy token flow (immersive_product_page_token) uses more_stores=true to inline the expanded seller list. New integrations should prefer the catalog path; more_stores stays available for existing callers.
Handling Session Expiry
immersive_product_page_token values are tied to an upstream Google session that ages out after several hours. When that happens, the plugin returns HTTP 410 Gone:
{
"error": "session expired",
"message": "The immersive_product_page_token is older than 6h0m0s and is past Google's product-viewer session lifetime. Fetch /plugin/google/shopping again to mint a fresh token, then retry."
}(The exact lifetime message may vary; the recovery is always the same.)
Recovery: When you receive HTTP 410 on /plugin/google/shopping/product, re-fetch /plugin/google/shopping with the same query, grab a fresh immersive_product_page_token from the response, then retry the product call with the new token. Don't store tokens long-term — treat them as ephemeral cursors.
See Transient Errors for the full rotate-on-expiry pattern.
Error Handling
All endpoints return errors in a consistent JSON format:
{
"error": "error_code",
"message": "Human readable error message"
}Common Error Codes
| Status | Error | Description |
|---|---|---|
400 | token is required | Missing authentication token |
400 | q is required when catalog_id is supplied | catalog_id was set without a non-empty q |
400 | invalid catalog_id | catalog_id was empty, too long, or contained non-digit characters |
400 | next_page_token belongs on /plugin/google/shopping/product/stores | A pagination cursor was sent to /product — replay it against /product/stores instead |
400 | catalog_id mismatch | On /product/stores, the supplied catalog_id does not match the catalog_id embedded inside next_page_token |
400 | product_id (or immersive_product_page_token) is required | No identifier was supplied at all |
400 | invalid immersive_product_page_token | Token could not be base64-decoded or its JSON payload could not be parsed |
400 | invalid product_id | product_id was empty, too long, or contained non-digit characters |
400 | device must be one of: desktop, mobile, tablet | Invalid device value |
400 | invalid google_domain | Domain not in the supported list |
400 | invalid sort_by | Sort value not in base_price, total_price, promotion, seller_rating |
400 | invalid next_page_token | The pagination cursor's signature failed, payload couldn't be parsed, or one of the bounded fields fell out of range. Fetch a fresh first page from /plugin/google/shopping/product and resume from its cursor |
410 | session expired | Token is past its upstream session lifetime. Re-mint via /plugin/google/shopping (token path) or simply retry the same request (catalog path — the server re-mints transparently and continues). See Handling Session Expiry below |
502 | request failed | Transient upstream failure. Retry |
502 | unexpected response | Non-200 response from Google |
500 | failed to parse product viewer response | Google returned an unparseable product viewer payload (rare; if persistent, report to support) |
500 | decompression failed | Response body decompression error |
Empty seller list is 200, not an error. When a catalog_id is valid in Google's index but the regional catalog graph has no sellers for it (common for US-specific catalogs queried with gl=de / gl=fr etc.), the response is 200 with product_results.stores = []. The catalog_id is simply unavailable in that market — re-search in the target region (/plugin/google/shopping?gl=de…) to get the region-specific catalog_id.

