logo

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 + q to 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/stores to walk every remaining seller. Pass load_all_stores=true to 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), and html (legacy product-id-only fallback). The chosen mode is echoed in product_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 device parameter.
  • No Blocks or CAPTCHAs: All anti-bot measures are handled automatically by Scrape.do.

Endpoint

GET https://api.scrape.do/plugin/google/shopping/product

Parameter keys are case-insensitive. You can use product_id, Product_ID, gl, GL, etc.


Request Parameters

Required

ParameterTypeDescription
tokenstringYour 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.

InputTypeDescription
catalog_id + qstring + stringRecommended. 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_tokenstringShort-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_tokenstringAlias for immersive_product_page_token. Behaves identically.
product_idstringGoogle 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

ParameterTypeDefaultDescription
devicestringdesktopDevice type. One of desktop, mobile, tablet.

Routing

ParameterTypeDefaultDescription
google_domainstringgoogle.comGoogle domain to query (e.g., google.de, google.com.tr).

Sort

ParameterTypeDefaultDescription
sort_bystringSeller 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

ParameterTypeDefaultDescription
load_all_storesbooleanfalseCatalog 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_storesbooleanfalseToken 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

ParameterTypeDefaultDescription
hlstringenHost language. Overrides the language embedded in immersive_product_page_token when both are present.
glstringusCountry perspective. Overrides the country embedded in immersive_product_page_token when both are present.
locationstringLocation name (e.g., Austin,Texas,United States). Auto-encoded to UULE when uule is not set.
uulestringGoogle-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

# 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.

FieldTypeAlways presentDescription
positionintyes1-based position in the seller list
namestringyesMerchant name (e.g., "Best Buy", "Amazon.com")
logostringMerchant favicon URL
linkstringDirect link to the merchant's product page (Google redirect unwrapped)
titlestringProduct title as listed on the merchant's site (may differ slightly from product_results.title)
ratingfloatMerchant rating out of 5
reviewsfloatMerchant review count (suffix-aware, e.g., 1.2K is parsed as 1200)
pricestringFormatted base price ("$248.00", "1.299,00 TL")
extracted_pricefloatNumeric base price
currencystringISO currency code when detected ("USD", "EUR", "TRY")
shippingstringFormatted shipping cost ("Free", "$8.99")
shipping_extractedfloatNumeric shipping cost (0 when free)
taxstringFormatted tax line when shown
extracted_taxfloatNumeric tax value
tax_hintstringTax hint shown by Google when an exact tax line isn't available (e.g., "+tax")
totalstringFormatted total (price + shipping + tax)
extracted_totalfloatNumeric total
payment_methodsstringAccepted payment methods (e.g., "PayPal, Affirm accepted")
details_and_offersstring[]Per-offer notes — delivery window, stock, return policy (e.g., ["Free delivery between May 21 – 27", "In stock", "90-day returns"])
merchant_idstringGoogle's internal merchant ID
thumbnailstringMerchant-specific product thumbnail when distinct from the canonical image
tagstringBadge 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_pricestringPre-discount price as displayed, when Google renders a strikethrough
extracted_original_pricefloatNumeric pre-discount price
discountstringDiscount badge text (e.g., "20% OFF")
installment_monthsintNumber 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_nowstringUpfront 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 sentModesourceWhat 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 onlyHTML fallback"html"Title, product_id, sellers
Any input and sort_byHTML 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
ParameterTypeDescription
tokenstringYour Scrape.do API authentication token
catalog_idstringThe 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_tokenstringThe 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')
done

Response 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

StatusErrorDescription
400token is requiredMissing authentication token
400q is required when catalog_id is suppliedcatalog_id was set without a non-empty q
400invalid catalog_idcatalog_id was empty, too long, or contained non-digit characters
400next_page_token belongs on /plugin/google/shopping/product/storesA pagination cursor was sent to /product — replay it against /product/stores instead
400catalog_id mismatchOn /product/stores, the supplied catalog_id does not match the catalog_id embedded inside next_page_token
400product_id (or immersive_product_page_token) is requiredNo identifier was supplied at all
400invalid immersive_product_page_tokenToken could not be base64-decoded or its JSON payload could not be parsed
400invalid product_idproduct_id was empty, too long, or contained non-digit characters
400device must be one of: desktop, mobile, tabletInvalid device value
400invalid google_domainDomain not in the supported list
400invalid sort_bySort value not in base_price, total_price, promotion, seller_rating
400invalid next_page_tokenThe 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
410session expiredToken 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
502request failedTransient upstream failure. Retry
502unexpected responseNon-200 response from Google
500failed to parse product viewer responseGoogle returned an unparseable product viewer payload (rare; if persistent, report to support)
500decompression failedResponse 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.


On this page