Shopify Price & Stock Monitor: Price Drops & Restocks
Pricing
Pay per event
Shopify Price & Stock Monitor: Price Drops & Restocks
Track Shopify stores and get only what changed since your last run, per variant: price drops and rises, back in stock, out of stock, new and removed variants. $1.50 per 1,000 products checked; quiet runs leave a free no_changes row.
Pricing
Pay per event
Rating
0.0
(0)
Developer
Changefeeds Tools
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
11 hours ago
Last modified
Categories
Share
- What you get: for a Shopify store that serves its public catalog (most do), one row per variant that changed since your last run: price drops and rises, compare-at ("was") price changes, back in stock, out of stock, new and removed variants, title and SKU changes.
- How it works: it keeps the previous run's catalog snapshot for you and returns only the differences. Schedule it daily or hourly and send changes to Slack, Sheets, Zapier, Make or your own webhook.
- Price: $1.50 per 1,000 products checked; changes are not charged extra. A run with no changes leaves one free
no_changesrow.
Sample output
Changes. We ran this actor on Allbirds on 2026-09-29 and again on
2026-10-23. The second run checked the first 200 products (2,151 variants) and
found 190 changes: 4 back_in_stock and 186 out_of_stock. Four of them:
| change_type | product_title | variant_title | sku | price | before → after |
|---|---|---|---|---|---|
| back_in_stock | Women's Canvas Runner NZ - Deep Navy Stripes (Blizzard Sole) | 8.5 | A12500W085 | 100 | available false → true |
| back_in_stock | Men's Wool Cruiser Waterproof - Natural Black (Natural White Sole) | 12 | A11806M120 | 140 | available false → true |
| out_of_stock | Men's Cruiser Slip On - Natural Black (Blizzard Sole) | 10 | A12642M100 | 100 | available true → false |
| out_of_stock | Men's Canvas Cruiser - Burnt Olive (Natural White Sole) | 8 | A12486M080 | 75 | available true → false |
The first row in full:
{"change_type": "back_in_stock","store": "www.allbirds.com","product_id": "7258384564304","product_handle": "womens-canvas-runner-nz","product_title": "Women's Canvas Runner NZ - Deep Navy Stripes (Blizzard Sole)","product_url": "https://www.allbirds.com/products/womens-canvas-runner-nz","variant_id": "41884543123536","variant_title": "8.5","sku": "A12500W085","price": 100,"compare_at_price": null,"available": true,"updated_at": "2026-10-22T20:49:41-07:00","field": "available","before": false,"after": true,"pct_change": null,"previous_run_at": "2026-09-29T08:38:56.118Z","run_at": "2026-10-23T03:49:55.736Z"}
A price_changed row has the same shape, with field: "price", the old and new
price in before and after, and pct_change (for example -20 for a 20%
drop).
Your first run is a baseline. There is nothing to compare with yet, so it
returns every variant read with change_type: "baseline" and saves the
snapshot; changes start from the second run of the same input. The prefilled
input's first run on 2026-10-23 returned 302 baseline rows for 25 products, for
example:
{"change_type": "baseline","store": "www.allbirds.com","product_id": "7340901859408","product_handle": "womens-allbirds-flip-flop-dusty-pink","product_title": "Women's Allbirds Flip Flop - Dusty Pink","product_url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink","variant_id": "42146889039952","variant_title": "5","sku": "A12513W050","price": 25,"compare_at_price": 50,"available": false,"updated_at": "2026-10-22T20:56:55-07:00","field": null,"before": null,"after": null,"pct_change": null,"previous_run_at": null,"run_at": "2026-10-23T03:57:14.492Z"}
How to run in 30 seconds
- Click Try for free. The input already holds
https://www.allbirds.comand a 25-product cap. - Click Start. Our first run of that input took 21 seconds and cost $0.038 (25 products checked, 302 baseline rows).
- Click Start again later (or tomorrow): that run returns only what changed, or one free
no_changesrow.
To watch your own stores, replace the URL (add as many stores as you like) and raise Max products per store to cover the whole catalog. Then save it as a task and add a schedule (see Scheduling below).
Pricing
- $0.0015 per product checked ($1.50 per 1,000), however many variants it has, plus Apify's actor-start fee ($0.00005 per run, per GB of run memory).
- The baseline run is charged the same way: checking every product is the work.
- Not charged: the change rows themselves, the
no_changesrow, store errors and the webhook. A product that appears twice in one run is charged once. - You pay to check, not per change found:
- first run, 1 store, 800 products: 800 × $0.0015 = $1.20;
- daily, 3 stores × 2,000 products: $9.00 per run, about $270 a month;
- 4 times a day, 1 store × 500 products: $0.75 per run, about $90 a month.
- Set a maximum cost per run and the actor stops fetching when the budget is
used up, saves what it has, marks the affected stores truncated
(
reason: "charge_limit") and setscharge_limit_reached: truein the summary. It never charges past your limit.
FAQ
Why did my first run return every variant? That is the baseline. Changes start from the second run of the same input.
How do I get price-drop alerts in Slack without code? Save your input as a task, add a schedule, and attach an Apify integration (Slack, Zapier, Make, Google Sheets) to the task's "run succeeded" event. Each run's dataset holds only the changed rows, so the integration fires on real changes. Or set Webhook URL and the run POSTs its summary and up to 500 changes itself.
I edited my store list and everything is baseline again. The default snapshot key comes from the store list. Set Snapshot key to a fixed name to keep history when you edit the list; new stores get a baseline, the others keep comparing.
What currency are the prices in? The store's own default currency, exactly
as products.json reports the number. The response does not say which.
Do I need the whole catalog every time? No, that is the point: this actor diffs for you. If you do want a full catalog export, use our Shopify Products Scraper.
Is this allowed? The actor reads the public JSON catalog Shopify serves to every visitor, at about one request per second, and identifies itself in its User-Agent. You are responsible for your own use of the data and for respecting each store's terms.
Limits
- Public catalog only. It reads only
products.json, never logs in, never touches cart or checkout, and collects no personal data. - Some stores hide
products.json, block it (HTTP 403) or are password protected. Those give astore_errorrow; there is no workaround, by design. - Only products published to the Online Store appear. Hidden or channel-only products are not visible.
- Removal detection needs a complete fetch.
removed_variantis reported only when the whole catalog was read in this run. If a store stops at Max products per store, at your cost limit or on an error, removals are not reported for it and the summary says so (the 2026-10-23 run above was capped at 200 products, so it reported no removals). - Stores larger than the product cap are read in catalog order and only the
first N products are compared; products moving in or out of the first N can
appear as
new_variant. Raise the cap to cover the catalog if that matters. - Polite, so slower on big catalogs: one request at a time per store, at
least 1 second apart, HTTP 429
Retry-Afterhonoured. Up to three stores run in parallel. - No inventory counts. Only in stock or out of stock is public.
Change types
change_type | Meaning |
|---|---|
price_changed | price differs; before, after, pct_change |
compare_at_changed | compare_at_price differs, including set or cleared; pct_change when both sides have a value |
back_in_stock | available went from false to true |
out_of_stock | available went from true to false |
new_variant | a variant id not in the previous snapshot |
removed_variant | in the previous snapshot, absent from this run's complete fetch |
title_changed | product title (field: "product_title") or variant title (field: "variant_title") differs |
sku_changed | SKU differs |
baseline | first run for this store: current values, no comparison |
unchanged | only with includeUnchanged: true |
store_error | this store could not be read (see error_code) |
A variant with several changes in one run produces several rows, one per field.
A run that finds nothing new leaves one free row with type: "no_changes" and a
message saying how many products were checked, so a quiet run is never mistaken
for a broken one. A run stopped by your cost limit before it found any change
leaves a free incomplete row instead.
Scheduling
Changes only mean something when the same input runs repeatedly:
- Create a saved task with your store list (actor page → Create task).
- In Apify Console, open Schedules → Create new, pick a cron such as
0 7 * * *(daily at 07:00 UTC) or0 * * * *(hourly), and add the task. - Each scheduled run compares with the previous one. Read the dataset, set Webhook URL, or use an integration on the "run succeeded" event.
Keep the store list unchanged, or set a fixed Snapshot key, so runs keep comparing with the same snapshot.
Input reference
{"storeUrls": ["https://www.allbirds.com", "https://www.tentree.com"],"maxProductsPerStore": 5000,"trackFields": ["price", "compare_at_price", "available", "title", "sku"],"snapshotKey": "","webhookUrl": "","includeUnchanged": false}
storeUrls(required): store base URLs. Any path is ignored;allbirds.comandhttps://allbirds.com/collections/xmean the same store.maxProductsPerStore(default 5,000, maximum 50,000): the most products read per store per run.trackFields: which fields produce change rows. New and removed variants are always reported.snapshotKey: which saved snapshot to compare against. Empty = derived from the sorted store list.webhookUrl: when a run finds changes, the run summary and the first 500 changes are POSTed here as JSON (10-second timeout). A baseline-only run does not call it. A failed POST is logged and recorded in the summary; the run still succeeds.includeUnchanged: also output a row for every variant that did not change.
Output reference
Prices are numbers in the store's own currency. A store that cannot be read gives one error row, and the other stores still run:
{"change_type": "store_error","store": "www.example-store.com","error_code": "blocked","error_message": "HTTP 403: the store refused the public catalog request","partial": false,"run_at": "2026-09-29T03:56:00.489Z"}
error_code is one of not_shopify, password_protected, blocked,
rate_limited, http_error, network_error. partial: true means some pages
were read before the error; changes from those pages are still reported, but
removals are not.
The run summary is saved to the default key-value store as OUTPUT. From the
2026-10-23 change run above (store details trimmed):
{"run_at": "2026-10-23T03:49:55.736Z","finished_at": "2026-10-23T03:49:56.402Z","snapshot_key": "stores-d4b5b2499a2c579e91762e768a1a6587","first_run": false,"products_checked": 200,"variants_checked": 2151,"changes_total": 190,"changes_by_type": { "price_changed": 0, "compare_at_changed": 0, "back_in_stock": 4, "out_of_stock": 186,"new_variant": 0, "removed_variant": 0, "title_changed": 0, "sku_changed": 0 },"baseline_rows": 0,"errors": [],"truncated_stores": [{ "store": "www.allbirds.com", "reason": "max_products" }],"charge_limit_reached": false,"requests_total": 1,"stores": [{ "store": "www.allbirds.com", "status": "truncated", "previous_run_at": "2026-09-29T08:38:56.118Z","removals_checked": false, "note": "Fetch was not complete, so removed variants were not checked for this store.", "…": "…" }],"webhook": null}