WooCommerce Store Monitor – Price, Stock & New Product Tracker
Pricing
from $1.00 / 1,000 product scrapeds
WooCommerce Store Monitor – Price, Stock & New Product Tracker
Track competitors' public WooCommerce catalogs: new and removed products, price changes, sales starting or ending, 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
WooCommerce Store Monitor – Price, Sale, Stock & New Product Tracker
Watch any public WooCommerce store and get told what changed since the last run: new products, removed products, price changes, sales starting or ending, items going out of stock or coming back, and renamed products. Every run also gives you the store's 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
- Brands and shops 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, goes on sale or comes back in stock.
What it does
- Reads each store's public WooCommerce Store API (
https://<store>/wp-json/wc/store/v1/products, the API WooCommerce provides for headless storefronts), 100 products per page, newest first, politely (one request per second per store, at most 3 stores in parallel). Stores whose/wp-json/address is not routed (plain permalinks) are read throughhttps://<store>/?rest_route=/wc/store/v1/productsinstead. If the store hides out-of-stock products from its catalog (the listing that names every stock status reports more products than the plain one), that listing is read instead, so the hidden products are included too. - Writes one
productrow per product: prices, sale status, stock, categories, tags, attributes, rating and image. - 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 to its end or to
maxProductsPerStorewithout 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 | ["https://www.nutribullet.com"] | Up to 50 store addresses, e.g. https://www.nutribullet.com or just porterandyork.com. Paths are ignored; only the standard ports (80/443) are accepted. |
maxProductsPerStore | 2000 | Stop after this many products per store (newest first, max 25,000). |
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. |
Example:
{"storeUrls": ["https://www.nutribullet.com", "porterandyork.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://www.nutribullet.com","productId": 1037584,"slug": "nutribullet-turbo-chill-bundle","url": "https://www.nutribullet.com/shop/bundles/nutribullet-turbo-chill-bundle/","title": "Turbo & Chill Bundle","sku": "ECOM01684-PDP","productType": "variable","categories": ["Bundles"],"tags": ["1000 watts", "24oz"],"price": 224.99,"regularPrice": 299.98,"salePrice": 224.99,"minPrice": 224.99,"maxPrice": 224.99,"currency": "USD","onSale": true,"inStock": false,"purchasable": true,"onBackorder": false,"lowStockRemaining": null,"stockStatus": null,"stockText": null,"variationsCount": 2,"attributes": [{ "name": "Color", "values": ["Matte Black", "Matte White"] }],"averageRating": 0,"reviewCount": 0,"imageUrl": "https://nbmedia.imgix.net/FwKE4J1f-NBA30350AMZ_CrispLite-Vision-Air-Fryer_Black_Ecomm_PDP-1_Config_1500x1201.jpg?auto=compress%2Cformat&ixlib=php-3.3.0","scrapedAt": "2026-10-12T11:32:25.704Z"}
| Field | Meaning |
|---|---|
price | Current selling price. For variable products, the lowest variation price. |
regularPrice | The non-sale price. |
salePrice | The sale price while onSale is true; null otherwise. |
minPrice, maxPrice | Price range for variable and grouped products whose variations differ in price; both equal price otherwise. |
currency | Currency code the store's Store API reports. Prices are converted from the API's minor units using the decimals the store reports; they are never guessed. |
inStock, onBackorder, purchasable | The store's own flags for the product. |
stockStatus, stockText | Stock class and label (for example out-of-stock, "Only 3 left in stock") on stores whose WooCommerce version reports them; null otherwise. |
lowStockRemaining | Units left, only when the store shows a low-stock count. |
variationsCount, attributes | Number of variations and the product's attributes (for example sizes or colors). Per-variation prices and stock are not in the listing; see Limitations. |
Change row (type: "change")
{"type": "change","store": "https://porterandyork.com","productId": 15,"slug": "buy-porterhouse-steak-online","url": "https://porterandyork.com/product/buy-porterhouse-steak-online/","title": "Porterhouse Steak","sku": "PY1261","changeType": "price_changed","before": { "min": 65, "max": 100 },"after": { "min": 60, "max": 95 },"currency": "USD","detectedAt": "2026-10-12T11:33:05.081Z"}
changeType | before → after |
|---|---|
new_product | null → { title, minPrice, maxPrice, onSale, inStock } |
removed_product | { title, minPrice, maxPrice, onSale, inStock } → null |
price_changed | old price → new price |
sale_started | price before the sale → sale price |
sale_ended | sale price → price after the sale |
out_of_stock | true → false |
back_in_stock | false → true |
title_changed | old title → new title |
- Prices in change rows are a number, or
{ "min": ..., "max": ... }when the product has a price range on either side. - Sales: when a product goes on sale or comes off sale, you get one
sale_startedorsale_endedrow that carries the price move. No separateprice_changedrow is written for the same move. A sale price that changes while the sale continues is aprice_changed. - Removed products are double-checked before they are reported, because a product can be missing from the listing and still exist: a listing can shift while it is being read, and products can be hidden from it (out-of-stock products on a store whose API does not list them even when every stock status is requested, or products with a custom stock status when that listing is used). Missing products are first looked up in batches of 100 (up to 1,000 per store per run, asking for every stock status); a product found this way is compared like a listed one (for example, you get an
out_of_stockrow) and is not reported as removed. The rest get a check on the Store API's single-product address, up to 20 per store per run, and only the Store API's own "invalid product" answer counts as removed. Products that could not be checked in a run are not reported; they are checked first on the 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.nutribullet.com","store": "https://www.nutribullet.com","status": "ok","productsFetched": 180,"productsPushed": 180,"changesDetected": 2,"changesPushed": 2,"changesByType": { "new_product": 0, "removed_product": 0, "price_changed": 1, "sale_started": 1, "sale_ended": 0, "back_in_stock": 0, "out_of_stock": 0, "title_changed": 0 },"catalogComplete": true,"stopReason": null,"baseline": "compared","currency": "USD","storeReportedProductCount": 180,"siteOrigin": "https://www.nutribullet.com","apiRoute": "wp-json","hiddenOutOfStockListed": false,"currencyChange": null,"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 "Store API not available") 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 because its product links point to the same site).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-productswhen the catalog was larger than what was read.storeReportedProductCount: the store's own product count (X-WP-Total).apiRoute:wp-jsonorrest_route, the address that worked.hiddenOutOfStockListed:truewhen the store hides out-of-stock products and the listing that includes them was read.currencyChange:{ "before": "USD", "after": "EUR" }when the store's currency differs from the previous run's;nullotherwise. See below.
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
woocommerce-store-monitor-state-ye1mdfmmvk92rmzxs(the name ends with this actor's ID in lowercase, so it does not clash with the state store of another actor that has the same name), one record per store. They hold only what is needed for comparison (the store's currency; per product: title, slug, link, SKU, price or price range, sale and stock flags). 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 (or a dataset error) stops a run while change rows are being written, the snapshot keeps the previous values for exactly the changes that were not delivered (per product and per kind of change) and takes the new values for the ones that were. The next run reports exactly the changes that were not delivered, and never a delivered one again.
- Currency switches. If a store's currency differs from the one saved with the previous snapshot, prices cannot be compared: that run writes no
price_changed,sale_startedorsale_endedrows for the store, its prices start a new baseline, and the switch is shown inOUTPUT(currencyChange) and in the run's status message. Other changes are still reported. - Large catalogs. If a store has more products than
maxProductsPerStore, only the newest products are read, removed products cannot be detected for it, and a product counts as new only when its id is higher than every id in the previous snapshot (the Store API gives no creation dates; WordPress assigns ids in creation order). - 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). If you abort a run, nothing more is written or charged after the abort, and the same applies to the changes that were not delivered.
Limitations
- Works only with WooCommerce stores whose public Store API answers. Stores that disable it, block automated traffic (HTTP 401/403), or return a web page instead of data are reported as
failedwith the reason, for example "Store API not available". - Stores that hide out-of-stock products from their catalog: the actor notices it (asking the Store API for every stock status returns more products than the plain listing) and reads that listing instead, so hidden products are output (with
inStock: false) and kept in the baseline, and a product that comes back is reported asback_in_stock, not as new. Product rows can therefore include products that shoppers do not see in the store's catalog. On such stores, products with a custom stock status (added by a plugin) are left out of that listing; those already in the baseline are kept through the single-product check rather than reported as removed. If a store's API does not list hidden products even when every stock status is requested, products already in the baseline are found with the extra lookup above and reported as out of stock rather than removed, but a product that was already hidden when the store was first tracked is unknown to the actor and is reported as new when it reappears. - Variable products are tracked at product level: the price (or price range) and the product's own stock flag. The listing does not include per-variation prices or stock, so a single size selling out while others stay available is not reported.
- Reads only public catalog data. No access to sales numbers, exact inventory (unless the store shows a low-stock count), 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).
- Stores installed in a subfolder (for example
example.com/shop/with WordPress itself at that path) are not supported; the actor reads the API at the domain root. - Prices are the ones the Store API returns for an anonymous visitor. Stores that price by country, customer group or currency switcher may show other shoppers different prices; discounts applied at checkout are not included.
- No creation or update dates are available from the Store API, so product rows have none.
FAQ
How do I know a store works? Open https://<store>/wp-json/wc/store/v1/products 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? At most one page of 100 products per second per store, with up to 3 stores in parallel; many WooCommerce stores take a few seconds to answer each page. A 180-product store took about 10 seconds in testing.
Do I need proxies? No. The actor reads the public API directly, with a clear user agent and at most one request per second per store.