Google Shopping Product API
View as MarkdownFetch 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 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 (
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 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). A capped or partially failed walk returns the sellers collected so far, a next_page_token for manual continuation, and the Scrape.do-Auto-Page-Truncated: true response header. A walk that fails before collecting any sellers returns HTTP 502. 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 next_page_token + /plugin/google/shopping/product/stores instead. |
Cursor opacity: 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. One exception on the token (seller-list) path: the upstream session the token replays into is region-bound, so a gl that would move the request to a different market than the token was minted in yields to the token's country there. A gl from the same market as the token is still honored, and the hl language is unaffected. |
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 */ } ],
"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 /goto redirects are resolved before the URL is returned. | |
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 + 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 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 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.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.next_page_token // empty')
doneResponse shape is lean — only product_id, stores[], and (when more sellers remain) 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. The walk is billed per upstream hop and capped at about 10 pages by default. If the cap is reached or a later page fails, the response keeps the sellers collected so far and includes a next_page_token for manual continuation plus the Scrape.do-Auto-Page-Truncated: true response header. If the walk fails before collecting any sellers, the endpoint returns HTTP 502 and the request is not charged.
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 |
502 | failed to load sellers | load_all_stores=true failed before collecting any sellers. Retry the request |
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 |
An empty seller list means no sellers are available in that market. 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 = []. Re-search in the target region (/plugin/google/shopping?gl=de…) to get its region-specific catalog ID. A failed load_all_stores=true walk does not return an ambiguous empty response: it returns HTTP 502 when no sellers were collected, or a partial HTTP 200 response with Scrape.do-Auto-Page-Truncated: true when earlier pages succeeded.

