serpix

google-shopping-api

Google Shopping API

Google Shopping as JSON. Forty products a page, each with what it costs now, what it cost before, who is selling it and how it is rated, with prices parsed into numbers so comparing them is arithmetic rather than string work.

Endpoint

One credit per request. A page carries forty products, which is the whole grid Google renders before it asks for another page.

GET/v1/shopping
bash
curl "https://api.serpix.io/v1/shopping?q=nike+air+max&gl=us" \
  -H "Authorization: Bearer $SERPIX_API_KEY"

The same parameters are accepted as a JSON body on POST /v1/shopping:

post variant
curl -X POST "https://api.serpix.io/v1/shopping" \
  -H "Authorization: Bearer $SERPIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"q": "nike air max", "gl": "us"}'

Parameters

Query and localization
fieldtypedescription
qrequiredstringThe search query.
glstringCountry to shop in, two-letter code. Sets the currency and the merchants: the same query on gb comes back in sterling from UK sellers. Defaults to us.
hlstringLanguage of the results page, two-letter code.

Results

Results
fieldtypedescription
devicedesktop | mobile | tabletResults as Google serves them to a desktop browser or a phone.
pageinteger1-based page. Each page is a fresh forty products; pages do not overlap. One credit per page.
autocorrectbooleanSet false to search the query exactly as typed, excluding results Google returns for its spell-corrected guess.
safeactive | offSafeSearch. active filters explicit results.

Rejected parameters

Unknown parameters are never silently ignored. A request carrying one is rejected with 422 before any search runs, so a typo can't bill you for results you didn't ask for. Common parameters from raw Google URLs and other SERP APIs get a pointed message naming the equivalent here:

Rejected aliases
fieldtypedescription
num422Google serves a fixed grid of forty products. Paginate with page.
start422Use page, a 1-based page number, instead of a result offset.
tbs422Google ignores the price range on this vertical: a $100 ceiling came back with 25 of 40 products priced above it.
min_price / max_price / ppr_min / ppr_max422The same inert price filter, under its other names.
nfpr422Use autocorrect.
location / uule422Use gl and hl. City-level geotargeting is not wired for this vertical.
lr422Not supported on this vertical.
udm / tbm422Not needed. This endpoint is the shopping vertical.
engine422Not needed. This endpoint is Google Shopping.
api_key422Authenticate with the Authorization header, never the query string.
422 · rejected parameter
HTTP/2 422
content-type: application/json

{
  "detail": [
    {
      "type": "value_error",
      "loc": ["query"],
      "msg": "Value error, `num` is not supported: Google serves a fixed page of 40 products; paginate with `page`",
      "input": { "q": "nike air max", "num": "100", ... }
    }
  ]
}

Response

A search returns one JSON object: two envelope objects that are always present (search_metadata, search_parameters) and the ranking as shopping_results. A field that wasn't served is omitted entirely, never null, never [], so a presence check is all you need before reading one.

200 · trimmed
{
  "search_metadata": {
    "id": "srx_TyU4WNhPmEBr48wd",
    "status": "success",
    "created_at": "2026-07-28T18:15:34Z",
    "took_ms": 5637,
    "parser_version": "2026.07.1",
    "google_url": "https://www.google.com/search?q=nike+air+max&udm=28&hl=en&gl=us&...",
    "html_url": "https://api.serpix.io/searches/srx_TyU4WNhPmEBr48wd/GZImiWL8CuCQ2QSx.html",
    "json_url": "https://api.serpix.io/searches/srx_TyU4WNhPmEBr48wd/GZImiWL8CuCQ2QSx.json"
  },
  "search_parameters": {
    "q": "nike air max",
    "gl": "us",
    "hl": "en",
    "device": "desktop",
    "page": 1
  },
  "shopping_results": [
    {
      "position": 1,
      "title": "Air Max 90 Nike Men's",
      "price": "$101.25",
      "extracted_price": 101.25,
      "original_price": "$135",
      "extracted_original_price": 135,
      "source": "Nike",
      "multiple_sellers": true,
      "rating": 4.6,
      "reviews": 13000,
      "tag": "25% OFF",
      "extensions": ["Free delivery by Mon"],
      "product_id": "15610799269751422523",
      "thumbnail": "data:image/webp;base64,UklGRhgJAABXRUJQV..."
    },
    ...
  ],
  "pagination": {
    "current": 1,
    "next": "https://www.google.com/search?q=nike+air+max&udm=28&hl=en&gl=us&start=10&..."
  }
}

Every response also reports its cost in the X-Serpix-Credits-Charged and X-Serpix-Credits-Remaining headers.

The grid

Each product card becomes one entry of shopping_results. The capture below is the grid exactly as Google served it, above the JSON it parsed into:

The gridHover or tap a tile to pair it with its entry

Google Shopping results for nike air max: a grid of product cards, each with a photo, discount badge, price, merchant and star rating.
q=nike air max · desktop · google.com

shopping_results

[  {
    "position": 1,
    "title": "Air Max 90 Nike Men's",
    "price": "$101.25",
    "extracted_price": 101.25,
    "original_price": "$135",
    "extracted_original_price": 135,
    "source": "Nike",
    "multiple_sellers": true,
    "rating": 4.6,
    "reviews": 13000,
    "tag": "25% OFF",
    "extensions": ["Free delivery by Mon"],
    "product_id": "15610799269751422523"
  },  {
    "position": 2,
    "title": "Nike Men's Air Max Excee Sneakers",
    "price": "$75.00",
    "extracted_price": 75,
    "original_price": "$100",
    "extracted_original_price": 100,
    "source": "Nike",
    "multiple_sellers": true,
    "rating": 4.3,
    "reviews": 7900,
    "tag": "25% OFF",
    "extensions": ["Free delivery on $75+"],
    "product_id": "7304528092508809722"
  },  {
    "position": 3,
    "title": "Nike Women's Air Max 90",
    "price": "$101.25",
    "extracted_price": 101.25,
    "original_price": "$135",
    "extracted_original_price": 135,
    "source": "Nike",
    "multiple_sellers": true,
    "rating": 4.6,
    "reviews": 3700,
    "tag": "25% OFF",
    "extensions": ["Free delivery by Mon"],
    "product_id": "11633386364053739221"
  },  {
    "position": 4,
    "title": "Nike Men's Air Max Phoenix",
    "price": "$87.73",
    "extracted_price": 87.73,
    "original_price": "$155",
    "extracted_original_price": 155,
    "source": "Nike",
    "multiple_sellers": true,
    "rating": 4.5,
    "reviews": 144,
    "tag": "43% OFF",
    "extensions": ["Free delivery by Mon"],
    "product_id": "16515621927653627757"
  },  {
    "position": 5,
    "title": "Nike Kids' Air Max 270",
    "price": "$99.00",
    "extracted_price": 99,
    "original_price": "$132",
    "extracted_original_price": 132,
    "source": "Nike",
    "multiple_sellers": true,
    "rating": 4.6,
    "reviews": 5800,
    "tag": "25% OFF",
    "extensions": ["Free delivery by Mon"],
    "product_id": "2998018693582404652"
  },  {
    "position": 6,
    "title": "Nike Men's Air Max Torch 4 Running Shoes",
    "price": "$75.00",
    "extracted_price": 75,
    "original_price": "$100",
    "extracted_original_price": 100,
    "source": "Nike",
    "multiple_sellers": true,
    "rating": 4.7,
    "reviews": 3100,
    "tag": "25% OFF",
    "extensions": ["Free delivery on $75+"],
    "product_id": "14220130338569971496"
  }]

Six cards are marked here; the response carries forty. Prices arrive twice over: as the string Google printed and as a number, so a currency symbol never gets in the way of a comparison.

search_metadata

search_metadata fields
fieldtypedescription
idstringUnique id of this search, srx_ prefixed.
statusstringsuccess on every 200.
created_atstringWhen the search ran, ISO 8601 UTC.
took_msintegerEnd-to-end time to serve this response, in milliseconds.
parser_versionstringDated version of the shopping parser that produced this response, so any response can be tied to the parsing behaviour that made it. Each vertical carries its own.
google_urlstringThe exact Google URL fetched.
html_urlstringArchived copy of the results page exactly as served. Included when available.
json_urlstringArchived copy of this JSON response. Included when available.

search_parameters

The parameters the search actually ran with, defaults filled in.

search_parameters fields
fieldtypedescription
qstringThe query as received.
glstringCountry shopped in, which set the currency and the merchants.
hlstringLanguage used.
devicestringDevice profile used.
pageintegerPage served.

shopping_results

shopping_results entry fields
fieldtypedescription
positionintegerRank within the grid, starting at 1.
titlestringProduct name, as the card prints it.
pricestringCurrent price with its currency symbol, exactly as displayed.
extracted_pricenumberThe same amount as a number.
original_pricestringThe struck-through price, on cards where Google shows one.
extracted_original_pricenumberThe same amount as a number.
sourcestringMerchant as displayed.
multiple_sellersbooleanTrue when the card says the offer has other sellers: Google prints & more beside the merchant.
ratingnumberStar rating out of five.
reviewsintegerNumber of reviews behind that rating, as a number: Google's 13K comes back as 13000.
tagstringThe badge across the photo, such as 25% OFF or LOW PRICE.
extensionslistEverything else the card prints, as printed: delivery promises, return windows, stock notes.
product_idstringGoogle's own identifier for the product.
thumbnailstringProduct photo, inline.

pagination

Ask for the next page with page. pagination.next is present only while the page you asked for came back full, so its absence is how you know you have reached the end of the vertical.

pagination fields
fieldtypedescription
currentintegerThe page this response is.
nextstringGoogle URL for the next page. Absent on the last one.

Errors

A rejected request answers with detail, naming the parameter and what to send in its place. A search that could not be completed answers with error and a search_id you can quote. No error is ever charged.

Status codes
fieldtypedescription
401unauthorizedMissing, malformed, or revoked key. See Authentication.
402payment requiredThe balance can't cover a search. See Credits & billing.
422invalid requestUnknown or invalid parameter. The detail names the parameter and what to send in its place.
502search_failedThe search could not be completed. Retry it; you were not charged.
503search_unavailableGoogle did not serve a usable results page. Retry it; you were not charged.
502 · search failed
HTTP/2 502
content-type: application/json
x-serpix-credits-charged: 0

{ "error": { "code": "search_failed", "search_id": "srx_kq3v9tR2xLw8mA1c" } }