Shopify Store Monitor – Price, Stock & New Product Tracker avatar

Shopify Store Monitor – Price, Stock & New Product Tracker

Pricing

from $1.00 / 1,000 product scrapeds

Go to Apify Store
Shopify Store Monitor – Price, Stock & New Product Tracker

Shopify Store Monitor – Price, Stock & New Product Tracker

Track competitors' public Shopify catalogs: new and removed products, price changes, and stock changes, plus clean normalized product data.

Pricing

from $1.00 / 1,000 product scrapeds

Rating

0.0

(0)

Developer

skuang

skuang

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

Watch any public Shopify store and get told what changed since the last run: new products, removed products, price changes, items going out of stock or coming back, renamed products, and added or removed variants. Every run also gives you the store's full catalog as clean, flat product rows.

Run it once to get a baseline, then schedule it (for example daily). Each later run compares the store with the previous run and outputs only what changed.

Who it is for

  • DTC brands keeping an eye on competitors' launches, discounts and stock-outs.
  • E-commerce agencies reporting on clients' markets.
  • Resellers and price watchers who want to know when a product drops in price or comes back in stock.

What it does

  1. Reads each store's public product feed (https://<store>/products.json), page by page, politely (one request per second per store, at most 3 stores in parallel).
  2. Writes one product row per product: prices, sale status, stock, vendor, type, tags, image and (optionally) every variant.
  3. Compares the catalog with the snapshot saved by the previous run for that store and writes one change row per change.
  4. Saves the new snapshot for next time, but only if the store was read completely without errors.
  5. Writes a run summary (per store: ok / partial / failed / skipped, counts, changes by type, errors) to the OUTPUT record.

Input

FieldDefaultWhat it does
storeUrls (required)–Up to 50 store addresses, e.g. https://www.allbirds.com or just allbirds.com. Paths are ignored; only the standard ports (80/443) are accepted.
maxProductsPerStore2000Stop after this many products per store (max 25,000, which is also Shopify's own limit for this feed).
trackChangestrueCompare with the previous run and output changes.
outputModeproducts_and_changesproducts_and_changes, changes_only (cheapest for daily monitoring) or products_only.
includeVariantstrueAdd the variant list to every product row.

Example:

{
"storeUrls": ["https://www.allbirds.com", "colourpop.com"],
"maxProductsPerStore": 2000,
"trackChanges": true,
"outputMode": "changes_only"
}

Output

All rows go to the run's dataset. The type field tells products and changes apart. The dataset has two views: Products and Changes.

Product row (type: "product")

{
"type": "product",
"store": "https://colourpop.com",
"productId": 7567362162770,
"handle": "neutral-swipe-shadow-stix-set",
"url": "https://colourpop.com/products/neutral-swipe-shadow-stix-set",
"title": "Neutral Swipe",
"vendor": "ColourPop",
"productType": "Shadow Stix",
"tags": ["Birthday2026", "brand:Colourpop", "category:Bundles", "category:Set"],
"createdAt": "2026-04-02T09:00:13-07:00",
"updatedAt": "2026-10-11T13:07:09-07:00",
"publishedAt": "2026-04-17T09:23:47-07:00",
"minPrice": 77,
"maxPrice": 77,
"currency": "USD",
"compareAtMin": 90,
"compareAtMax": 90,
"onSale": true,
"variantsCount": 1,
"availableVariants": 1,
"inStock": true,
"imageUrl": "https://cdn.shopify.com/s/files/1/1338/0845/files/BYOB_Shadow-Stix-Neutral_PDP-01_2000x2000_89956604-1d00-4be4-a8aa-c8cbe7c5a92d.jpg?v=1775252232",
"scrapedAt": "2026-10-11T20:07:11.021Z",
"variants": [
{
"id": 42358844227666,
"title": "Default Title",
"sku": "Bundle-NeutralSwipe",
"price": 77,
"compareAtPrice": 90,
"available": true,
"option1": "Default Title",
"option2": null,
"option3": null
}
]
}
FieldMeaning
minPrice, maxPriceLowest and highest variant price.
currencyThe store's currency as reported by the store's /meta.json. null if the store does not report it; it is never guessed.
compareAtMin, compareAtMaxLowest and highest "was" price, counting only variants whose compare-at price is above their current price. null when no variant is marked down.
onSaletrue when at least one variant has a compare-at price above its price.
availableVariants, inStockNumber of variants that can be bought, and whether at least one can. null if the store hides availability.
variantsOnly when includeVariants is on.

Change row (type: "change")

{
"type": "change",
"store": "https://www.allbirds.com",
"productId": 7340901859408,
"handle": "womens-allbirds-flip-flop-dusty-pink",
"url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
"title": "Women's Allbirds Flip Flop - Dusty Pink",
"changeType": "price_changed",
"variantId": null,
"sku": null,
"variantTitle": null,
"before": 35,
"after": 25,
"currency": "USD",
"detectedAt": "2026-10-11T20:10:28.576Z"
}
changeTypeLevelbefore → after
new_productproductnull → { title, minPrice, maxPrice, inStock, variantsCount }
removed_productproduct{ title, minPrice, maxPrice, inStock, variantsCount } → null
price_changedproduct or variantold price → new price
out_of_stockproduct or varianttrue → false
back_in_stockproduct or variantfalse → true
title_changedproductold title → new title
variant_addedvariantnull → { title, sku, price, available }
variant_removedvariant{ title, sku, price, available } → null

Product-level rows have variantId, sku and variantTitle set to null. How the level is chosen:

  • Price: if every variant moved from the same old price to the same new price, you get one product-level row. Otherwise you get one row per variant whose price changed.
  • Stock: if the product as a whole sold out (no variant available any more) or came back (at least one variant available again), you get one product-level row. Otherwise, while the product stays in stock, you get one row per variant (size, color, ...) that sold out or came back.
  • Removed products are double-checked on the product's own page before they are reported, so a listing that shifted while it was being read does not produce false removals. Up to 20 products per store are checked per run; if all of them are confirmed gone, the rest are reported as removed too, otherwise the rest are kept and checked again next run.

Run summary (OUTPUT record)

The default key-value store holds an OUTPUT record with totals and one entry per store:

{
"input": "https://www.allbirds.com",
"store": "https://www.allbirds.com",
"status": "ok",
"productsFetched": 692,
"productsPushed": 692,
"changesDetected": 3,
"changesPushed": 3,
"changesByType": { "new_product": 0, "removed_product": 1, "price_changed": 1, "back_in_stock": 0, "out_of_stock": 0, "title_changed": 1, "variant_added": 0, "variant_removed": 0 },
"catalogComplete": true,
"stopReason": null,
"baseline": "compared",
"currency": "USD",
"storeReportedProductCount": 692,
"shopDomain": "weareallbirds.myshopify.com",
"errorType": null,
"error": null
}
  • status: ok, partial (some rows were delivered, then an error or the charge limit stopped it), failed (nothing could be read, for example not a Shopify store) or skipped (not processed: the charge limit was already reached, or the address leads to a store that is already in the list, by redirect or by the same Shopify shop domain).
  • errorType: fetch (reading the store failed), output (writing rows to the dataset failed), charge-limit, aborted, duplicate, input or internal; null when everything worked. productsPushed and changesPushed always count the rows that were actually delivered, also when an error happened.
  • baseline: created (first run for this store, no changes reported), compared, kept (the catalog could not be read to the end, so the previous snapshot was left untouched) or not-tracked.
  • stopReason: max-products or shopify-page-limit when the catalog was larger than what was read.

The run fails only if every store failed. A store that fails is listed in OUTPUT and in the run's status message.

How change tracking works

  • First run for a store = baseline. It saves a snapshot and reports no changes. The status message says so.
  • Every later run compares with the latest saved snapshot for that store, then replaces it.
  • Schedule it. Use an Apify schedule (daily is typical). Changes are measured between consecutive runs.
  • Snapshots live in your account, in a named key-value store called shopify-store-monitor-state, one record per store. They hold only what is needed for comparison (product title, handle, variant prices, availability and SKUs). Delete a store's record to start that store over with a new baseline.
  • Partial runs never overwrite the baseline with a partial catalog. If a store could not be read to the end (network error, blocking, charge limit), its previous snapshot is kept. Otherwise every product that was not read would look "removed" next time.
  • Undelivered changes come back, delivered ones do not. If the charge limit, an abort or a dataset error stops a run while change rows are being written, the snapshot records only the changes that were delivered. The next run reports the changes that were not delivered and none of the delivered ones. A product's change rows are delivered together: when the remaining budget cannot cover all of the next product's rows, the run stops before that product. Two cases can stop in the middle of one product's rows: a dataset error, and a product with more change rows than a whole run's budget allows (it is then delivered over several runs). In those cases, a product-level price or stock change of that product that was not delivered can come back as one row per variant, or, when a delivered variant removal already made the product sell out, not at all.
  • Large catalogs. If a store has more products than maxProductsPerStore (or more than Shopify's 25,000 limit), removed products cannot be detected for it, and only products created or published after the previous run count as new.
  • All runs of this actor in your account share these snapshots. Two schedules watching the same store will each see only the changes since the other one ran.

Pricing

Pay per event. You pay only for rows actually delivered to your dataset:

EventWhenPrice
product-scrapedOne product row written$0.001 ($1.00 per 1,000 products)
change-detectedOne change row written$0.003 ($3.00 per 1,000 changes)

Tip: for daily monitoring, use outputMode: "changes_only". You then pay only for changes; product rows are not written.

If you set a maximum cost per run, the actor stops cleanly when it is reached and says so in the status message and in OUTPUT. Stores that were cut short are marked partial, stores that were not started are marked skipped, and changes that could not be delivered are reported on the next run (changes that were delivered are not reported or charged again).

Limitations

  • Works only with Shopify stores that expose the public /products.json feed. Some stores disable it, put the store behind a password, or block automated traffic; those stores are reported as failed with the reason.
  • Reads only public catalog data. No access to sales numbers, inventory quantities, customers or anything behind a login.
  • Only public internet addresses are contacted. Addresses that resolve to private, local or cloud-internal networks, and non-standard ports, are refused (also when a store redirects to them).
  • Shopify's feed stops at 25,000 products per store.
  • Prices are the ones the public feed returns. For stores that sell in several currencies or countries, a shopper elsewhere may see different prices; discounts applied at checkout are not included.
  • Stock is the store's "available for sale" flag per variant; Shopify does not expose quantities in this feed.

FAQ

How do I know a store works? Open https://<store>/products.json in your browser. If you see product data, the actor can read it.

Why did the first run report no changes? There was nothing to compare with yet. The first run saves the baseline; changes appear from the second run on.

Can I track the same store with different settings? Yes, but runs share one snapshot per store, so each run reports changes since the last run of any of them.

How fast is it? About one page of 250 products per second per store, with up to 3 stores in parallel. A 2,000-product store takes roughly 10 seconds.

Do I need proxies? No. The actor reads public feeds directly, with a clear user agent and at most one request per second per store.