Shopify Store Products, Prices & Stock Tracker
Pricing
from $1.00 / 1,000 products
Shopify Store Products, Prices & Stock Tracker
Export products from Shopify stores, each with its variants' prices, compare-at prices, SKUs and stock, via the public products.json feed. Track price drops, restocks, sell-outs, new and removed products between runs. $1 per 1,000 products.
Pricing
from $1.00 / 1,000 products
Rating
0.0
(0)
Developer
Naveen Raja
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
17 hours ago
Last modified
Categories
Share
Export the products of Shopify stores, each with every variant's price, compare-at price, SKU and stock status, in one clean JSON format. The Actor reads the public products.json feed that Shopify online stores publish, so it is fast and complete, with no browser and no login. Apify's residential proxy is on by default (included in the price) because Shopify throttles cloud servers.
Turn on change tracking and every later run tells you what changed since the previous run: price drops, price increases, back in stock, sold out, new products, removed products, and variants added or removed. Schedule it daily (or hourly) and you have a competitor price monitor or a restock alert.
Price: $1 per 1,000 products ($0.001 per product row, all variants included). With Output only changed rows, each product checked costs $0.0001 and each changed product returned $0.001. Stores that can't be read cost nothing.
What you get
- One row per product, with its variants nested in
variants(each size / colour with its own price, compare-at price, SKU, options, stock status, link and image). - Product summary: lowest and highest price, whether any variant is on sale (and the largest discount), whether any variant is in stock, and how many variants are in stock.
- Product data: title, handle, vendor (brand), product type, tags, image, product URL, created and published dates.
- With tracking on:
changeType(new,removed,price_down,price_up,back_in_stock,out_of_stock,variant_added,variant_removed,unchanged), achangeslist naming every variant that changed with its old and new price and stock,previousPrice,priceChangePct,previousAvailableandfirstSeenAt. - Whole store or selected collections (e.g. only
saleornew-arrivals). - Optional clean-text product description (HTML converted to plain text).
- Built for automation: the same fields on every row, pay-per-event pricing with no usage fees, limited permissions, a machine-readable
SUMMARYrecord, free error rows that say why a store failed, and a clean stop when your spending limit is reached.
Use cases
- Competitor price monitoring: track a rival's whole catalog and get only the products whose price changed each day, with the old price and the change in percent.
- Restock and sell-out alerts: watch a store or a collection and get a row whenever a size or colour comes back in stock or sells out;
changessays which one. - Catalog export: download a complete product feed (titles, SKUs, prices, images, links) as JSON, CSV or Excel for analysis, comparison sites or a migration.
- Sale and discount tracking: filter
onSalerows and sort bydiscountPctto see what a store is discounting right now. - New product launches: with tracking on, new products show up as
new, so you can follow a brand's drops.
How to use
- Add stores in Shopify stores (one per line): a domain (
allbirds.com), a store URL, a collection URL (https://www.deathwishcoffee.com/collections/coffee) or aname.myshopify.comaddress. Enter the domain, not the store's name. - Optional: add Collection handles to read only those collections of every store.
- Optional: set Max products per store (newest products first).
- Optional: turn on Track price and stock changes, and Output only changed rows if you only want the changes. Use the same Tracking ID for every run of the same monitor.
- Run the Actor (or schedule it) and download the results as JSON, CSV or Excel, or read them via API. A per-store summary is saved as the
SUMMARYrecord in the run's key-value store.
Input example
{"stores": ["deathwishcoffee.com","https://www.deathwishcoffee.com/collections/coffee"],"trackChanges": true,"onlyChanges": true,"trackingId": "my-competitor-monitor"}
Other options: "collections": ["sale"], "maxProductsPerStore": 100, "includeDescription": true.
Output example
A real product row from a test run on 20 Oct 2026 (one variant shown, tags shortened):
{"store": "deathwishcoffee.com","storeName": "Death Wish Coffee","productId": "6608733995063","handle": "gingerdead-coffee-cups","title": "Gingerdead Single-Serve Pods","vendor": "Death Wish Coffee Company","productType": "Coffee","tags": ["Gingerdead", "Single Serve"],"price": 12.99,"priceMax": 38.97,"compareAtPrice": null,"onSale": false,"discountPct": null,"currency": "USD","available": true,"variantCount": 3,"availableVariants": 3,"url": "https://deathwishcoffee.com/products/gingerdead-coffee-cups","imageUrl": "https://cdn.shopify.com/s/files/1/0271/7209/files/Gingerdead_10ct_Angle_Vertical_Pods_DTC.jpg?v=1791810372","createdAt": "2021-11-02T18:17:23Z","publishedAt": "2026-10-08T13:15:33Z","variants": [{"variantId": "39534412496951","variantTitle": "10-count","sku": "LCGNGD10","options": { "Size": "10-count" },"variantPrice": 12.99,"variantCompareAtPrice": null,"variantOnSale": false,"variantDiscountPct": null,"variantAvailable": true,"variantUrl": "https://deathwishcoffee.com/products/gingerdead-coffee-cups?variant=39534412496951","variantImageUrl": "https://cdn.shopify.com/s/files/1/0271/7209/files/Gingerdead_10ct_Angle_Vertical_Pods_DTC.jpg?v=1791810372"}],"changeType": null,"changes": null,"previousPrice": null,"priceChangePct": null,"previousAvailable": null,"firstSeenAt": null,"scrapedAt": "2026-10-20T00:21:01Z"}
With tracking on, a changed product carries the change (a real row from a monitor run on 19 Oct 2026 UTC, shown with the field names of the current version; other fields as above). With tracking on, each variant also has variantChangeType, variantPreviousPrice, variantPriceChangePct, variantPreviousAvailable and variantFirstSeenAt:
{"title": "Men's Tree Runner NZ - Medium Grey (Blizzard Sole)","price": 100,"available": true,"variantCount": 14,"availableVariants": 2,"changeType": "out_of_stock","changes": [{ "variantId": "41206419226704", "variantTitle": "10", "sku": "A11925M100", "change": "out_of_stock","variantPrice": 100, "variantPreviousPrice": 100, "variantAvailable": false, "variantPreviousAvailable": true },{ "variantId": "41206419292240", "variantTitle": "11", "sku": "A11925M110", "change": "out_of_stock","variantPrice": 100, "variantPreviousPrice": 100, "variantAvailable": false, "variantPreviousAvailable": true }],"previousPrice": 100,"priceChangePct": 0,"previousAvailable": true,"firstSeenAt": "2026-10-19T17:12:50Z"}
A removed product keeps the identifying fields saved when it was last seen (title, handle, URL, vendor, and in variants each variant's SKU, variantPreviousPrice and variantPreviousAvailable); price and available are null because the product is no longer listed.
A store that can't be read gives one free error row with input, error and errorKind (all product fields null); see the Errors view.
Output fields
| Field | Description |
|---|---|
store, storeName | Store domain (without www.) and the store name published by Shopify |
productId, handle, title | Shopify product ID, URL handle and title |
vendor, productType, tags | Brand / vendor, product type and tags as set by the store |
price, priceMax | Lowest and highest variant price |
onSale, discountPct, compareAtPrice | Whether any variant is on sale; the largest discount of any variant in percent, and that variant's "was" price |
currency | The store's main currency (ISO code) |
available, variantCount, availableVariants | In stock if any variant is; number of variants and of variants in stock. The public feed has no stock quantities |
url, imageUrl | Product page and image |
createdAt, publishedAt | ISO 8601 timestamps in UTC |
variants | Every variant: variantId, variantTitle (e.g. Black / M), sku, options, variantPrice, variantCompareAtPrice, variantOnSale, variantDiscountPct, variantAvailable, variantUrl (with ?variant=), variantImageUrl |
description | Plain-text description (only with Include product description) |
changeType, changes | Change since the previous run (tracking on; null when off), and the variants that changed |
previousPrice, priceChangePct, previousAvailable | Lowest price and stock in the previous run, and the change of the lowest price in percent |
firstSeenAt, scrapedAt | When this tracker first saw the product, and when this run read it |
input, error, errorKind | Only on free error rows |
There is no "updated at" field: Shopify's public feed now returns the time of the request as every product's updated_at, so it says nothing about the product.
The dataset has ready-made views: Products, Changes, Errors and All fields.
How change tracking works
With Track price and stock changes on, the Actor saves a compact, encrypted snapshot of each store in a named key-value store in your Apify account (shopify-price-stock-tracking, one record per store, collection scope and Tracking ID). On the next run every variant is compared with it, and each product row reports what happened to its variants:
changeType | Meaning |
|---|---|
new | Product first seen by this monitor (in a monitor that has only ever read part of the catalog: published or re-published since the previous run, see below) |
back_in_stock / out_of_stock | A variant became available / unavailable |
price_down / price_up | A variant is cheaper / dearer than in the previous run |
variant_added / variant_removed | The product got a new variant / lost one |
removed | Product was listed before and is no longer listed. Output once, then forgotten |
unchanged | None of the above |
If several apply, changeType is the first in this table and changes lists every changed variant (change: back_in_stock, out_of_stock, price_down, price_up, new or removed).
Good to know:
- Output only changed rows skips unchanged products. Each product compared costs $0.0001 (
product-checked, once per product and run), and each changed product returned $0.001. The first run of a store saves a baseline and returns nothing for it (its products are checked and cost $0.0001 each); changes appear from the second run. - If a run stops before a store is fully read (for example at your maximum charge), the next run completes the baseline: products not in it yet are added without being reported (or charged) as
new. Products your maximum charge can't pay for are not compared or remembered in that run. - Without Output only changed rows, the first run returns every product as
new. - Products that vanish from a store's listing are re-checked one by one on their own page before they are reported as
removed(the feed can shift while a big store is being read). If there are more than a run can check in about two minutes, or more than your maximum charge could pay for, the rest are checked in the next run. In collection mode, a product that leaves the collection is reported asremovedfrom that collection; when the collection spans several pages, it is read a second time first, and only products missing twice are reported. - Removed products are only reported when the whole store (or collection) was read in that run: without Max products per store, or with a limit above the catalog size. If a run stops early (network trouble or your spending limit), unreported changes are kept and reported next time.
- Max products per store limits the products read (newest first), so with Output only changed rows it is the number of products checked per run.
- Monitors that have only ever read part of the catalog (every run used Max products per store) don't know the older products outside their window. When such a monitor meets a product it doesn't know, it reports
newonly if the product was published after the previous run (or, without a publish date, created after it; with neither date it is notnew). Otherwise the product is added asunchanged: without output and for the price of the check with Output only changed rows. So raising or removing the limit doesn't report the whole catalog asnew, and older products that move into the first N of a manually sorted collection aren't either. The trade-off: such a monitor also doesn't report an older product that is newly added to a collection (for example put on sale). To monitor additions to a collection, run the monitor once without a limit (or keep it at 0): from then on it knows every product, and every product it doesn't know is reported asnew, also in runs with a limit. - A store that re-publishes an old product gives it a new publish date, so in a monitor of the previous point it is reported as
new; itscreatedAtshows that it is an old product. - Use different Tracking IDs for independent monitors. Changing the collections starts a new snapshot.
- Monitors started with version 1.0 continue with the same Tracking ID. Monitors that used one row per variant (the default) keep their snapshot. Monitors that used
outputLevel: "product"are compared once by lowest price and stock, and per variant from the next run; their old plain-text record is replaced by an encrypted one.
Pricing
Pay per event, no subscription and no extra usage fees:
| Event | Price |
|---|---|
product: one product row in the dataset, with all its variants (including new, changed and removed products) | $0.001 ($1 per 1,000 products) |
product-checked: one product read and compared with the previous run, only with Output only changed rows (once per product and run, also on the first run that saves the baseline) | $0.0001 ($0.10 per 1,000) |
| Actor start (standard Apify event, per run) | $0.00005 |
Examples:
- Full export of a 692-product store with 7,454 variants (allbirds.com): about $0.69.
- A 148-product store: about $0.15.
- A daily "only changed rows" monitor of a 692-product store that finds 10 changed products: 692 x $0.0001 + 10 x $0.001 = about $0.08 per run.
- A 10,000-product store with Output only changed rows: about $1.00 per run plus $0.001 per changed product. Limit the products checked with Max products per store or watch a collection to pay less.
Apify platform costs (compute, storage, proxy, data transfer) are included in the price. Stores that fail (not Shopify, unknown domain, feed disabled, blocked) and their error rows are free, and so is the SUMMARY record. Set Maximum cost per run in the run options (or maxTotalChargeUsd via API) to cap spending; the Actor stops cleanly when the cap is reached, and everything returned up to that point is in the dataset.
Use it from the API or an app
Run synchronously and get the rows in one call (keep your own API token in the APIFY_TOKEN environment variable):
curl -X POST "https://api.apify.com/v2/acts/fairfetch~shopify-price-stock-tracker/run-sync-get-dataset-items?maxTotalChargeUsd=0.50" \-H "Authorization: Bearer $APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"stores": ["allbirds.com"], "collections": ["sale"]}'
The Actor runs with limited permissions and pay-per-event pricing without usage fees. Each run also writes a machine-readable SUMMARY record with per-store counts, errors and notes.
Performance and limits
- One request per 250 products. In our cloud tests (Apify, residential proxy) a 692-product store was read in about 5 seconds and the 148-product store of the example in about 3 seconds; larger stores take longer, since each page of 250 products is one request and a store gets at most two requests per second.
- Requests are polite: one request at a time per store and at most 2 requests per second per store, automatic retries with exponential backoff for network and server errors, and timeouts. Several stores are read in parallel, each with its own proxy session.
- Shopify's rate limit (HTTP 429) slows the store down and is retried from a new proxy session. Shopify's bot protection (HTTP 430) is retried at most once, from another proxy IP address; if it comes again, the store is reported as
blockedand nothing is charged for it. - Shopify's public feed lists at most 25,000 products per store or per collection. For bigger stores, add collection handles; the run summary tells you when a store hit the limit (removed-product detection is skipped for that store).
- The feed lists products published to the online store. Prices are the store's main-currency prices; stores selling in several countries may show converted prices on their website.
- Memory: the default 512 MB handles stores with tens of thousands of variants. For tracking stores with more than about 60,000 variants, run with 1 GB.
- If the platform moves a run to another server, the run continues where it was: rows already delivered are not repeated or charged again.
Which stores work
Shopify stores that use Shopify's standard online store. The Actor checks each store and reports problems as a free error row, in the run summary and in the status message, then continues with the other stores. A run where every store fails because of the input still finishes successfully, with the error rows:
- Invalid entry (
invalid_input): a store name with spaces ("Death Wish Coffee") or a bare word ("allbirds"). Enter the domain or the fullname.myshopify.comaddress. - Not found (
not_found): the domain does not exist, or Shopify says "Store unavailable". - Not a Shopify store (
not_shopify): the site does not run on Shopify. - Headless Shopify store (
headless): the brand uses Shopify behind a custom or Hydrogen storefront that does not serve the public feed. If you know the store'sname.myshopify.comaddress, try that. - Feed disabled (
disabled) or password-protected (password): the store has switched off its public product feed, or is not open to the public yet. - Blocked (
blocked): the store refused access (HTTP 403, or Shopify's bot protection twice). Try again later.
FAQ
Do I need a Shopify account, app or API key? No. The Actor reads the same public product feed that the store's own website exposes to every visitor.
How do I know if a store runs on Shopify? Just add it. If it isn't on Shopify (or doesn't serve the feed) the error row and the run summary say so, and you are not charged for it.
Why one row per product, not per variant?
A product row carries all its variants in variants, so you pay once per product however many sizes and colours it has. To get one row per variant, read the dataset through Apify's API with unwind=variants (for example https://api.apify.com/v2/datasets/<datasetId>/items?unwind=variants&format=csv): each row then has the product's fields and one variant's variant… fields side by side.
What does product-checked cost me, and when?
Only with Output only changed rows: $0.0001 for each product the run reads and compares (also on the first run, which saves the baseline). A full export, or tracking without that option, never charges it.
Can I monitor only a sale or new-arrivals collection?
Yes. Paste the collection URL (https://store.com/collections/sale) or add the handle (sale) under Collection handles.
How often should I run it? Daily is typical for price monitoring; hourly works for restock alerts. Use Schedules in Apify Console with Track price and stock changes and Output only changed rows on.
Can I get stock quantities?
No. Shopify's public feed only says whether each variant is available. The Actor reports variantAvailable true / false per variant (and available for the product) and tracks when it changes.
Why are some prices 0 or some products odd?
The Actor returns what the store publishes. Some stores list gift cards, samples or placeholder products; filter them by productType, tags or price.
What does "newest first" mean for Max products per store? The store feed lists the most recently published products first, so a limit of 100 returns the 100 newest products. Collections keep the order set by the store.
Do I need a proxy? It's on by default and included in the price: Apify residential proxy, because Shopify throttles requests from cloud and datacenter servers (HTTP 429 / 430). In tests from Apify's cloud, 5 of 5 stores worked with it, against 1 of 5 with datacenter IPs.
What happens if one store fails?
The other stores still run. The failure and its reason are in a free error row, in the SUMMARY record (perStore[].error, errorKind) and in the run status message.
Is the saved snapshot readable by me? The tracking record in your account holds only the store, scope, Tracking ID, a row count and the save time in readable form; the snapshot itself is encrypted and only used by the Actor.
Changes in version 1.1 (for users of 1.0)
- One row per product instead of one row per variant: the variants are in
variants, and the price is $1 per 1,000 products instead of per 1,000 variants, so a full export costs less. Fields that were per variant (variantId,variantTitle,sku,options, the variant's price and stock) are now insidevariants;updatedAtwas removed. Useunwind=variants(see the FAQ) if your integration needs one row per variant. - "Output only changed rows" monitors now cost $0.0001 per product checked plus $0.001 per changed product returned, and report changes per product with the changed variants listed in
changes. outputLevelis no longer used (old inputs, tasks and schedules still run). Existing monitors continue with the same Tracking ID.- Store names and bare words ("allbirds") are no longer guessed as
allbirds.myshopify.com; enter the domain. - Stores that can't be read now appear as free error rows, and a run whose stores all fail because of the input finishes successfully.
Legal
This Actor only reads product information that stores publish openly to every visitor, through the public product feed of their own website. It does not log in, does not solve CAPTCHAs and collects no personal data. When Shopify's bot protection answers a request with HTTP 430, the request is retried at most once from another proxy IP address; if it comes again, the store is reported as blocked and nothing is charged for it. You are responsible for how you use the data and for complying with the terms of the websites you read and the laws that apply to you. This Actor is not affiliated with, endorsed or sponsored by Shopify Inc. or the stores it reads; "Shopify" is used only to describe where the data comes from.