Shopify Store Monitor – Price, Stock & New Product Tracker
Pricing
from $1.00 / 1,000 product scrapeds
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
Maintained by CommunityActor 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
- 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). - Writes one
productrow per product: prices, sale status, stock, vendor, type, tags, image and (optionally) every variant. - Compares the catalog with the snapshot saved by the previous run for that store and writes one
changerow per change. - Saves the new snapshot for next time, but only if the store was read completely without errors.
- Writes a run summary (per store: ok / partial / failed / skipped, counts, changes by type, errors) to the
OUTPUTrecord.
Input
| Field | Default | What 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. |
maxProductsPerStore | 2000 | Stop after this many products per store (max 25,000, which is also Shopify's own limit for this feed). |
trackChanges | true | Compare with the previous run and output changes. |
outputMode | products_and_changes | products_and_changes, changes_only (cheapest for daily monitoring) or products_only. |
includeVariants | true | Add 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}]}
| Field | Meaning |
|---|---|
minPrice, maxPrice | Lowest and highest variant price. |
currency | The store's currency as reported by the store's /meta.json. null if the store does not report it; it is never guessed. |
compareAtMin, compareAtMax | Lowest and highest "was" price, counting only variants whose compare-at price is above their current price. null when no variant is marked down. |
onSale | true when at least one variant has a compare-at price above its price. |
availableVariants, inStock | Number of variants that can be bought, and whether at least one can. null if the store hides availability. |
variants | Only 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"}
changeType | Level | before → after |
|---|---|---|
new_product | product | null → { title, minPrice, maxPrice, inStock, variantsCount } |
removed_product | product | { title, minPrice, maxPrice, inStock, variantsCount } → null |
price_changed | product or variant | old price → new price |
out_of_stock | product or variant | true → false |
back_in_stock | product or variant | false → true |
title_changed | product | old title → new title |
variant_added | variant | null → { title, sku, price, available } |
variant_removed | variant | { 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) orskipped(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,inputorinternal;nullwhen everything worked.productsPushedandchangesPushedalways 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) ornot-tracked.stopReason:max-productsorshopify-page-limitwhen 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:
| Event | When | Price |
|---|---|---|
product-scraped | One product row written | $0.001 ($1.00 per 1,000 products) |
change-detected | One 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.jsonfeed. Some stores disable it, put the store behind a password, or block automated traffic; those stores are reported asfailedwith 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.