Price Tracker & Monitor — Any Retailer's Prices & Stock avatar

Price Tracker & Monitor — Any Retailer's Prices & Stock

Pricing

from $1.20 / 1,000 product priceds

Go to Apify Store
Price Tracker & Monitor — Any Retailer's Prices & Stock

Price Tracker & Monitor — Any Retailer's Prices & Stock

Paste product URLs from any store and get the price, currency, stock, list price and discount for each, read from the page's own structured data. Monitor mode remembers the last run and returns price drops, rises, restocks and sell-outs with a price history. No browser, no key.

Pricing

from $1.20 / 1,000 product priceds

Rating

0.0

(0)

Developer

Insight Solutions

Insight Solutions

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a day ago

Last modified

Security

Limited permissions

Share

The price, stock and list price of any product URL — and, run on a schedule, every price drop, rise, restock and sell-out since the last run. Paste product page URLs from any store — a Shopify brand, a WooCommerce shop, Apple, Staples — and get one row per product: price, currency, availability, list price and discount, brand, SKU, GTIN, rating, image, the method that found the price and a confidence grade. JSON, CSV or Excel.

Most product pages publish their price as structured data for search engines and social previews: JSON-LD Product and Offer, microdata, Open Graph product tags, Shopify's .js product endpoint, WooCommerce's public Store API, the state a JavaScript storefront embeds in its own HTML. This Actor reads that — HTTP only, no browser, no login, no API key — and only falls back to the visible price, marked low, when there is nothing else. No bot wall is ever bypassed: a retailer that refuses the request is one free blocked row, after one request.

$2.00 per 1,000 products priced and $3.00 per 1,000 changes detected on the FREE tier; blocked and unpriced pages are free.

Try it in 30 seconds

This is the Store prefill; paste it and run:

{ "urls": [
"https://www.allbirds.com/products/mens-cruiser-medium-grey",
"https://www.allbirds.com/products/womens-tree-runner-nz-medium-grey",
"https://www.brooklinen.com/products/luxe-core-sheet-set",
"https://www.brooklinen.com/products/classic-core-sheet-set",
"https://www.apple.com/shop/buy-iphone/iphone-17",
"https://www.staples.com/hammermill-copy-plus-8-5-x-11-copy-paper-20-lbs-92-brightness-5000-sheets-carton-105007/product_122374",
"https://www.gymshark.com/products/gymshark-crest-hoodie-black-ss22",
"https://barefootbuttons.com/product/max-bore-standard-silver-footswitch-topper/",
"https://colourpop.com/products/mocha-moment-cloud-whip-lip-liner-fresh-kiss-glossy-balm"
], "mode": "snapshot" }

The prefill is 8 (or 9) product pages — nine URLs on six stores (Shopify, WooCommerce and custom storefronts), every one on a store that priced through Apify's default datacenter proxy in our live smoke, so most rungs of the ladder below get used every day. On the pinned copies in our test suite (captured 2026-11-03): Allbirds' Men's Cruiser $105 through Shopify's .js endpoint, the iPhone 17 from $899 (to $1,129) and the Hammermill paper $39.99 from JSON-LD, the Gymshark Crest hoodie $40 — its headless storefront has no .js endpoint, so JSON-LD answers — Brooklinen's Luxe sheet set $169 from the state its page embeds, and the Barefoot Buttons topper $9.95 from the WooCommerce Store API. A live run prices the other three as well (a second Allbirds and a second Brooklinen product, and ColourPop's Mocha Moment).

Watch a list and get only what changed:

{ "urls": ["https://www.allbirds.com/products/mens-cruiser-medium-grey", "https://www.staples.com/hammermill-copy-plus-8-5-x-11-copy-paper-20-lbs-92-brightness-5000-sheets-carton-105007/product_122374"], "mode": "monitor", "watch": ["price", "availability", "discount"], "stateKey": "my-watchlist" }

The first run is the baseline. Schedule it — hourly, daily — and every later run adds a change row for each price move, restock, sell-out or discount.

From code — client.actor("insight.solutions/price-tracker-api").call(run_input={"urls": [...]}) with apify-client, or POST https://api.apify.com/v2/acts/insight.solutions~price-tracker-api/run-sync-get-dataset-items.

What comes back

A product row (from the pinned Allbirds page, priced through Shopify's .js; null columns cut):

{
"rowType": "product",
"url": "https://www.allbirds.com/products/mens-cruiser-medium-grey",
"canonicalUrl": "https://www.allbirds.com/products/mens-cruiser-medium-grey",
"retailer": "allbirds.com",
"platform": "shopify",
"name": "Men's Cruiser - Medium Grey (Blizzard Sole)",
"brand": "Allbirds",
"sku": "A11599M090",
"gtin": "196942184509",
"price": 105,
"currency": "USD",
"availability": "in_stock",
"availabilityText": "available",
"condition": "new",
"image": "https://cdn.shopify.com/s/files/1/1104/4168/files/A11559_25Q3_Cruiser_Dark_Navy_Blizzard_PDP_LEFT-2000x2000_9f5ba943-6b25-4f75-9c74-edd67a6a0aa7.png?v=1751900449",
"variantCount": 14,
"priceMin": 105,
"priceMax": 105,
"extractionMethod": "shopify-js",
"confidence": "high",
"httpStatus": 200
}

A change row (monitor mode; an illustrative price drop on the same product):

{
"rowType": "change",
"url": "https://www.allbirds.com/products/mens-cruiser-medium-grey",
"retailer": "allbirds.com",
"name": "Men's Cruiser - Medium Grey (Blizzard Sole)",
"changeType": "price-down",
"previousPrice": 105,
"price": 84,
"currency": "USD",
"changeAbs": -21,
"changePct": -20,
"previousAvailability": "in_stock",
"availability": "in_stock",
"previousSeenAt": "2026-11-03T06:00:12.000Z",
"seenAt": "2026-11-04T06:00:09.000Z"
}

A diagnostic row — free — for a retailer behind a bot wall:

{
"rowType": "diagnostic",
"url": "https://www.walmart.com/ip/Apple-AirPods-4/11381374703",
"retailer": "walmart.com",
"errorType": "blocked",
"wallVendor": "perimeterx",
"httpStatus": 200,
"error": "walmart.com answered with a bot wall (perimeterx; redirected to the retailer's /blocked page). Walmart refused both datacenter and residential exits in our probe, and this Actor never bypasses a bot wall, so it is unsupported. Nothing was charged."
}

With includeVariants, one variant row per priced variant (title "8", options {"Size": "8"}, its own sku, gtin, price and available). Every run ends with a free summary row: pages read, priced, by method, blocked by retailer, changes by type.

Use cases

  • Price-drop alerts — monitor mode on a watchlist, scheduled hourly or daily; the changes view is the feed, a webhook or the Slack integration delivers it.
  • Competitor price monitoring — the same products across brand sites and stores, one schema whatever the platform, with list price and discount so promotions show up as discount-started.
  • Restock watching — back-in-stock and out-of-stock rows for the products you are waiting on, or that your customers keep asking about.
  • MAP and reseller compliance — the advertised price on each reseller's own product page, with the seller organisation and the date it was read.
  • Catalogue and feed QA — check that your own product pages publish the price, currency and stock that your feed says, in the structured data search engines read.
  • Dashboards — every product row carries priceHistory[] in monitor mode, so a chart needs nothing but the dataset.

Tracking homes rather than products? Rightmove Property API returns UK listings for sale and to rent by area with their price, status and last update — run it daily and compare price by listingId for reductions.

How it compares

  • Any retailer, one Actor. Not one scraper per store: any product page that publishes structured data — which is most of them — is read the same way, into the same columns.
  • Structured data first. The price comes from what the page publishes for machines (JSON-LD, microdata, Open Graph, the platform's own endpoint), not from guessing at its layout, so a redesign does not break it. Every row says which rung answered and how much to trust it.
  • Change detection built in. Monitor mode keeps the last observation of every URL and returns only what moved, with the previous price, the percentage and a price history — no diffing on your side.
  • Honest about walls. A retailer that refuses automated requests is reported as blocked, free, and named in the README's table. Nothing is bypassed, and nothing is billed for it.
  • HTTP only. No browser: a product page costs one request (two on Shopify), so a thousand products cost cents in platform usage.

How a price is read

Every URL climbs this ladder and stops at the first rung that yields a price. The other rungs' facts still fill the row's empty columns (a .js price with the JSON-LD rating, an embedded-state price with the Open Graph image), never its price.

RungextractionMethodWhat is readconfidence
0shopify-jsA Shopify-served /products/<handle> page: GET <path>.js — every variant with price, compare_at_price, available, sku, barcode (prices in cents, converted); the currency from the page's own Shopify.currencyhigh
0woo-store-apiA /product/<slug>/ URL: WooCommerce's public Store API, wp-json/wc/store/v1/products?slug=<slug> — prices in minor units with currency_minor_unit, is_in_stock, stock wording, sku. Asked first; when the site has no Store API the page is readhigh
1json-ldJSON-LD Product, ProductGroup (its hasVariant[]) or ProductModel, with Offer / AggregateOffer and nested PriceSpecification (a SalePrice is the price, a ListPrice or StrikethroughPrice the list price), availability, priceValidUntil, itemCondition, seller, sku, gtin*, mpn, brand, image, aggregateRatinghigh
2microdataitemtype=schema.org/Product with itemprop=price, priceCurrency, availabilitymedium
3opengraphproduct:price:amount / og:price:amount (and product:sale_price:amount), product:price:currency, product:availability, a twitter:data labelled "Price"medium
4embedded-state__NEXT_DATA__, window.__NUXT__, __PRELOADED_STATE__, __INITIAL_STATE__, dataLayer.push({ecommerce: …}), digitalData, <script type="application/json"> product blocks — walked breadth-first to depth 12, at most 2 MB of JSON per block, for the first object with a price-like key (price, currentPrice, salePrice, offerPrice, priceAmount, unitPrice, or a {value} under price) and a name or SKU beside it; an object named like the page's title wins over an earlier onemedium
5visible-textallowTextFallback (on by default): the first currency amount inside an element marked as a price (class with "price", data-price, aria-label, itemprop), skipping struck-through, "was", "compare at" and per-month amountslow

Which node is the page's. A product page often carries other products' JSON-LD — a "customers also bought" carousel, related items, a ProductGroup's 136 variants on Allbirds' page. The node used is the one whose url or @id is the page (or its canonical), else one with no URL of its own; a page whose structured data is several other products is a listing. Review authors and other Person nodes are removed before anything is read; reviews themselves never are.

Prices are read as written in any notation — 95, "169.0", "1,299.00", "1.299,00", "10,99", "$19". A zero price is a placeholder, not a price. An AggregateOffer's lowPrice is the price and its highPrice the top of the range (priceMax), never a list price. listPrice comes from Shopify's compare_at_price, the Store API's regular price on sale, a ListPrice / StrikethroughPrice specification, a struck-through amount or a sale-price meta tag, and only when it is higher than price; discountPct is computed from the two.

Currency: the rung's own (priceCurrency, Shopify.currency, the Store API's currency_code, product:price:currency, the symbol next to a visible amount), else your currencyHint, else the domain — a country-code TLD (.co.uk → GBP, .de → EUR) or a country in the path (/us/en/ → USD) — else null.

Availability is one of in_stock, out_of_stock, preorder, backorder, discontinued, limited or unknown, from schema.org's values with or without their URL (https://schema.org/InStock, InStock), Open Graph's (in stock, oos) and the Store API's. A retailer's own value — IKEA uses an internal URL in one of its JSON blocks — is unknown, and availabilityText keeps what the page said.

Not a product page. A category, collection, search, shop or home page — by its URL (/browse/, /c/, /collections/<x>, /search, /shop/, Best Buy's ….c?id=) or by structured data that is a list of other products — is a free not-a-product-page row, never a price taken from a tile.

Which retailers work

From our probe of 2026-11-03, through Apify's US datacenter proxy and a US residential exit. This Actor never bypasses a bot wall — no CAPTCHA solving, no signed-API reverse engineering — so a retailer that refuses both is listed as unsupported, and its URLs come back as free blocked rows naming the wall's vendor.

Works on datacenter (the default)Needs residential / intermittentUnsupported — active bot wall
Shopify stores (Allbirds, Brooklinen, Gymshark, ColourPop, Kylie Cosmetics), WooCommerce stores (Barefoot Buttons), Apple, Staples, and most brand sites that publish JSON-LD. Best Buy's and Nordstrom's category pages answered too (and are returned as not-a-product-page; their product pages were not probed)LEGO (Cloudflare on datacenter; its product page priced on residential), Bombas (a Vercel checkpoint on datacenter; residential got through). Intermittent: IKEA — its Cloudflare challenge comes and goes on datacenter IPs (open in our probe, a 403 challenge in the live smoke the same day); run again or use residential. Others as users reportAmazon, Walmart, Target, Home Depot, Lowe's, Newegg, Chewy, Petco, Wayfair, REI, B&H, Adorama, Sweetwater, Crutchfield, Micro Center, Ace Hardware, Harbor Freight, Dick's, Backcountry, Build.com, Kroger, Walgreens, Safeway, Menards — refused on datacenter, and the big boxes that were tried on residential (Walmart, Home Depot, Lowe's, Newegg, Chewy, REI, B&H) refused their product and category pages there too

The walls seen, and how they are recognised (each from a captured refusal): Cloudflare's "Just a moment..." challenge and cf-mitigated: challenge; Akamai's "Access Denied" and edge refusals; PerimeterX's px-captcha (Walmart's "Robot or human?" behind its /blocked?url= redirect); Kasada's x-kpsdk-* headers; Imperva's x-iinfo and _Incapsula_Resource; AWS WAF's x-amzn-waf-action: challenge; Vercel's Security Checkpoint; DataDome's x-datadome; CloudFront's "Request blocked". Any 401, 403, 429 or 503 is a refusal too. A wall ends that URL after one request: no retry, no second endpoint. Williams-Sonoma refused its product page on datacenter and was not tried on residential; the blocked row's advice says to try residential once.

Monitor mode

mode: "monitor" keeps the last observation of every URL in a named key-value store — price-tracker-api-state, or price-tracker-api-state-<stateKey> — so the next run, scheduled or not, has something to compare with. (A run's default store starts empty every run, so it cannot remember yesterday.)

  • The first run is the baseline: product rows, no change rows.
  • After that, a change row for each of: the price moving by at least minChangePct percent (price-up, price-down; 0, the default, reports every move); availability flipping between buyable and not (back-in-stock, out-of-stock); and, with "discount" in watch, a list price appearing or disappearing (discount-started, discount-ended). An unknown availability on either side is an extraction gap and never a flip; a change of currency is never a price move.
  • Every product row carries previousPrice, previousAvailability, previousSeenAt, changePct and priceHistory[] — one point per UTC day (date, price, availability) for the last historyDays days. A second run on the same day replaces that day's point.
  • A failed check is not an observation. A page that was blocked, missing, unreachable or unpriced this run leaves its stored observation exactly as it was, so a wall is never reported as a sell-out and a fetch error never as a price change.
  • One run per store at a time. A run lock stops a second run on the same store — it would compare against the same observations and double-report every change — with a FAILED status saying so. Give concurrent schedules different stateKeys. Each URL's record is checked against an 8 MB ceiling before it is written, and records not seen for 90 days are deleted; the store keeps up to 90 days of history per URL.
  • Delivered before stored. A URL's new observation is saved only after its rows are in your dataset, so a run that stops at your maximum charge reports the change on the next run instead of losing it.

Input reference

FieldTypeDefaultWhat it does
urlsstring[]prefill: 9 productsProduct page URLs, up to 2,000. A missing scheme becomes https://; fragments and tracking parameters (utm_*, gclid, fbclid, …) are ignored when matching a URL to its earlier observations; ?variant= is kept. The same product twice is read once.
modeselectsnapshotsnapshot reads and returns; monitor also compares with the last observation (see Monitor mode).
watchstring[]price, availabilityMonitor mode: which changes become rows — price, availability, discount.
minChangePctnumber0Monitor mode: the smallest price move, in percent, that is a change row.
historyDaysinteger30Monitor mode: 1–90 days of priceHistory[] on each product row.
stateKeystring""Monitor mode: a watchlist name; each name has its own store, history and lock.
includeVariantsbooleanfalseOne variant row per priced variant (Shopify, JSON-LD ProductGroup), each billed as a product.
allowTextFallbackbooleantrueRung 5, the visible price, confidence: low. Off: structured data only.
currencyHintstring""An ISO 4217 code used only when the page names no currency.
requestTimeoutSecsinteger305–120 seconds per request. A timeout is not retried. Some large product pages (Apple's, a few WooCommerce shops) take over 20 s through a proxy.
maxConcurrencyinteger81–32 pages in parallel, each through its own proxy session.
maxRunSecsinteger24030–3,600. URLs not reached in time get a free timeout row; rows already returned are kept, and billed.
proxyConfigurationobject{ "useApifyProxy": true }Every request goes through it. Datacenter by default; the RESIDENTIAL group for the retailers in the middle column above.

A run with nothing usable — no fetchable URL, an unknown mode or watch value, a currencyHint that is not an ISO 4217 code — finishes FAILED before a single request, with a free invalid-input row saying why. Bad entries among good ones are free invalid-input rows and the rest run.

Output reference

Every row has the same columns, null where a column does not apply, so the dataset exports as one table. rowType is product, variant, change, diagnostic or summary.

On every row: ok, rowType, scrapedAt; on every row but the summary, input (your entry, as typed), url and retailer (the registrable domain).

Product rows (paid): canonicalUrl, platform (shopify · woocommerce · magento · bigcommerce · salesforce · custom), name, brand, sku, mpn, gtin, price, currency, listPrice, discountPct, availability, availabilityText, priceValidUntil, condition, seller (an organisation, or null), rating, reviewCount, image, variantCount, priceMin, priceMax, extractionMethod, confidence, rungsTried[], httpStatus, fetchMs; in monitor mode also previousPrice, previousAvailability, previousSeenAt, changePct, priceHistory[], seenAt.

Variant rows (paid as products): parentUrl, variantId, title, options{}, sku, gtin, price, currency, listPrice, discountPct, available, availability.

Change rows (paid, monitor mode): name, changeType, previousPrice, price, currency, changeAbs, changePct, listPrice, discountPct, previousAvailability, availability, previousSeenAt, seenAt.

Diagnostic rows (free): errorType, error, and where they apply httpStatus, wallVendor, title (the page's <title>), platform, rungsTried[], fetchMs.

Summary row (free, last): pagesRead, priced, byMethod{}, blockedByRetailer{}, changesByType{}, diagnosticsByType{}, and the run's status line in error.

errorTypeMeaning
blockedA bot wall answered (wallVendor names it). One request, no retry; the row carries the advice from Which retailers work.
no-price-foundThe page was read and no rung found a price; rungsTried lists them.
not-a-product-pageA category, collection, search, shop or home page.
not-foundHTTP 404 or 410: the product page is gone.
unreachableA network error, a 5xx after one retry, or another 4xx.
timeoutThe page did not answer within requestTimeoutSecs, or the run's maxRunSecs ran out before it was reached.
invalid-inputAn entry or a setting that cannot be used: see Input reference.
state-lockedMonitor mode: another run holds this store's lock. The run is then FAILED.
state-write-failedMonitor mode: an observation could not be saved; the next run compares with the one before.
budgetYour maximum total charge was reached before this URL's rows could be delivered.
internalThis Actor's own error. The run is then FAILED.

Pricing

EventWhat it isFREEBRONZESILVERGOLD
actor-startOnce per run, only after the run has returned a paid row$0.001$0.001$0.001$0.001
productOne product URL priced (and one per variant row)$0.002$0.002$0.0016$0.0012
changeOne price or stock change (monitor mode)$0.003$0.003$0.0024$0.0018

$2.00 per 1,000 products priced, plus $3.00 per 1,000 changes detected.

RunCost
The prefill, 9 URLs, as the offline test suite runs it — 6 priced$0.013
The same live, all 9 priced$0.019
A 500-product watchlist, daily, 20 changes a day$1.061 a day
The same at GOLD$0.637 a day
100 Shopify products with includeVariants, 6 variants each$1.401
50 URLs on walled retailers$0.00, SUCCEEDED

Charging is charge-after-push: rows are in your dataset before the event is recorded. A product is delivered whole or not at all — its product row, its variant rows and its change rows are checked against ACTOR_MAX_TOTAL_CHARGE_USD together — and the run stops reading when the next product would not fit, finishing SUCCEEDED and saying so. Never charged: blocked, missing, unreachable and unpriced pages, category pages, invalid entries, the summary row, and a run that prices nothing (start fee included). A run that read pages and priced none is SUCCEEDED: "Read N pages, no price found on any (M blocked)".

Limits

  • No walls bypassed. A retailer behind an active bot wall is unsupported, not slow. Residential proxy helps only the brand sites in the middle column; the big boxes refused it.
  • Marketplaces return the featured offer. A page listing several sellers' offers gives the first priced one as price and seller; priceMin and priceMax span the rest.
  • The price the proxy's country sees. Prices, currency and stock are what the page serves to a visitor from the proxy's exit — a US exit by default. Retailers that localise by IP or cookie may show another country's price on another exit; set proxyConfiguration to the country you want.
  • What the page publishes. A price drawn only by JavaScript after load, with no structured data, embedded state or price element in the HTML, is not seen (no-price-found). Brooklinen builds its JSON-LD in the browser, which is why its page is read from embedded state.
  • Selected options. The price is the page's default: the variant ?variant= names on Shopify, else the first available one; the cheapest variant of a ProductGroup when the page names none. includeVariants returns them all.
  • Visible text is a guess. Rung 5 is marked low because it is one; switch allowTextFallback off for structured data only.
  • Sizes and caps: up to 2,000 URLs per run; embedded state is walked to depth 12 and at most 2 MB of JSON per block; one stored observation per URL, at most 8 MB, deleted after 90 days unseen; 90 days of history per URL.
  • Not affiliated with any retailer, platform or company named here. Names are used descriptively, to say what was read.

Use it from an AI agent, or from code

One JSON object in, one flat array out — the shape agent runtimes want. The Actor runs with limited permissions and pay-per-event pricing, so it works over the Apify MCP server (mcp.apify.com) and with agentic (x402) payments. The Integrations tab pushes results to Slack, a webhook, Zapier, Make, Google Sheets, Snowflake or BigQuery, and Apify Schedules run monitor mode unattended — the changes dataset view is then exactly what moved.

curl -X POST "https://api.apify.com/v2/acts/insight.solutions~price-tracker-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"urls":["https://www.allbirds.com/products/mens-cruiser-medium-grey","https://www.staples.com/hammermill-copy-plus-8-5-x-11-copy-paper-20-lbs-92-brightness-5000-sheets-carton-105007/product_122374"]}'
# pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("insight.solutions/price-tracker-api").call(run_input={
"urls": ["https://www.allbirds.com/products/mens-cruiser-medium-grey"],
"mode": "monitor",
"watch": ["price", "availability", "discount"],
"stateKey": "my-watchlist",
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if row["rowType"] == "change":
print(row["changeType"], row["name"], row["previousPrice"], "→", row["price"], row["currency"], row["url"], sep=" | ")

Building an app with Lovable? Apify's Lovable connector runs any Actor from inside a Lovable project with your Apify token. Connect Apify in Lovable once, then ask it: "Use Apify to run insight.solutions/price-tracker-api on my watchlist every hour and build a price-drop dashboard around the results." A run of a few dozen products finishes well inside Lovable's 300-second synchronous limit at the default settings, and every result is one JSON object per row — with priceHistory[] on each product in monitor mode — so the generated app needs no extra parsing. Usage bills to the Apify account whose token is connected.

FAQ

Why is a page I can open in my browser blocked? Your browser runs the retailer's bot checks — JavaScript challenges, device fingerprints — and passes them; an HTTP client from a cloud address does not, and this Actor does not try to. Try residential proxy once; if it is still refused, the retailer is unsupported.

Why does a row say confidence: low? No structured data on the page carried a price, so the visible price was read (visible-text). It is usually right and occasionally a finance offer's or a bundle's price. Switch allowTextFallback off to drop such rows.

Why is availability unknown? The page's structured data did not say (Apple's iPhone page publishes a price range and no stock state), or said it in the retailer's own vocabulary. availabilityText shows what was there. In monitor mode unknown never triggers a stock change.

Why did the first monitor run report no changes? It is the baseline: there is nothing to compare with yet. The second run is the first with change rows.

Can I track the price of each size or colour? Yes: includeVariants: true returns a variant row per priced variant on Shopify products and JSON-LD ProductGroup pages. Or watch the variant's own URL (?variant= on Shopify) as its own entry.

Which currency is the price in? The page's own, almost always. When the page does not say, your currencyHint, then the domain (.co.uk, /us/en/); with none of them, currency is null rather than a guess.

Does it read reviews? No. Only the aggregate rating and reviewCount. No review text and no reviewer is ever read.

  • Public pages only. Product pages, Shopify's public product endpoint and WooCommerce's public Store API, as any anonymous visitor receives them. It never logs in, never supplies cookies or session tokens, never accepts credentials, and never bypasses a bot wall, CAPTCHA or access control: a refusal is reported, not worked around.
  • Catalogue data, not people. Rows describe products, prices and the organisations that sell them. seller is kept only when it is an organisation — a marketplace seller with a person's name is null. Reviews and reviewers are never read; Person nodes are removed from structured data before any field is taken.
  • We are not a data broker. This Actor holds no database and sells no list. It reads public pages on demand and writes the result to your dataset on your account; monitor mode's store is in your account too.
  • Respect the sites you read. Keep maxConcurrency modest on a small store, and check a site's terms before monitoring it at high frequency. Prices are what the page showed at scrapedAt and may differ at checkout.
  • Not affiliated with, endorsed by or sponsored by any retailer, platform or company named here. Names and trademarks are used descriptively.
  • You are responsible for how you use the output, including competition law, consumer-protection rules on price claims, and the terms of the sites you monitor.

Our other Actors

Every Insight Solutions Actor is pay-per-result with no browser, no login and no API key, and every one of them returns free diagnostic rows instead of billing for failures. Prices are per 1,000 results.

Video, audio & social

News, documents & the web

Business, finance & jobs

Apps & games