Shopify Price History & Fake Discount Tracker
Pricing
from $3.00 / 1,000 product trackeds
Shopify Price History & Fake Discount Tracker
Track Shopify product prices over time, get the 30-day lowest price, and flag inflated "was" prices. History starts with your first scheduled run.
Pricing
from $3.00 / 1,000 product trackeds
Rating
0.0
(0)
Developer
Viktor Kondas
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
27 minutes ago
Last modified
Categories
Features
Share
Track Shopify product prices over time, get the 30-day lowest price, and flag inflated "was" prices. Schedule it daily on the stores you watch: every run saves the price of every variant in your own Apify account, then tells you the 30-day low, whether the shop's compare-at ("was") price is consistent with it, and what changed since yesterday. Dataset, CSV, a one-click price history chart and optional webhook or Telegram alerts. $0.003 per product check, $0.02 per store snapshot, no subscription.
Independent tool. Not affiliated with Shopify or any store. Reads each store's public product feed only — no login, no cart, no personal data. Information tool, not legal advice.
Start your log before Black Friday
Past prices cannot be recovered for most shops, so your history starts with your first scheduled run. Run 1 is a baseline: it saves today's prices and returns no warning, just a clear message ("Baseline saved for N products. Schedule this task daily. 30-day lows become reliable after 30 days of runs."). From run 2 you get changes; after about 30 days of daily checks the 30-day low and the discount verdict are fully backed by data.
Black Friday is 27 November 2026 — a schedule started now has roughly two weeks of history by then. The status of every number says how complete it is (lowest30dStatus: full, partial or none), so a short history never looks like a full one.
Quick start
- Open the task 30-day lowest-price log, replace the example shops with the Shopify store URLs you want to watch (a domain, a homepage, a product URL, or a collection like
/collections/sale). - Click Start. Run 1 saves your baseline. Open the dataset, the Price history chart and the CSV export from the run's output tab.
- Click Schedule → daily (for example 06:00). Keep the same shop URLs and the same History store name, so the history keeps growing.
Every run writes to a named key-value store in your account (default shopify-price-history). Named stores do not expire.
Get alerts, not spreadsheets
Use the task Competitor price-drop alerts (outputMode: changesOnly, minDropPct: 5). It returns only variants that really changed: price drop or rise, new, back in stock, out of stock, removed, or a newly inflated discount. Small moves under minDropPct are still recorded in history but are not reported.
- Webhook or Telegram (only when something changed): fill in
webhookUrl, ortelegramBotToken+telegramChatId. You get one summary message per run that had changes and nothing on quiet days. - Apify integrations: connect the built-in email or Slack integration to the task (Integrations → add → run succeeded). Because the dataset only holds real changes, an attached dataset is empty on quiet days. The webhook/Telegram inputs above are the way to be notified only when something changed.
- Every run also writes a
CHANGESrecord (counts per change type plus the changed items) to the run's key-value store.
What you get
One dataset item per variant per run (rowType: product), plus one summary item per store (rowType: store). Variants are tracked by their Shopify variant ID, so a renamed product handle keeps its history.
| Field | Meaning |
|---|---|
store, title, handle, variantId, variantTitle, sku, productUrl | Which variant (handle and titles are refreshed each run; variantId is the history key) |
price, compareAtPrice, currency, available | Current price, the shop's "was" price, ISO 4217 currency, in stock |
lowest30d, lowest30dStatus, checkedDays30d, historyDaysCovered | Lowest price of the last 30 days and how complete that window is |
discountVerdict, saleStartedAt, referenceLowest30d, claimedDiscountPct (discountPct), actualDiscountPct | Is the "was" price consistent with the lowest price of the 30 days before the sale? |
changeType, previousPrice, priceChangePct, baseline | What changed since the previous check |
removed, removedAt | The variant is no longer returned by a completed check (history kept) |
chartUrl | Link to the price history chart (see below) |
Legacy fields from earlier versions keep working: promoStatus, onSaleSince, daysOnSale, lowestPriceSeen30d (same value as lowest30d), regularPriceObserved, referenceInflationPct, inflatedReference (now set from the verdict: true, false, or null when there is not enough history).
lowest30dStatus — full: 27+ checked days in the last 30 and no gap longer than 2 days · partial: some history, or gaps (missed runs hide anything that happened between checks) · none: first sighting.
discountVerdict — consistent: the compare-at price is within tolerance (default 1%) of the lowest price in the 30 days before the sale started · inflated_reference: it is higher · insufficient_history: fewer than 27 checked days (or a gap longer than 2 days) before the sale — the partial numbers are still returned · no_discount: no compare-at price above the price.
changeType — new (variant first seen after the baseline) · price_drop · price_rise · new_inflated_discount (verdict became inflated_reference) · back_in_stock · out_of_stock · removed · none.
CSV and chart (not charged). Every run writes price-history.csv (long format, one row per price change: shop, product_handle, product_title, variant_id, variant_title, sku, currency, timestamp, price, compare_at_price, available) and price-history.html (one small step-line chart per product: price, the shop's "was" price as a dashed line, the 30-day low as a marked level; up to 50 charts sorted by the largest recent price movement, the rest are in the CSV). The chart is a single self-contained page: no scripts, fonts or external requests; light and dark mode; readable on a phone. Both files appear in the run's output tab and in your history store (chart-<shop>, csv-<shop>) for a stable location. The links are for the account owner when logged in — the storage is private and there are no public or shareable chart links.
The EU 30-day rule
Under the EU price-indication rules (Directive 98/6/EC as amended by the Omnibus Directive 2019/2161), an announced price reduction must reference the lowest price the seller applied in the 30 days before the reduction. This tracker compares each shop's "was" price with the lowest price it recorded itself in the 30 days before the sale started (referenceLowest30d) and tells you how much history backs that comparison. It is an information tool, not legal advice; member states allow some exceptions (for example for perishable goods, products on the market for less than 30 days, or progressive reductions).
Who uses it
- Sellers watching competitors — see price drops, new products and sales the day they happen.
- Brands checking resellers — find out whether a reseller's "was" price was ever charged.
- Journalists and consumer groups checking Black Friday claims — evidence with a verdict, a history window and a CSV.
Pricing
Pay per event, no subscription: $0.003 per product check (per product — a product with 12 variants is one charge) and $0.02 per store snapshot (one summary item per store per run). Reading the catalog, retries, the CSV, the chart, the changes record, removed-variant items and alerts are free; shops that cannot be read are free.
Examples with every product delivered each day (mode: all): 300 products in one store checked daily ≈ $27.60 per month; 1,000 products ≈ $90.60 per month. outputMode: changesOnly charges only the products that changed (plus the store snapshot of a store that had changes — a quiet run pushes and charges nothing), mode: summary only the store snapshot. Use Max products per run (maxProducts) as a hard cost cap and Max products read per shop (scanLimitPerStore) to limit large shops. Set Max cost per run in the Run options as a hard limit.
Input
| Field | Description |
|---|---|
storeUrls | Shopify stores: domain, homepage, product URL (whole store), collection URL (that collection only) or market sub-folder like /en-de. Can import a list from a text file / Google Sheet CSV. Max 500 per run. |
historyEnabled | Keep price history (default on). Legacy alias trackHistory; history is off if either is off |
historyStoreName | Named key-value store in your account (default shopify-price-history). Tasks that share a name share (and merge) their history |
outputMode | all (default) or changesOnly — only variants whose changeType is not none |
minDropPct | Price moves smaller than this are recorded but reported as none (default 5) |
maxProducts | Cap on products delivered across all stores — your cost cap (default 200) |
scanLimitPerStore | Products read per shop, 250 per request, free (default 5,000). A capped or failed scan is not counted as a completed check |
webhookUrl, telegramBotToken, telegramChatId | Optional summary alert, only when something changed |
mode | on-sale (default) · all · changes (legacy: sale started / deepened / ended …) · summary (store rows only) |
maxProductsPerStore, minDiscountPct, includeStoreSummary | Per-store cap, minimum discount, store summary item ($0.02) on/off |
referenceTolerancePct | How far the "was" price may exceed the pre-sale 30-day low before it is flagged (default 1) |
proxyConfiguration | Residential proxy is recommended for big catalogs; direct egress often gets HTTP 429 |
FAQ
Why is my first run empty of history? Run 1 is a baseline: there is nothing to compare with yet, so the 30-day fields are empty and baseline is true. This is not an error. From run 2 on you get changes; the 30-day low is reliable after about 30 days of daily checks.
Can I get prices from before I started? No. The tracker only knows the prices it saw itself. Your history starts with your first scheduled run.
What happens if a scheduled run fails? That day is not counted as a completed check, nothing is marked as removed, and lowest30dStatus shows partial until the window is covered again. A scan that hits scanLimitPerStore or a page that keeps failing (HTTP 429/5xx after up to 5 retries) is treated the same way.
Can two tasks track the same shop? Yes. Before saving, the tracker re-reads the record and merges it (union of price points and check dates), so concurrent tasks on the same shop and store name do not overwrite each other.
Does it work on every Shopify store? No. It needs the public /products.json feed. Password-protected shops, some headless stores and shops that disallow it in robots.txt are returned as a free item with the reason.
Where is my history stored and what if I delete it? In a named key-value store in your Apify account (default shopify-price-history), one record per shop (large shops are split into numbered records). Delete the store or a record and that shop starts a new baseline. Let the Actor create the store on its first run — an Actor with limited permissions cannot open a store you created by hand.
What if a shop changes currency, or shows different prices by country? Shopify Markets can localise prices (and currency) by the visitor's country. The tracker records the currency the shop actually presents and, with an Apify proxy, pins the proxy's exit country to the shop's own country (override it in the proxy settings). If the presented currency still changes, the old series is closed and a new baseline starts; currencies are never mixed in one series.
Does it cover Amazon? No — Shopify stores only.
Why is onSalePct 0 on a shop that runs Black Friday? Many shops apply Black Friday discounts at checkout (codes, automatic discounts) instead of marking products down. Those never appear in the public product feed.
Can you add a field or fix a store? Yes — open an issue with the store URL.
Limitations
- Shopify stores only, through the public product feed. Discounts applied at checkout (codes, bundles, "buy 2 get 1") are not visible there.
- Prices are in the store's primary currency and market. Market sub-folders such as
/en-dework only where the shop serves its feed there. - The 30-day verdict needs 27+ checked days before the sale; before that you get
insufficient_historyand the partial numbers.
Related Actors
- EU Omnibus Discount Checker — check a single product page's discount against the legally required 30-day lowest price
- Product Catalog Scraper — export full Shopify catalogs as import-ready CSV
- Website Change Monitor — watch a store's landing page and promo banners for changes
- Vinted Deal Monitor — second-hand deals, price drops and Telegram/webhook alerts across 26 markets
Built by Viktor Kondas · Vibe Coder's Life · Custom work: hello@vibecoderslife.com