# Shopify Store Products & Price Tracker — Any Store (`northbell/shopify-store-products-tracker`) Actor

Any Shopify store's catalogue as clean rows: price, compare-at price, sale flag, stock, SKU, vendor, tags and image per variant. Run it again or on a schedule for price drops, restocks, sell-outs, new and removed products since the last run. Store domains or collection URLs. No login.

- **URL**: https://apify.com/northbell/shopify-store-products-tracker.md
- **Developed by:** [Northbell](https://apify.com/northbell) (community)
- **Categories:** E-commerce, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 products

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Store Products & Price Tracker

Give it any Shopify store — a domain like `allbirds.com` or a collection URL — and get every product as clean rows: **price, compare-at price, sale flag, in-stock flag, SKU, vendor, product type, tags and image for each variant**. **$0.50 per 1,000 rows**, no login, no API key. Most Shopify product scrapers stop at today's list; this one also tells you **what changed since your last run**.

Run it again — or put it on a daily schedule — and every row also says **what changed since the last run**: the old price and the price change, items back in stock or sold out, products that are new, and a free summary row per store that counts them and lists the ids of removed products. A plain product scraper hands you today's list and leaves the comparison to you; this is the part of competitor price and stock monitoring you would otherwise build yourself.

### What you get

One row per variant (size, colour…) — or per product, if you switch variants off.

| Field | What it is |
|---|---|
| `type`, `observedAt` | `product` for these rows; when the row was read (UTC) |
| `store`, `collection` | the store (without `www.`) and, when you read a collection, its handle |
| `productId`, `handle`, `title`, `vendor`, `productType`, `tags`, `url`, `imageUrl` | the product, as the store lists it; `url` opens the exact variant when variants are on |
| `variantId`, `variantTitle`, `sku`, `variantCount` | the variant, and how many variants the product has |
| `price`, `compareAtPrice`, `onSale`, `discountPercent`, `currency` | current price, the store's compare-at price when it set one, whether that makes it a sale (`onSale`: compare-at is above the price), the discount, and the currency the prices are in |
| `available` | `true` if the variant can be bought (Shopify's public feed says in stock or not, not how many) |
| `createdAt`, `publishedAt`, `updatedAt` | when the product was created, published and last changed (UTC) |
| `isNew`, `previousPrice`, `priceChange`, `priceChangePercent`, `becameAvailable`, `becameUnavailable` | the change since your previous run (`null` on the first run). `priceChange` is new minus old, so negative means cheaper |

Each store also gets one **free `store-summary` row**:

| Field | What it is |
|---|---|
| `products`, `variants`, `rows`, `onSaleProducts`, `onSaleShare` | what was read, and the share of products with at least one variant on sale |
| `catalogueComplete`, `stoppedBecause` | `true` when the whole catalogue was read; otherwise `limit` (Max products per store), `pages` (the store hands out fewer products per page than asked for) or `error` |
| `newSinceLastRun`, `removedSinceLastRun`, `removedSinceLastRunCount` | new products, and the ids of products that disappeared (first 500) |
| `priceChanges`, `priceDrops`, `priceIncreases`, `restocked`, `soldOut` | how many rows (variants, or products) changed price, came back in stock or sold out |
| `firstRun`, `previousRunAt`, `country`, `currency`, `note` | run context, and a plain-English note when something limits the comparison |

### Use it for

- **Competitor price monitoring** — schedule it daily on the stores you compete with and see every price drop and increase, with the old price
- **Sale and discount watch** — `onSale`, `discountPercent` and `onSaleShare` show how deep and how wide a competitor's promotion is
- **Restock and sell-out alerts** — `becameAvailable` and `becameUnavailable` per size, for resellers and for buyers waiting on a drop
- **Assortment tracking** — new products and removed products per store, per collection

### Input

```json
{ "stores": ["allbirds.com", "https://colourpop.com"], "maxProductsPerStore": 1000, "includeVariants": true, "country": "US" }
```

- **Stores** — domains or store URLs, one per line. A collection URL such as `https://www.allbirds.com/collections/mens-sneakers` reads just that collection; the **Collection** field does the same for every store in the list.
- **Max products per store** — 1,000 by default, up to 20,000.
- **One row per variant** — on by default. Off gives one row per product with its lowest price, and `available` is `true` if any variant is in stock.
- **Country** — which country's prices to read (default `US`).

The previous values are remembered per store, collection, country and row mode in a named key-value store in your Apify account, so the second run of the same Actor already shows changes. Schedule it (Apify → Schedules) for a daily history.

### Pricing

**$0.50 per 1,000 rows**, plus $0.01 per run. With variants on, every size and colour is a row: a store with 700 products and 7,500 variants is about $3.75, and the same store with variants off (700 rows) is about $0.35. Error rows and store-summary rows are free. The $0.01 run-start fee is the one charge every run pays, even one that returns nothing.

### Notes

- Public data only: the product feed every Shopify store publishes for visitors. No customer, reviewer or other personal data, and no product descriptions are returned. Titles, vendors and tags are cleaned of HTML and entities (`&amp;` becomes `&`).
- **Prices follow the country.** Stores that sell in several currencies show a different price per country, so the Actor pins one country (US unless you change it; `UK` is read as `GB`) and reads the currency from the store's own reply. `currency` is `null` only if a store does not report one. Changing `country` starts a separate history for that store, and if a store switches currency between runs the summary says so and does not report price changes.
- **New and removed products need the whole catalogue.** They are only reported when this run and the previous run both read everything (`catalogueComplete` is `true`). If a run stops at Max products per store, `isNew` can be `null` for products it has not seen before and `removedSinceLastRun` is `null` — set the limit above the store's size to track them. With variants on, a new size on an old product is `isNew: true`, while `newSinceLastRun` in the summary counts products.
- Fields that are empty on some rows by nature: `compareAtPrice` is filled only where the store set a compare-at price (it can equal the price, then `onSale` is `false`) and `discountPercent` only while `onSale` is `true`; `productType`, `sku` and `imageUrl` are blank where the store leaves them blank; `collection` appears only when you read a collection; the six change fields are `null` on a store's first run (and everything but `isNew` for an item that is new). With variants off, `variantId`, `variantTitle` and `sku` are `null` and `price` is the lowest variant price. `variantTitle` is `Default Title` for products with a single variant — that is Shopify's own name for it.
- Some stores cannot be read: those behind a bot-protection wall (Cloudflare and similar), password-protected stores, and sites that are not on Shopify. Each gets a free error row saying why — nothing is made up. Not sure a competitor runs on Shopify? The [Tech Stack Detector](https://apify.com/northbell/tech-stack-detector) tells you what a site is built with.
- Shopify's feed pages through at most 25,000 products, so a single store is capped at 20,000. A catalogue of 5,400 products and 24,500 variant rows is read in about 25 seconds and fits in the default 256 MB; for catalogues several times bigger, set the run's memory to 512 MB.

Looking at the Shopify *app* side instead? See the [Shopify App Reviews Scraper](https://apify.com/northbell/shopify-app-reviews-scraper).

### For AI agents

`{"stores": ["allbirds.com"], "maxProductsPerStore": 5}` returns the variant rows of 5 products (about 60 rows) with price, compare-at price and availability, plus one free `store-summary` row. Run it again with the same input to get `priceChange`, `becameAvailable` and `isNew` filled in. Rows have `type`: `product`, `store-summary` or `error`.

# Actor input Schema

## `stores` (type: `array`):

One store per line: a domain (allbirds.com) or a store URL. A collection URL (store.com/collections/sale) reads just that collection. Not sure a site runs on Shopify? Try it: a store that is not Shopify gets a free error row.

## `collection` (type: `string`):

Read only this collection in every store, by its handle (the last part of store.com/collections/mens-sneakers is mens-sneakers). Leave empty for the whole catalogue. A collection URL in the stores list wins over this.

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

Stop after this many products in each store. With variants on, every size and colour is a row, so 1,000 products can be several thousand rows. New and removed products are only reported when a run reads the whole catalogue, so set this above the store's size to track them.

## `includeVariants` (type: `boolean`):

On: one row per variant (size, colour...), each with its own price, stock and SKU. Off: one row per product with its lowest price and 'in stock' if any variant is.

## `country` (type: `string`):

Two-letter country code whose prices to read (US, GB, DE, JP...). Stores that sell in several currencies show a different price per country, so a fixed country keeps run-to-run price changes honest. Empty means US.

## Actor input object example

```json
{
  "stores": [
    "allbirds.com"
  ],
  "maxProductsPerStore": 20,
  "includeVariants": true,
  "country": "US"
}
```

# Actor output Schema

## `products` (type: `string`):

Price, compare-at price, stock and the price and stock change since the last run, per variant (or per product).

## `stores` (type: `string`):

Per store: products, share on sale, new and removed products, price changes, restocks and sell-outs since the last run.

# 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 = {
    "stores": [
        "allbirds.com"
    ],
    "maxProductsPerStore": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("northbell/shopify-store-products-tracker").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 = {
    "stores": ["allbirds.com"],
    "maxProductsPerStore": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("northbell/shopify-store-products-tracker").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 '{
  "stores": [
    "allbirds.com"
  ],
  "maxProductsPerStore": 20
}' |
apify call northbell/shopify-store-products-tracker --silent --output-dataset

```

## MCP server setup

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

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/s693CJNMLeXPMSwhE/builds/pjkbiab9Yw5nk9XTo/openapi.json
