Shopify Store Monitor: Price & Stock Changes avatar

Shopify Store Monitor: Price & Stock Changes

Pricing

from $1.00 / 1,000 results

Go to Apify Store
Shopify Store Monitor: Price & Stock Changes

Shopify Store Monitor: Price & Stock Changes

Monitor Shopify stores for price drops, restocks, sold-out variants, and new or removed products. The first run saves a baseline, one row per product; each later run returns only what changed. Public catalogue data, no login.

Pricing

from $1.00 / 1,000 results

Rating

0.0

(0)

Developer

reventadata

reventadata

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

8 days ago

Last modified

Share

Unofficial. This Actor is not affiliated with, authorised by, endorsed by, or in any way officially connected to Shopify Inc. or to any store it reads. "Shopify" is a trademark of its respective owner and is used here only to describe what this tool reads. It reads the same public catalogue data a browser does.

Watch Shopify stores and get only what changed since your last run: price drops and increases, variants back in stock or sold out, new products and variants, removed products, and sales starting or ending.

Unlike a catalogue scraper that bills every product on every run, you pay for the full catalogue once (one row per product), then only for what changed: a Shopify price tracker and restock feed in one schedule (no built-in notifications; connect the dataset to your own).

What does it do?

  1. First run per store: a baseline. It reads the store's public catalogue (/products.json, falling back to the sitemap and product pages when that feed is closed), outputs one baseline row per product with every variant nested inside it, and saves a snapshot in a named key-value store.
  2. Every later run: changes only. It reads the catalogue again, compares it with the snapshot, outputs one row per changed variant plus one summary row per store, and updates the snapshot.

Put it on an hourly or daily Apify schedule and the dataset of each run is your change feed.

Who is it for?

  • Brands and retailers watching competitors' prices and markdowns.
  • Resellers and deal hunters waiting for restocks of specific products.
  • Analysts building a price history for a set of stores.

If you only need a one-off catalogue export, the first run already gives you that: one row per product, all variants included.

Input

FieldTypeDescription
storeUrlsarrayStore addresses, e.g. https://allbirds.com. Required.
maxProductsPerStoreintegerRead at most this many products per store (default 2000). A capped read cannot report removed products.
collectionstringOptional collection handle to watch just one collection. It has its own snapshot.
emitBaselinebooleanOutput all products on a store's first run (default on).
includeUnchangedbooleanAlso output unchanged variants (default off).
includeInventorybooleanAdd inventory quantity and barcode from product detail endpoints (default off).
maxInventoryLookupsintegerMaximum distinct product detail requests for inventory per store (default 200).
minPriceChangePercentnumberIgnore price-only moves smaller than this percent. Stock changes are never hidden.
stateStoreNamestringNamed key-value store that holds snapshots (default shopify-monitor-state-v2). Use another name for a separate watch-list.
resetStatebooleanTreat the run as a first run and save a fresh snapshot.
proxyConfigurationobjectOff by default.

There is no keyword search: the Actor watches whole stores or one collection.

Example input:

{
"storeUrls": ["https://allbirds.com"],
"maxProductsPerStore": 20
}

Output

changeType is one of baseline, new_product, new_variant, removed_product, removed_variant, price_drop, price_increase, back_in_stock, sold_out, sale_changed, unchanged (only with includeUnchanged), summary, error. When one variant changed in several ways at once, changes lists all of them and changeType holds the first.

Baseline row (first run). Real row from a run on allbirds.com on 2026-10-12, shortened to 3 of its 16 tags and 2 of its 7 variants:

{
"changeType": "baseline",
"changes": [],
"storeDomain": "allbirds.com",
"currency": "USD",
"productId": 7340901859408,
"handle": "womens-allbirds-flip-flop-dusty-pink",
"title": "Women's Allbirds Flip Flop - Dusty Pink",
"vendor": "Allbirds",
"productType": "Shoes",
"tags": [
"allbirds::carbon-score => undefined",
"allbirds::cfId => color-a4d5d0f6620dc304306f503bf67bc5f9",
"allbirds::complete => true"
],
"url": "https://allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
"imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345",
"productUpdatedAt": "2026-10-12T01:14:41-07:00",
"variants": [
{
"variantId": 42146889039952,
"variantTitle": "5",
"sku": "A12513W050",
"price": 25,
"compareAtPrice": 50,
"available": false,
"cartUrl": "https://allbirds.com/cart/add?id=42146889039952"
},
{
"variantId": 42146889072720,
"variantTitle": "6",
"sku": "A12513W060",
"price": 25,
"compareAtPrice": 50,
"available": false,
"cartUrl": "https://allbirds.com/cart/add?id=42146889072720"
}
],
"scrapedAt": "2026-10-12T08:14:41.168712+00:00"
}

Change rows (later runs) are one row per changed variant with storeDomain, currency, productId, handle, title, vendor, productType, tags, url, cartUrl, imageUrl, variantId, variantTitle, sku, price, previousPrice, priceChange, priceChangePct, compareAtPrice, previousCompareAtPrice, available, previousAvailable, scrapedAt, plus inventoryQuantity and barcode when includeInventory is on. Removed products and variants carry the ids, handle, title and the previous values.

Summary row (every run, one per store). Real row from the run right after the baseline above, where nothing had changed. scanComplete is false because the run was capped at 20 products:

{
"changeType": "summary",
"storeDomain": "allbirds.com",
"currency": "USD",
"mode": "diff",
"source": "products.json",
"scanComplete": false,
"variantsScanned": 243,
"productsScanned": 20,
"changeCounts": {},
"scrapedAt": "2026-10-12T08:15:09.523351+00:00"
}

A store that cannot be read gives an error row instead, for example "error": "HTTP 403, 919 B body at https://www.gymshark.com/products/gift-card.json".

What it costs

Billed per dataset item at $0.001, plus a $0.00005 start fee per run.

  • Baseline: one row per product, not per variant. A 2,000-product store is about 2,001 items, about $2.00, once.
  • Later runs: only the change rows plus one summary row per store. A store where nothing changed costs one row ($0.001) plus the start fee per run.

Limits

  • Stock is in stock / out of stock per variant. The catalogue feed carries no quantities. includeInventory adds one request per distinct product to fetch quantity and barcode; many stores publish neither, and then both fields are null.
  • Not every store is reachable. Some stores put a firewall in front of the whole site and refuse automated requests on every path. Such a store returns an error row; the other stores in the same run still complete. A run where every store fails ends as failed.
  • Prices are in the currency the store presents for this run (from /cart.js, falling back to /meta.json); currency is null when neither publishes it. Prices are not converted, and a store can show different currencies to different regions.
  • Removed products are only reported after a complete read (scanComplete: true on the summary row), so a capped run never reports false removals.
  • Password-protected stores are not supported.
  • Only public catalogue data is read. No customer, seller or personal data is collected.

Scheduling

  1. Run the Actor once with your store list to save the baselines.
  2. Create an Apify schedule on this Actor (or on a saved task) with the same input.
  3. Keep stateStoreName the same between runs; the snapshot lives there. Use a different name for each independent watch-list.

FAQ

Can I get every variant as its own row on the first run? No; baseline rows nest variants to keep the first run cheap. Change rows are per variant.

How do I start over? Set resetState to true for one run, or use a new stateStoreName.