Shopify Price & Stock Monitor: Price Drops & Restocks avatar

Shopify Price & Stock Monitor: Price Drops & Restocks

Pricing

Pay per event

Go to Apify Store
Shopify Price & Stock Monitor: Price Drops & Restocks

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

Changefeeds Tools

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

11 hours ago

Last modified

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_changes row.

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_typeproduct_titlevariant_titleskupricebefore → after
back_in_stockWomen's Canvas Runner NZ - Deep Navy Stripes (Blizzard Sole)8.5A12500W085100available false → true
back_in_stockMen's Wool Cruiser Waterproof - Natural Black (Natural White Sole)12A11806M120140available false → true
out_of_stockMen's Cruiser Slip On - Natural Black (Blizzard Sole)10A12642M100100available true → false
out_of_stockMen's Canvas Cruiser - Burnt Olive (Natural White Sole)8A12486M08075available 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

  1. Click Try for free. The input already holds https://www.allbirds.com and a 25-product cap.
  2. Click Start. Our first run of that input took 21 seconds and cost $0.038 (25 products checked, 302 baseline rows).
  3. Click Start again later (or tomorrow): that run returns only what changed, or one free no_changes row.

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_changes row, 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 sets charge_limit_reached: true in 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 a store_error row; 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_variant is 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-After honoured. Up to three stores run in parallel.
  • No inventory counts. Only in stock or out of stock is public.

Change types

change_typeMeaning
price_changedprice differs; before, after, pct_change
compare_at_changedcompare_at_price differs, including set or cleared; pct_change when both sides have a value
back_in_stockavailable went from false to true
out_of_stockavailable went from true to false
new_varianta variant id not in the previous snapshot
removed_variantin the previous snapshot, absent from this run's complete fetch
title_changedproduct title (field: "product_title") or variant title (field: "variant_title") differs
sku_changedSKU differs
baselinefirst run for this store: current values, no comparison
unchangedonly with includeUnchanged: true
store_errorthis 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:

  1. Create a saved task with your store list (actor page → Create task).
  2. In Apify Console, open Schedules → Create new, pick a cron such as 0 7 * * * (daily at 07:00 UTC) or 0 * * * * (hourly), and add the task.
  3. 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.com and https://allbirds.com/collections/x mean 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
}