Shopify Catalogue + Price & Stock Monitor avatar

Shopify Catalogue + Price & Stock Monitor

Pricing

Pay per usage

Go to Apify Store
Shopify Catalogue + Price & Stock Monitor

Shopify Catalogue + Price & Stock Monitor

Pull any Shopify store's full public catalogue from its own /products.json endpoint — every product and every variant in one normalised schema with stable IDs — and run it on a schedule as a price and stock change monitor that tells you what changed and what it was before.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Daniel James

Daniel James

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Categories

Share

Pull any Shopify store's full public catalogue — every product, every variant — from the store's own /products.json endpoint, normalised to one clean schema with stable IDs. Then run it on a schedule and it becomes a price and stock change monitor that tells you not just that something changed, but what changed and what it was before.

No browser, no proxy, no HTML parsing, no API key. This is the merchant's own public product feed, on the merchant's own domain, served by Shopify for exactly this purpose — which makes it fast, reliable and compliance-friendly.

What you get

One row per variant, because price and stock live on the variant. A monitor that only watched the product would miss "size 9 sold out" — the single most valuable signal in retail data.

{
"id": "a4e027feb0623770059af879b31c256dbce0d577",
"store": "allbirds.com",
"productId": "7199699927120",
"variantId": "41243839496272",
"handle": "mens-strider-medium-grey",
"title": "Men's Strider - Medium Grey (Blizzard Sole)",
"variantTitle": "8",
"sku": "A11718M080",
"vendor": "Allbirds",
"productType": "Shoes",
"price": 91,
"compareAtPrice": 130,
"currency": "USD",
"onSale": true,
"discountPercent": 30,
"available": false,
"productAvailable": false,
"availableVariantCount": 0,
"variantCount": 13,
"productUrl": "https://allbirds.com/products/mens-strider-medium-grey?variant=41243839496272",
"imageUrl": "https://cdn.shopify.com/s/files/…"
}
  • Prices are numbers. Shopify ships money as strings ("91.00"), so sorting a competitor's catalogue by price gives you nonsense unless somebody parses it. We parse it.
  • Stable IDssha1(store:productId:variantId). The same variant keeps the same id across every run, so joins and diffing downstream just work.
  • Deep links to the exact variantproductUrl includes ?variant=…, so a click lands on the right size, not the product page.
  • Sale maths doneonSale and discountPercent, with the fake discounts removed (see below).
  • No description HTML — it is the bulk of the payload, nobody sorts by it, and under pay-per-event you would be paying for it.

Monitor mode (changesOnly)

Turn any store into a price feed. The actor remembers each store's state between runs and emits only what moved:

changeTypeMeaning
newa product or a variant that wasn't there last run — a launch, or a new colourway/size
updatedprice, compare-at price or availability changed
removedthe variant is gone from the catalogue — discontinued, or delisted

Updated rows carry the before and after, which is the part most change-monitors leave out:

{
"changeType": "updated",
"changedFields": ["price", "available"],
"previousPrice": 116,
"price": 91,
"priceDelta": -25,
"previousAvailable": true,
"available": false,
"title": "Men's Strider - Medium Grey (Blizzard Sole)",
"variantTitle": "8.5"
}

Removed rows keep the productId, variantId and last known price, so a delisting is actionable rather than just an ID you can no longer look up.

The change signal is deliberately quiet. Shopify bumps a product's updated_at on any edit, and CDN image URLs churn on their own. Both are excluded from the change hash, so you are billed for rows where price, compare-at price, availability, title, SKU or link actually moved — not for background noise.

Run it hourly against your competitors and you have a price-and-stock intelligence pipeline: who discounted what, by how much, what sold out, and what quietly disappeared.

Input

FieldExampleNotes
storesallbirds.com, https://kith.com/collections/newa bare domain, a full URL, or any page on the store — everything reduces to the store's origin, so paste whatever you have
changesOnlyfalsemonitor mode as above
includeImagestrueone image URL per row (variant image, falling back to the product image)
maxProductsPerStore00 = unlimited

Things that quietly break other Shopify actors

Everything here is verified against live payloads — the evidence is in tests/fixtures/SOURCES.md.

  • www. vs the bare domain is not cosmetic. The same merchant can serve one and refuse the other: www.gymshark.com answers 403, gymshark.com answers 200. This actor retries a failed host once on its twin and tells you in the log which one served, so you don't get an empty run because of a prefix.
  • compare_at_price: "0.00" is not a was-price. Some themes write null where there is no discount, others write "0.00". Take it literally and you publish a 100% discount on the entire catalogue. We drop it — along with any compare-at price at or below the live price, which is a stale field, not a saving.
  • since_id does nothing here. It is an Admin-API parameter; on the storefront endpoint it is silently ignored and returns the same first page forever. Pagination is ?limit=250&page=N, and an empty page is the only end-of-catalogue signal the endpoint gives you.
  • A password-protected store returns HTML, not an error. That is a JSON.parse crash in a naive actor. Here it is one clear line: "the store is password-protected, so its catalogue is not public" — and the other stores in your run finish normally.

Honest limits

  • No stock quantities exist on this endpoint. Shopify publishes available (true/false) per variant and nothing more, so this actor reports availability, availableVariantCount and variantCount — never an invented number. If you need real inventory counts you need the merchant's own Admin API credentials, which no third party has.
  • Currency is best-effort. /products.json carries no currency at all; we read it from the store's /cart.js in one extra request. Where a store doesn't serve it, currency is null and the run log says so rather than guessing from the domain.
  • Stores that have disabled the catalogue endpoint return 404 — you get a clear per-store FAILED line, and the rest of the run continues.
  • Multi-currency storefronts report their default presentment currency, not a per-visitor one.
  • Catalogues are capped at 200 pages (50,000 products) per store to bound a runaway endpoint.
  • Monitor state is one record per variant per store; comfortable to roughly 50k variants per store.

Typical costs

Pure JSON over HTTP — no browser, no proxy — so platform usage is tiny: a four-store run pulling 167 variants completed in a few seconds. You pay per result via the event pricing shown on this page.