Product Price Checker – Any Store URL (price, stock, old → new)
Pricing
from $3.00 / 1,000 url checkeds
Product Price Checker – Any Store URL (price, stock, old → new)
Paste product URLs from ANY online store and get price, stock availability and compare-at price, plus old → new change detection on scheduled runs. Plain HTTP (no browser, no proxies), honest per-URL status: ok / no_price / blocked / not_found / error / skipped.
Pricing
from $3.00 / 1,000 url checkeds
Rating
0.0
(0)
Developer
Thongpoo Supong
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
20 hours ago
Last modified
Categories
Share
Paste product URLs from any online store, get price, currency, compare-at
price and stock availability read straight from the page's own structured data
(JSON-LD, microdata, OpenGraph) over plain HTTP — no login, no browser, no
proxies. The actor remembers every URL and, on scheduled runs, emits only what
changed (old → new): a product whose price moved looks like
previousPrice: 24.99 → price: 29.99 in a single row, no dataset joins.
Status: M3 (pay-per-event url-checked, default full list, changes mode for
scheduled monitoring).
Billing: url-checked $0.003, charged once per URL whose price was actually
read (status: ok). Nothing is charged for blocked, not-found, error or
no-price URLs. See PRICING.md.
Input
| Field | Type | Default | Notes |
|---|---|---|---|
productUrls | array[string] | required | full product page URLs, e.g. ["https://singer.com/products/singer-smart-heavy-duty-9120-sewing-embroidery-machine", "https://www.nutrimuscle.com/products/native-iso-blend"] |
onlyChanges | boolean | false | Turn ON for scheduled monitoring: emit only rows whose price / compareAtPrice / inStock changed since the previous run, plus URLs never seen. OFF (default) emits a row for every URL. The first run of a URL is always emitted (it builds the baseline). |
priceChangedOnly | boolean | false | further restrict to rows where price or compareAtPrice changed (availability-only moves are skipped) |
maxItems | integer | 1000 | hard cap on dataset rows pushed in one run |
fallbackToVisiblePrice | boolean | true | if no structured data, try a conservative regex on the HTML near a price class/id/itemprop; result gets source: "html-heuristic", confidence: "low" |
URLs are fetched with ≤ 2 concurrent requests per host, ≥ 400 ms between
requests to the same host, 25 s timeout, 3× retries with backoff on
network/5xx/429 (Retry-After honoured, capped at 60 s). Response bodies are
capped at 2 MB. Big brands that answer 403/429 with a JS challenge are reported
blocked and are never bypassed (no header spoofing, no proxies, no
browser).
Scheduling recipe
For a daily or weekly price/stock alert, schedule the actor with onlyChanges
turned on:
{"productUrls": ["https://singer.com/products/singer-smart-heavy-duty-9120-sewing-embroidery-machine","https://ugmonk.com/products/analog-starter-kit-black","https://www.nutrimuscle.com/products/native-iso-blend"],"onlyChanges": true,"priceChangedOnly": false,"maxItems": 1000,"fallbackToVisiblePrice": true}
- The first run of each URL is always emitted (
isNew: true) — that is the paid baseline. Every run after that emits only the moves. - A scheduled run with nothing changed pushes 0 rows but still checks every
URL and writes a
SUMMARY(you pay only theokchecks). - Keep the actor's named key-value store
product-url-price-checker-statebetween runs — that is the snapshot it diffs against. Wiping it resets every tracked URL to "new". - Only runs that actually read a price (
status: ok) write the snapshot. A blocked / not-found / no-price check never overwrites the stored entry, so a temporary 403 can't be mistaken for a price change, and the previous known price stays on the row for context. - The default input (
onlyChanges: false) always yields a row per URL, so an unscheduled "run now" or a daily auto-test is never empty.
Output
One dataset row per URL (one row even for no_price / blocked / not_found /
error URLs, so you see every URL in the table). Every row carries the previous
values, so a change reads old → new without any join:
| Field | Type | Example |
|---|---|---|
url / finalUrl | string | https://singer.com/products/… / final URL after redirects |
status | string | ok |
title | string|null | SINGER® Smart Heavy Duty 9120™ … |
price | number|null | 499.99 |
currency | string|null | USD |
compareAtPrice | number|null | 199.99 (only when it exceeds the price) |
inStock | boolean|null | true |
sku / gtin / brand | string|null | from structured data |
imageUrl | string|null | product image |
source | string | jsonld | microdata | opengraph | html-heuristic | none |
confidence | string | high | medium | low |
platformHint | string | shopify | woocommerce | magento | bigcommerce | prestashop | unknown (informational) |
isNew | boolean | true on the first run of a URL |
priceChanged / compareAtChanged / availabilityChanged | boolean | what moved vs the previous run |
changeType | string | new | price | compareAt | availability | unchanged | none |
previousPrice / previousCompareAtPrice / previousInStock / previousCheckedAt | …|null | previous run's values |
checkedAt | ISO string | when the URL was checked |
httpStatus | int|null | final HTTP status |
The dataset ships two built-in views: Overview (every field, image
column included) and Changes (old → new) with URL, title, changeType and the
before/after price, stock and compare-at columns. A SUMMARY key-value record
lists one slim status row per URL (url, finalUrl, status, price,
source).
Per-URL statuses (never silent empty output)
| Status | Meaning |
|---|---|
ok | price found and read |
no_price | page answered 200 but no price via any enabled source |
blocked | 403 / 429 / CAPTCHA / JS-challenge page — reported, never bypassed |
not_found | 404 / 410 |
error | network or 5xx failure after retries |
skipped | the run stopped before this URL: maxItems reached or the pay-per-event charge limit was hit |
Only ok rows are charged (one url-checked), and ok rows are charged even
if onlyChanges suppresses the push — the price was read. Every other status is
free: you get the loud status row in the table instead. If zero URLs are
ok the run fails loudly with the status breakdown — never a silent empty
dataset that looks like success.
Pair with the store-monitor
For whole-store monitoring (a Shopify storefront and all its variants in one fetch) use Shopify Price & Stock Monitor — this actor takes individual URLs from any platform and is the cheapest way to watch a short list of specific products.
What is NOT collected
- No personal data: no emails, phone numbers, names+contacts of private individuals, social profiles, customer/user records or reviews are ever read or output (enforced by a unit test).
- No login content: pages that need authentication or a password are out of scope.
- No bypassing: CAPTCHA/paywall/login walls, browsers, proxy rotation or header spoofing are never attempted.
Limitations
- JS-only stores: if a store renders its price entirely in JavaScript with
no JSON-LD/microdata/OpenGraph fallback on the raw HTML, the URL comes back
no_price(orhtml-heuristiclow-confidence when it matches). This actor deliberately has no browser. - Geo-priced stores: prices that depend on your IP's country come back as whatever the plain HTTP response shows; no geo-spoofing is performed.
- Blocked big brands: Dyson, LEGO, Sonos, Helly Hansen, Gymshark, Bombas
and similar answer 403/429 to plain requests — expected, reported
blocked, never evaded. - First run = baseline: every URL is
newonce (or again if the state store was wiped). - State = ok checks only: the snapshot is written only when a price is read
(
status: ok).blocked/no_price/not_foundruns never change it, so the last known-good price survives temporary outages without faking a change; an unchanged URL whose row is suppressed byonlyChangesstill refreshes its stored timestamp so it is never re-reported as "new" after 120 days.