Universal Shopify Product Price Inventory Monitor avatar

Universal Shopify Product Price Inventory Monitor

Pricing

from $0.60 / 1,000 shopify variant extracteds

Go to Apify Store
Universal Shopify Product Price Inventory Monitor

Universal Shopify Product Price Inventory Monitor

Extract public Shopify products, variants, SKUs, prices and availability. Compare against a previous dataset to detect price changes, restocks and sold-out variants. Export catalog snapshots or changed rows for competitor monitoring and automation.

Pricing

from $0.60 / 1,000 shopify variant extracteds

Rating

0.0

(0)

Developer

Arezdev

Arezdev

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

Turn public Shopify catalogs into ready-to-use product, price, and inventory alerts, with one clean row per variant and automatic change detection.

Monitor competitor pricing, find restocks, synchronize SKUs, and track discounts without browsers. The Actor reads public bulk JSON endpoints, follows product sitemaps when necessary, and compares each variant against a previous dataset.

Best for public catalog monitoring and variant-level price alerts. Run once to create a baseline, then pass its dataset ID in previousDatasetId. Scheduled runs do not select a baseline automatically; stock availability does not guarantee an exact inventory quantity.

How to use

  1. Enter Shopify store domains, such as allbirds.com.
  2. Run the prefilled sample to save up to three variant rows. Remove maxItems and increase maxProductsPerStore for a larger catalog.
  3. Open the dataset to review prices and availability, then export JSON, CSV, or Excel.
  4. Select a completed baseline dataset in previousDatasetId to detect changes. Enable onlyChangedItems if you only want changes.

Minimal input:

{
"storeDomains": ["allbirds.com"],
"maxProductsPerStore": 3,
"maxItems": 3
}

For scheduled monitoring, create an Apify Schedule with the desired input. Keep a complete baseline by leaving onlyChangedItems and onlyInStock disabled for runs that will become future baselines. Your n8n, Make, or API workflow can pass the latest successful full run's dataset ID into the next run's previousDatasetId. Reusing a change-only dataset as the next baseline can classify unchanged variants as new. There is no implicit cross-run state or automatic dataset selection.

What data can you extract?

DataOutput fields
Store and productstoreDomain, productId, productTitle, handle, vendor, productType, tags, productUrl
Variant identityvariantId, variantTitle, sku, grams
Pricingprice, compareAtPrice, discountPercent
AvailabilityavailableForSale
ChangesdeltaStatus, previousPrice, priceDifference
Images and timestampsfeaturedImageUrl, updatedAt, scrapedAt
Diagnosticsstatus, errorMessage

IDs are strings to preserve precision. tags is an array of strings; all other fields are scalar values or null. A product with 12 variants produces up to 12 rows. Prices are decimal amounts in the storefront's returned currency context. Bulk decimal prices and Ajax integer subunit prices are normalized to the same representation. The output does not include a currency field, so compare runs using consistent storefront and proxy geography. Shopify documents these responses in its Product API reference.

Availability means the storefront considers a variant purchasable. It does not expose exact warehouse quantities; backorders may be purchasable. Missing availability is null.

Delta statuses use (storeDomain, variantId), normalizing a leading www. for comparison:

StatusMeaning
NEW_VARIANTNo successful match in the baseline; also used on the initial run.
BACK_IN_STOCKAvailability changed from false to true.
SOLD_OUTAvailability changed from true to false.
PRICE_DROPPEDCurrent price is lower than the baseline price.
PRICE_INCREASEDCurrent price is higher than the baseline price.
UNCHANGEDNo price or known availability change.

Availability changes take precedence when both price and stock change. priceDifference still records current minus previous price, rounded to cents. Missing products are not assumed sold out. Unknown availability does not create a stock event. Changes to titles, tags, discounts, or SKU alone do not change deltaStatus.

Input settings

SettingDefaultDescription
storeDomainsRequired; sample allbirds.comPublic domains or home URLs, at most 1,000.
collectionHandlesAll productsHandles applied to every store; overlapping products are deduplicated.
onlyInStockfalseSave known purchasable variants. Disable to retain SOLD_OUT changes.
onlyChangedItemsfalseExclude UNCHANGED rows. Requires previousDatasetId.
previousDatasetIdNoneBaseline dataset from this Actor; picker requests READ permission.
maxProductsPerStore250; sample 3Unique products visited before filtering, from 1 to 50,000 per store.
timeoutSecs15Per-URL deadline including retries and redirects, from 3 to 60 seconds.
proxyConfigurationDirect, then datacenter if blockedCustom URLs force proxy use. useApifyProxy: false forces direct access. Configured or residential fallback requires explicit useApifyProxy: true.
maxConcurrency25Requested upper bound from 1 to 100; automatically reduced for available memory.
maxItemsNo additional cap; sample 3Optional saved row limit across all stores, including errors, from 1 to 10,000,000.

The product cap applies to the union of collections per store. Filters can reduce saved rows; the Actor does not exceed the product cap to compensate. Error rows remain visible with any filter.

Output example

{
"storeDomain": "example.com",
"status": "SUCCESS",
"productId": "100000001",
"productTitle": "Everyday Runner",
"handle": "everyday-runner",
"vendor": "Example Brand",
"productType": "Shoes",
"variantId": "200000001",
"variantTitle": "Blue / M",
"sku": "RUN-BLUE-M",
"price": 90,
"compareAtPrice": 120,
"discountPercent": 25,
"availableForSale": true,
"deltaStatus": "PRICE_DROPPED",
"previousPrice": 100,
"priceDifference": -10,
"grams": 250,
"tags": ["running", "summer"],
"featuredImageUrl": "https://cdn.shopify.com/example.jpg",
"productUrl": "https://example.com/products/everyday-runner",
"updatedAt": "2026-10-19T12:00:00.000Z",
"scrapedAt": "2026-10-20T12:00:00.000Z",
"errorMessage": null
}

Unreachable stores and invalid responses produce ERROR rows with a safe domain and error code. Missing product details are null. No stack traces or proxy authentication details are included. Strings that could execute spreadsheet formulas receive a leading apostrophe; numeric prices and differences remain numeric.

Use the results in Clay, n8n, Make, or your API

  • Clay: Import JSON or CSV and match records using storeDomain plus variantId. Use deltaStatus and priceDifference to prioritize opportunities.
  • n8n: Start a run with the Apify node, retrieve dataset items, and route PRICE_DROPPED or BACK_IN_STOCK rows to your workflow.
  • Make: Start the Actor, iterate dataset rows, and filter on deltaStatus before updating a sheet, database, or alert.
  • API: Start runs through the Actor API, then read defaultDatasetId through the Dataset items API. Authenticate with an authorization header rather than a query string.

Pass previousDatasetId from your workflow to compare against the correct full snapshot. No storefront login or Shopify Admin API access is required.

Pricing and limits

The custom event is product-scraped. Each saved SUCCESS variant row counts as one event. Filtered variants do not incur this event. ERROR rows cost $0 in event fees. Compute, storage, and proxy usage may still have platform costs.

For example, at $0.001 per successful variant, 1,000 successful rows cost $1.00 in event fees and 1,000 error rows cost $0.00. This is an illustrative launch price, not a live Store quote; the actual event price is configured in Apify Console and shown on the Pricing tab. A run's maximum charge stops extraction cleanly before the custom event budget is exceeded.

Free Apify accounts are limited to 25 saved rows per run, including successes and errors. Paid accounts and local runs have no free-plan cap; product, optional row, run time, and budget limits still apply. The default run uses 256 MB RAM with a 3,600-second timeout. The sample saves at most three rows; network conditions determine its duration.

FAQ and troubleshooting

Will this extract every product from any Shopify store?

It paginates public products in batches of up to 250 and follows exposed sitemaps. Password protection, private B2B catalogs, region restrictions, and blocked endpoints can limit coverage. Raise maxProductsPerStore and remove the sample maxItems cap for a full public catalog, up to 50,000 products per store. Hidden inventory cannot be guaranteed.

What happens when /products.json is blocked?

HTTP 401, 403, or 404 triggers sitemaps plus /products/{handle}.js. Oversized JSON also uses this fallback. The Actor first requests /sitemap_products_1.xml, then discovers query-based or split product sitemaps through /sitemap.xml. Collection fallback requires public /collections/{handle}/products/{product} links in collection pages. If scope cannot be verified, it reports an error rather than returning unrelated products.

What happens with HTTP 403, 429, or 503?

Direct access is followed by the SHADER datacenter group and the default account datacenter proxy. Unavailable SHADER access moves to the next tier. Configured Apify groups, including residential, require explicit opt-in. Custom proxy URLs bypass this cascade. The Actor uses run-provided proxy access and does not fetch user profile information. The timeout covers the whole cascade; increasing timeoutSecs can help slow sources.

Why do I see no changes, fewer rows, or new variants on every run?

Check filters, caps, and the baseline. Filtered, partial, failed, or different-store baselines can produce incomplete comparisons. If the baseline cannot be read, the Actor saves error rows and stops comparison instead of labeling all variants new. Initial runs and empty baselines mark observed variants NEW_VARIANT.

Does a successful run guarantee every store succeeded?

Check status, errorMessage, row counts, and the final status message. Per-store HTTP, DNS, TLS, and parser failures do not crash the Actor. Invalid input exits with code 1. Caps and event budgets stop cleanly with code 0. Uncertain storage writes are not automatically repeated because a retry could duplicate rows or charges.

Can this run with limited permissions?

Yes. The dataset picker declares resourceType: "dataset" and resourcePermissions: ["READ"]. Select an accessible baseline. The Actor does not request the user profile endpoint. Its temporary comparison database is deleted at shutdown.

Development and deployment

Use Node.js 22.13 or later. The Node 22 container uses built-in SQLite to avoid an in-memory copy of the baseline.

npm ci
npm run build
npm test
npm run smoke

Tests use node:test for security, streaming, parsing, deltas, billing, history, and pagination. The live smoke requests Allbirds directly and reports up to three rows, duration, and process RSS without Apify event charges. Test and start runners set UV_THREADPOOL_SIZE=64, NODE_ENV=production, and TLS validation.

For local execution, save the minimal input to storage/key_value_stores/default/INPUT.json, then run npm start. The Actor does not load .env files. Alternatively, build and run the non-root multi-stage image:

docker build -t shopify-monitor .
docker run --rm -v "${PWD}/storage:/app/storage" shopify-monitor

The client validates every address and redirect, pins DNS without changing HTTP authority, verifies TLS, limits redirects to six, and caps decompressed responses at 2 MB JSON, 15 MB XML, and 4 MB HTML. XML DTD and external entities are rejected. Nesting is capped at 128, and partial HTML recovery requires a successful response with a valid retained prefix.

Before publishing, configure product-scraped and its actual price in Apify Console. Keep automatic dataset-item event pricing disabled so errors remain free of event fees; the Actor rejects conflicting pricing. Store monetization settings are managed by Apify Console outside this repository.