Get a Google shopping card product
curl --request POST \
--url https://api.cloro.dev/v1/monitor/google/product \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"productResultToken": "nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg"
}
'import requests
url = "https://api.cloro.dev/v1/monitor/google/product"
payload = { "productResultToken": "nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({productResultToken: 'nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg'})
};
fetch('https://api.cloro.dev/v1/monitor/google/product', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cloro.dev/v1/monitor/google/product",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'productResultToken' => 'nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cloro.dev/v1/monitor/google/product"
payload := strings.NewReader("{\n \"productResultToken\": \"nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cloro.dev/v1/monitor/google/product")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"productResultToken\": \"nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloro.dev/v1/monitor/google/product")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"productResultToken\": \"nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"result": {
"productResults": [
{
"title": "Apple iPhone 17",
"stores": [
{
"name": "Apple",
"link": "https://www.apple.com/shop/buy-iphone/iphone-17",
"redirectLink": "https://www.google.com/goto?url=CAESaAHrOzAVev0pi_wnTxm1qpiZTmMyAllabLr1or10ZCfvJKPk",
"price": {
"value": 799,
"currency": "USD",
"raw": "$799.00"
},
"installments": "<string>",
"description": "iPhone 17 256GB White - Apple",
"buyingOptions": [
"<string>"
],
"logo": "<string>",
"shipping": "<string>",
"condition": "New",
"rating": 123,
"reviews": "2.3k"
}
],
"aboutTheProduct": {
"description": "<string>",
"features": [
"<string>"
]
},
"specifications": {
"general": {
"Brand": "Apple",
"Storage": "256 GB"
}
},
"images": [
{
"mainUrl": "<string>",
"thumbnailUrl": "<string>",
"title": "<string>"
}
],
"rating": 123,
"reviews": "<string>",
"variants": [
{}
],
"userReviews": [
{}
],
"relatedProducts": [
{}
]
}
]
}
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "prompt",
"message": "Prompt cannot be empty"
}
],
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "MISSING_API_KEY",
"message": "Missing or invalid API key",
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Insufficient credits",
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Route not found",
"details": {
"id": "/v1/invalid-endpoint"
},
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Route not found",
"details": {
"id": "/v1/invalid-endpoint"
},
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "CONCURRENCY_LIMIT_EXCEEDED",
"message": "Concurrency limit exceeded",
"details": {
"limit": 10
},
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "REQUEST_CANCELED",
"message": "Request was canceled by client",
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "Maximum retries exceeded",
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "EXTERNAL_SERVICE_ERROR",
"message": "goto resolve request failed",
"details": {
"service": "google"
},
"timestamp": "2025-01-15T12:00:00.000Z"
}
}Google
Get a Google shopping card product
Get the product panel that a Google Search shopping card or a Google AI Mode product opens: merchant offers, specifications, reviews and images.
POST
/
v1
/
monitor
/
google
/
product
Get a Google shopping card product
curl --request POST \
--url https://api.cloro.dev/v1/monitor/google/product \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"productResultToken": "nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg"
}
'import requests
url = "https://api.cloro.dev/v1/monitor/google/product"
payload = { "productResultToken": "nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({productResultToken: 'nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg'})
};
fetch('https://api.cloro.dev/v1/monitor/google/product', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.cloro.dev/v1/monitor/google/product",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'productResultToken' => 'nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.cloro.dev/v1/monitor/google/product"
payload := strings.NewReader("{\n \"productResultToken\": \"nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.cloro.dev/v1/monitor/google/product")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"productResultToken\": \"nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.cloro.dev/v1/monitor/google/product")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"productResultToken\": \"nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"result": {
"productResults": [
{
"title": "Apple iPhone 17",
"stores": [
{
"name": "Apple",
"link": "https://www.apple.com/shop/buy-iphone/iphone-17",
"redirectLink": "https://www.google.com/goto?url=CAESaAHrOzAVev0pi_wnTxm1qpiZTmMyAllabLr1or10ZCfvJKPk",
"price": {
"value": 799,
"currency": "USD",
"raw": "$799.00"
},
"installments": "<string>",
"description": "iPhone 17 256GB White - Apple",
"buyingOptions": [
"<string>"
],
"logo": "<string>",
"shipping": "<string>",
"condition": "New",
"rating": 123,
"reviews": "2.3k"
}
],
"aboutTheProduct": {
"description": "<string>",
"features": [
"<string>"
]
},
"specifications": {
"general": {
"Brand": "Apple",
"Storage": "256 GB"
}
},
"images": [
{
"mainUrl": "<string>",
"thumbnailUrl": "<string>",
"title": "<string>"
}
],
"rating": 123,
"reviews": "<string>",
"variants": [
{}
],
"userReviews": [
{}
],
"relatedProducts": [
{}
]
}
]
}
}{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"field": "prompt",
"message": "Prompt cannot be empty"
}
],
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "MISSING_API_KEY",
"message": "Missing or invalid API key",
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Insufficient credits",
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Route not found",
"details": {
"id": "/v1/invalid-endpoint"
},
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Route not found",
"details": {
"id": "/v1/invalid-endpoint"
},
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "CONCURRENCY_LIMIT_EXCEEDED",
"message": "Concurrency limit exceeded",
"details": {
"limit": 10
},
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "REQUEST_CANCELED",
"message": "Request was canceled by client",
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "Maximum retries exceeded",
"timestamp": "2025-01-15T12:00:00.000Z"
}
}{
"success": false,
"error": {
"code": "EXTERNAL_SERVICE_ERROR",
"message": "goto resolve request failed",
"details": {
"service": "google"
},
"timestamp": "2025-01-15T12:00:00.000Z"
}
}A shopping card opens a Google product panel, not a merchant page, and it carries no product URL. The card’s 
The request takes only the token. The token keeps the query, locale and location of the request that issued it, so you cannot change them.
It costs 1 credit plus the 2-credit sync surcharge, and only when it succeeds. It uses a concurrency slot. For async, send
A failed async task has
Only
Expect the first six on most offers and the rest only occasionally. Everything beyond
productResultToken gets you that panel. Google AI Mode shopping cards and inline products have the same token.

The product panel that opens from a 'More products' card
Get a token
Send a Google Search request withdevice: "desktop" (the default). Cards on the first results page carry a token when Google serves them with a panel reference. A Google AI Mode request returns a token on each shopping card and inline product that links to a Google product viewer. The token adds no cost to either request.
{
"title": "ASICS Women's Gel-Nimbus 28",
"position": 1,
"productResultToken": "nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg",
"category": "More products",
"store": "DICK'S Sporting Goods"
}
A token expires five minutes after the scrape that issued it. Time in the async queue counts against those five minutes, for the search and for the product request. If your search ran async, its tokens can expire before you read the result. Use the token immediately, or scrape again.
Get the product
curl -X POST "https://api.cloro.dev/v1/monitor/google/product" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"productResultToken": "nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg"
}'
{
"success": true,
"result": {
"productResults": [
{
"title": "Apple - Refurbished Excellent - iPhone 17 Pro 256GB 6.3\" 5G Fully Unlocked, Deep - Blue",
"brand": "Apple",
"rating": 4.5,
"reviews": "14K",
"stores": [
{
"name": "Best Buy",
"link": "https://www.bestbuy.com/product/apple-iphone-17-pro-256gb-6-3-5g-fully-unlocked-deep-blue/J7XLKFSPR8/sku/12337127?ref=212&loc=marketplace",
"price": { "value": 1080.0, "currency": "USD", "raw": "$1,080.00" },
"shipping": "Delivery between May 13 – 25 $16.49",
"condition": "Pre-owned",
"buyingOptions": ["Pre-owned", "In stock", "Delivery between May 13 – 25 $16.49"]
}
],
"specifications": {
"general": { "Screen Size": "6.3 inches", "Built-in Storage": "256 gigabytes" }
}
}
]
}
}
taskType: "GOOGLE_PRODUCT" with payload: { "productResultToken": "..." } to POST /v1/async/task or the batch endpoint. Async costs 1 credit.
If you send the product request async, set a high
priority (for example 10). The default priority is 1, so the task waits behind your other queued tasks and the token can expire before the task starts.Errors
The failure reason is inerror.details.errorType. No error is charged.
| Status | errorType | Cause |
|---|---|---|
404 | productResultTokenUnknown | cloro did not issue this token. |
410 | productResultTokenExpired | The token is older than five minutes. Scrape again. |
502 | googleProductUpstreamEmpty | Google returned no product data. |
502 | googleProductUnparseable | cloro could not parse Google’s response. |
502 | googleProductPayloadLayoutChanged | Google returned a panel layout that cloro does not support. |
status: "FAILED" and the same errorType in response.error.details.
Product structure
The response has one entry inproductResults, with the same shape as the right-rail product results.
Each entry describes one product cluster and its merchant offers.
| Field | Type | Description |
|---|---|---|
title | string | Product cluster title, as Google displayed it |
stores | array | Merchant offers. See store structure |
aboutTheProduct | object | description (Google’s prose blurb) and features (bullets, when shown) |
specifications | object | Spec tables grouped by category. See specifications |
images | array | mainUrl and thumbnailUrl per image, often identical |
title is guaranteed (Google Search entries also always carry stores) — anything Google’s panel omits is left out of the object rather than sent as null. Panels can also carry rating, reviews, variants, relatedProducts and userReviews, emitted under those names when present, though most carry none of them.
Specifications
A category name mapped to a flat set of spec key/value pairs:{
"specifications": {
"general": {
"Brand": "Sennheiser",
"Noise cancellation": "Yes"
}
}
}
Category names and spec keys arrive in the response language — a
country: "MX" request returns {"general": {"Marca": "Sennheiser"}}. Iterate the object; don’t read a hard-coded key like specifications.general.Brand.Store structure
| Field | Type | Description |
|---|---|---|
name | string | Merchant name (e.g. "Amazon") |
link | string | Merchant product URL, passed through from Google verbatim |
price | object | See price shape. Omitted entirely when the displayed text can’t be parsed |
installments | string | Installment terms as displayed (e.g. "for 24 mo, $0 now, $798.96 total") |
description | string | Merchant’s listing title, naming the exact SKU sold (e.g. "iPhone 17 256GB White - Apple") — unlike title, which names the product overall |
buyingOptions | array | Merchant badges in display order — stock status, rating, delivery promise, returns window. Google varies these per merchant and locale, so they stay an unparsed list of strings |
logo | string | Merchant logo URL |
shipping | string | Shipping cost or promise as displayed |
condition | string | Item condition (e.g. "New", "Refurbished") |
rating | number | Merchant rating |
reviews | string | Merchant review count, abbreviated (e.g. "384", "2.3k") |
redirectLink | string | Google’s /goto redirect for link, when Google served one. See redirect links |
name is optional and omitted when absent — Google shows different combinations per merchant, so an “Out of stock” listing may have no price or link at all.
link is not validated against the cluster. Google occasionally serves a merchant URL for an unrelated SKU, so an offer’s link can point at a different product than its description names. Verify against description if link correctness matters.Price shape
| Field | Type | Description |
|---|---|---|
value | number | Parsed numeric price; always present when price is emitted. On installment offers this is the monthly payment |
currency | string | ISO 4217 code (e.g. "USD", "BRL", "MXN", "AED", "JPY"), or the displayed token verbatim when cloro can’t map it. See currency codes |
raw | string | Visible price text verbatim (e.g. "$149.99") |
currency here is an ISO code ("USD"), while shopping cards carry the symbol as displayed ("$") and inline products carry either, depending on the data Google supplies. Normalize before comparing across surfaces.Currency codes
cloro maps the currency token Google displays next to the price to its ISO 4217 code. Multi-character and word tokens are recognized, so"R$ 5.597,99" returns "BRL", "MXN 19,499.00" returns "MXN", and "3,399.00 د.إ" returns "AED". Yen written as ¥, ¥ or 円 returns "JPY".
A bare $ doesn’t name a market on its own, because Google writes US, Canadian and Australian offers the same way. On AI Mode, cloro resolves a bare $ from the request country: CA returns "CAD", AU returns "AUD", NZ returns "NZD", SG returns "SGD", HK returns "HKD", and any other country returns "USD". On Google Search, a bare $ always returns "USD".
An explicit token always takes priority over the request country. A country: "CA" request that gets an offer displayed as "R$ 5.597,99" still returns "BRL".Authorizations
Body
application/json
The token from a Google Search shopping card
The productResultToken from a Google Search shopping card, or from a Google AI Mode shopping card or inline product. The token carries the query, locale and location of the request that issued it, so the request takes no other field.
Required string length:
1 - 512Example:
"nuzK-QgAV8BqrZdwQJYZopzdoFhpvKr-N2_YDZevwzg"
Was this page helpful?