# Shopify Products Scraper & Price Monitor (`oldjard/shopify-products-price-monitor`) Actor

Scrape every product from any Shopify store, custom domains included: prices, compare-at prices, discounts, SKUs, variants, stock status and images. One row per product or per variant. Price monitor mode returns only new, changed and removed products. $1 per 1,000 products.

- **URL**: https://apify.com/oldjard/shopify-products-price-monitor.md
- **Developed by:** [Joshua White](https://apify.com/oldjard) (community)
- **Categories:** E-commerce, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Shopify Products Scraper & Price Monitor

**Scrape every product from any Shopify store**, custom domains included: titles, prices, compare-at ("was") prices,
discounts, SKUs, variants, stock status, images, vendors and tags. Or turn on the **price monitor** and get only the
products whose price or stock changed since the last run. A **Shopify scraper** and **competitor price tracker** in
one, with no browser, no login and no proxies.

**Try it in one click:** the input is prefilled with Allbirds and ColourPop's lipstick collection (50 products each).
Apify's free plan covers about 5,000 products a month.

### What Shopify product data do you get?

- **The whole catalog**, checked against the store's own product count. Tested on 11 stores up to 10,346 products
  (Gymshark): every count matched exactly; 234 of 234 spot-checked variants matched on price.
- **Prices you can sort by:** price, compare-at price, `onSale`, `discountPercent`, per product and per variant, as
  numbers in the store's currency.
- **One row per product or one row per variant** (size/color, SKU, price, availability flat, ready for a spreadsheet).
- **Stock levels and barcodes** (optional) when the store exposes them.
- **Collections or single products:** paste a collection or product URL to read only that.

### How to scrape a Shopify store in 3 steps

1. Add store URLs: `allbirds.com`, a collection like `https://colourpop.com/collections/lips`, or a product page.
2. Optional: **One row per variant**, **Max products per store**, collection filters, stock levels, or the
   **price monitor**.
3. Click **Start**, then download CSV, Excel or JSON, or read it through the API.

### How much does it cost to scrape Shopify?

**$1 per 1,000 products**, so Apify's $5 monthly free credit covers about 5,000 products. In monitor mode you pay only for new, changed or removed products; unchanged products are
free. Stores that fail (not Shopify, password-protected, feed turned off, domain that does not resolve) are free. A 1,000-product competitor
checked daily, with about 20 changes a day, costs about $0.60 a month after the first run.

### Input example

```json
{
    "startUrls": ["https://www.allbirds.com", "https://colourpop.com/collections/lips"],
    "outputMode": "products",
    "maxProductsPerStore": 50,
    "includeInventory": false
}
```

This reads up to 50 products per store (100 in all, about $0.10). Set `maxProductsPerStore` to 0 for whole catalogs;
Allbirds alone has about 700 products.

### Output example

One row per product (variant list and images shortened):

```json
{
    "store": "www.allbirds.com",
    "storeName": "Allbirds",
    "currency": "USD",
    "productId": 7340901859408,
    "handle": "womens-allbirds-flip-flop-dusty-pink",
    "title": "Women's Allbirds Flip Flop - Dusty Pink",
    "url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
    "vendor": "Allbirds",
    "productType": "Shoes",
    "tags": ["allbirds::gender => womens", "allbirds::material => sugar"],
    "price": 25,
    "priceMax": 25,
    "compareAtPrice": 50,
    "onSale": true,
    "discountPercent": 50,
    "available": true,
    "variantsCount": 7,
    "availableVariantsCount": 1,
    "options": [{ "name": "Size", "values": ["5", "6", "7", "8", "9", "10", "11"] }],
    "variants": [
        {
            "variantId": 42146889039952,
            "title": "5",
            "sku": "A12513W050",
            "price": 25,
            "compareAtPrice": 50,
            "onSale": true,
            "discountPercent": 50,
            "available": false,
            "option1": "5",
            "option2": null,
            "option3": null,
            "grams": 455,
            "requiresShipping": true,
            "taxable": true,
            "imageUrl": null,
            "url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink?variant=42146889039952",
            "position": 1,
            "updatedAt": "2026-10-05T18:53:32.000Z"
        }
    ],
    "imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345",
    "images": ["https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345"],
    "descriptionText": "Sun on your feet. Comfort underneath. Light, easy, and made for warm weather...",
    "descriptionHtml": "<p>Sun on your feet. Comfort underneath...</p>",
    "publishedAt": "2026-09-25T23:58:13.000Z",
    "createdAt": "2026-03-27T21:03:50.000Z",
    "updatedAt": "2026-10-05T18:53:32.000Z",
    "collection": null,
    "scrapedAt": "2026-10-05T18:53:29.584Z"
}
```

With **One row per variant**, each row has the product fields plus `variantId`, `variantTitle`, `sku`, `price`,
`compareAtPrice`, `onSale`, `discountPercent`, `available`, `option1Name`/`option1Value` (up to 3 options), `grams`,
`variantImageUrl`, `variantUrl`, and the product's `productPriceMin`/`productPriceMax`. With stock levels on,
`inventoryQuantity` and `barcode` are added (per variant, and a product total when every variant's is known).

| Field | Meaning |
|---|---|
| `price` | Product rows: the lowest variant price. Variant rows: that variant's price. A number in `currency`. |
| `compareAtPrice` | The "was" price the store shows crossed out. `null` when there is none. |
| `onSale`, `discountPercent` | `true` when a variant's price is below its compare-at price; the largest discount in percent. |
| `available` | Whether it can be bought now (product rows: any variant). |
| `currency` | The store's own currency, from its shop information. |
| `collection` | The collection the product was found in, when you read collections. |

Missing values are `null`, never guessed.

#### Run summary

Each run writes an `OUTPUT` record (in the Console: **Storage → Key-value store**): per store, the products in the store, products listed and
output, monitor counts, how many variants exposed stock levels, and why a store failed.

### Price-change monitor

Turn on **Monitor: only new, changed and removed products** and schedule the actor (for example daily).

- The first run remembers every product. It outputs them all as `new`, or nothing if you turn off
  **Output the whole catalog on the first run**.
- Later runs output only products whose price, compare-at price or stock status changed (`changeType: "changed"`),
  new products (`"new"`) and products that disappeared (`"removed"`).
- Each product row gets `previousPrice` and a `changes` list with old and new values per variant. In per-variant
  mode you get one row per changed variant with `previousPrice`, `previousCompareAtPrice` and `previousAvailable`.
- Memory is kept per **state key** and store URLs, so you can run several independent monitors.

Example change (per-variant mode, illustrative values):

```json
{
    "store": "shop.example.com",
    "title": "Canvas Tote",
    "variantId": 50020935008498,
    "sku": "TOTE-01",
    "price": 45,
    "previousPrice": 52,
    "compareAtPrice": null,
    "previousCompareAtPrice": null,
    "available": true,
    "previousAvailable": true,
    "changeType": "changed",
    "url": "https://shop.example.com/products/canvas-tote"
}
```

### Speed and limits

- About 250 products per request. 19,040 products from 11 stores took 29 seconds in testing.
- **Stock levels** need one extra request per product: about 2 products per second per store.
- Shopify's public feed stops at 25,000 products per store or collection. For a bigger catalog the run summary says
  so; split it with **Collections**.
- Requests are polite: at most 2 per second per store, with retries, and `Crawl-delay` is honored.
- Some Shopify stores use a headless storefront (Hydrogen or a custom front end) that turns the public feed off.
  Those are reported as such and not charged.

### Responsible use

The actor reads only the public product feeds that Shopify stores publish for every visitor. It follows each
store's `robots.txt` and skips stores that disallow access, sends a clear User-Agent (`ShopifyProductsScraper`), uses
no proxies, and needs no login. It collects product data only, no customer or personal data. Use the data in line
with the law where you are and the stores' terms.

### Ready-made examples

Each one opens this actor with the input already filled in. Click **Try** to run it, or change the input to fit your own list.

- [Scrape a Shopify store catalog with prices](https://apify.com/oldjard/shopify-products-price-monitor/examples/shopify-store-catalog-prices)
- [Products on sale in a Shopify store](https://apify.com/oldjard/shopify-products-price-monitor/examples/shopify-products-on-sale)
- [Shopify variants with SKU, barcode and stock](https://apify.com/oldjard/shopify-products-price-monitor/examples/shopify-variants-sku-inventory)
- [Monitor competitor Shopify prices daily](https://apify.com/oldjard/shopify-products-price-monitor/examples/shopify-competitor-price-monitor)
- [Scrape one collection of a Shopify store](https://apify.com/oldjard/shopify-products-price-monitor/examples/shopify-single-collection)
- [Compare products and prices across Shopify stores](https://apify.com/oldjard/shopify-products-price-monitor/examples/compare-shopify-stores)

### More tools from oldjard

- [Tech Stack Detector](https://apify.com/oldjard/tech-stack-detector): what any list of websites is built with.
- [Sitemap URL Extractor](https://apify.com/oldjard/sitemap-url-extractor): every URL on a website, for RAG and SEO.
- [Workday, Greenhouse, Lever & Ashby Jobs Scraper](https://apify.com/oldjard/ats-career-site-jobs): every open job from company career sites.
- [Bulk Website Screenshot & URL to PDF](https://apify.com/oldjard/screenshot-pdf): screenshots and PDFs of any list of pages.
- [AI Web Scraper (your own key)](https://apify.com/oldjard/ai-web-scraper): describe fields in English, get JSON.
- [Website Change Monitor](https://apify.com/oldjard/website-change-monitor): a before/after diff by webhook, Slack or Discord when a page changes.
- [Company Registry Lookup](https://apify.com/oldjard/company-registry-lookup): UK Companies House, Spain, France, Finland and Norway in one schema.
- [UK & EU Public Tenders](https://apify.com/oldjard/uk-eu-public-tenders): Find a Tender and TED notices in one table, with daily only-new alerts.

### Use it from an AI agent or the API

- **Minimal input:** `{"startUrls": ["allbirds.com"], "maxProductsPerStore": 50}`. Set `maxProductsPerStore` to cap
  the work and the cost.
- **Cost:** $0.001 per product returned (with all its variants). Stores that fail are free. 50 products = $0.05.
- **Run time (our runs):** about 5 s for up to 100 products; 28 s for 5,570 products from 3 stores.
- **Results:** the default dataset, one row per product; per-store status is the `OUTPUT` record in the key-value
  store.
- Works over the Apify MCP server (`search-actors`, then `call-actor`) and is eligible for agentic payments (x402).

### FAQ

**Does it work on any Shopify store?** Any store with the standard public product feed. Headless storefronts that
turn the feed off (for example Hydrogen) are reported and not charged; in testing that was 3 of 18 stores.

**Can I get alerts?** Schedule it in monitor mode and connect Apify's Slack, email or webhook integration to the run.

**How do I know a site is on Shopify?** Just paste it. The actor checks, and tells you if it is not.

**Why are prices in one currency?** The feed gives prices in the store's own currency (shown in `currency`).
Stores that sell in several currencies convert at checkout.

**Why is `inventoryQuantity` null?** The store hides stock levels. Many do; `available` still tells you whether it
can be bought.

**Can I track just a few products?** Yes: paste their product URLs and turn on the monitor.

### Changelog

- 0.1: first release. Whole store, collections, single products; per-product or per-variant rows; stock levels;
  price-change monitor.

# Actor input Schema

## `startUrls` (type: `array`):

Shopify stores, one per line. A bare domain (allbirds.com) or any page of the store reads the whole catalog. A collection URL (https://colourpop.com/collections/lips) reads just that collection. A product URL (https://www.allbirds.com/products/mens-tree-runners) reads just that product. Custom domains work; the actor checks that each site is a Shopify store.

## `outputMode` (type: `string`):

One row per product (variants listed inside each row), or one flat row per variant (size, color…) with its own price, compare-at price, SKU and availability. Per-variant is easier in a spreadsheet. Either way you pay per product.

## `maxProductsPerStore` (type: `integer`):

Stop after this many products from each store. 0 or empty means no limit (the whole catalog).

## `collections` (type: `array`):

Only read these collections of each store, by handle (the part after /collections/ in the URL, e.g. 'mens-shoes') or full collection URL. Empty reads the whole store. A product in several collections is returned once.

## `includeDescription` (type: `boolean`):

Add the product description as HTML (descriptionHtml). The plain-text description (descriptionText) is always included.

## `includeInventory` (type: `boolean`):

Add each variant's stock quantity and barcode, when the store exposes them (many do, some hide them). Needs one extra request per product, so runs take longer (about 2 products per second per store).

## `onlyAvailable` (type: `boolean`):

Keep only products (or, per variant, variants) that can be bought now. Ignored in monitor mode.

## `onlyOnSale` (type: `boolean`):

Keep only products (or variants) priced below their compare-at price. Ignored in monitor mode.

## `onlyChanges` (type: `boolean`):

Price-change monitor. Compare each store with the previous run (same state key) and output only products that are new, removed, or whose price, compare-at price or availability changed, with the old and new values. Schedule it daily to track competitors' prices. You pay only for changes.

## `stateKey` (type: `string`):

Name of the monitor's memory. Use a different key for each separate monitor (for example 'competitors-daily'). Runs with the same key and the same store URLs share memory.

## `outputOnFirstRun` (type: `boolean`):

On a monitor's first run every product is new. On: output them all (as 'new'). Off: only remember them, output nothing and pay nothing; changes start from the next run.

## `trackAvailability` (type: `boolean`):

Also report a product when a variant goes in or out of stock. Off: only price and compare-at price changes count.

## `failOnSiteError` (type: `boolean`):

Strict mode for pipelines and health checks: fail the run when any store fails or lists no products. Off: failed stores are reported in the OUTPUT record and the run still succeeds.

## Actor input object example

```json
{
  "startUrls": [
    "https://www.allbirds.com",
    "https://colourpop.com/collections/lips"
  ],
  "outputMode": "products",
  "maxProductsPerStore": 50,
  "includeDescription": true,
  "includeInventory": false,
  "onlyAvailable": false,
  "onlyOnSale": false,
  "onlyChanges": false,
  "stateKey": "default",
  "outputOnFirstRun": true,
  "trackAvailability": true,
  "failOnSiteError": false
}
```

# Actor output Schema

## `results` (type: `string`):

Dataset, one row per product (or per variant with outputMode "variants"): store, storeName, currency, title, handle, url, vendor, productType, tags, price, priceMax, compareAtPrice, onSale, discountPercent, available, variants (with sku), options, images, collection, updatedAt. With onlyChanges, also changeType (new/changed/removed), previousPrice and changes.

## `summary` (type: `string`):

Run summary (JSON): products and rows output, stores requested and failed, and per store the product count vs the store's own total, status and a message.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "startUrls": [
        "https://www.allbirds.com",
        "https://colourpop.com/collections/lips"
    ],
    "maxProductsPerStore": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("oldjard/shopify-products-price-monitor").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "startUrls": [
        "https://www.allbirds.com",
        "https://colourpop.com/collections/lips",
    ],
    "maxProductsPerStore": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("oldjard/shopify-products-price-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://www.allbirds.com",
    "https://colourpop.com/collections/lips"
  ],
  "maxProductsPerStore": 50
}' |
apify call oldjard/shopify-products-price-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,oldjard/shopify-products-price-monitor"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/XN9TfjcQy91qcZmpc/builds/6PZJu7ypvJG69WzfX/openapi.json
