# Shopify Products Scraper — Prices, Variants, Stock (`architekture/shopify-products-scraper`) Actor

Scrape any Shopify store catalog: products, variant prices, sale prices, SKUs, availability, barcodes and stock counts where the store exposes them. Non-Shopify stores are detected and never charged. Pay per product.

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

## Pricing

from $0.50 / 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.

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 — Prices, Variants, Stock

**Recorded 18 Oct 2026: allbirds.com's first 250 products — 2,720 variants with prices, sale prices, SKUs and availability — in one request. With details on, 5 of 5 products came back with a barcode on every variant and live stock counts.**

From **Architekture**, makers of the AliExpress, Woolworths, Coles, Tesco, Pak'nSave and New World scrapers.

Give it store domains; get every product as clean JSON: title, vendor, type, tags, images, description, and every variant's price, compare-at (sale) price, SKU, options and availability. Each store is checked first: stores that are not Shopify, are walled, or don't answer get one free status row and are **never charged**. Turn on details for per-variant barcodes and real stock counts where the store exposes them.

🤖 **Built for AI workflows.** Every row is deterministic, schema-stable JSON — feed it straight to LLM apps, RAG pipelines, or agent tool calls via the Apify API, the Apify MCP server (`mcp.apify.com`), or AI coding agents like Claude Code and Cursor.
| 🛍️ Platform | 📍 Scope | 📦 Recorded | 💰 From | 🔑 Standout fields |
|---|---|---|---|---|
| Any Shopify store | Global | 250 products -> 2,720 variants in **one request** | $0.50 / 1,000 products | Per-variant barcodes, live stock counts |

### 🚀 Why choose this actor

- 🎨 **One row per product, all variants inside.** You pay per product, not per size and colour: a shoe in 13 sizes is one `product` row with `variants[13]`.
- 💸 **Sale detection built in.** `onSale`, plus `compareAtPrice` on every discounted variant; `minPrice`/`maxPrice` for the price range.
- 🔬 **Stock counts and barcodes.** With `includeProductDetails`, variants carry `inventoryQuantity`, `barcode` (GTIN/UPC) and the inventory policy — where the store publishes them.
- 🆓 **Probe-first, never pay for a dead store.** Each store gets a status — `OK`, `NOT_SHOPIFY`, `BLOCKED`, `UNREACHABLE`, `PARTIAL`, `ERROR` or `DUPLICATE` (same store listed twice, e.g. with and without `www.`, scraped once) — in `RUN_SUMMARY`; stores that could not be scraped also get a free row in the dataset.
- 🌐 **Fixes the `www.` trap.** Some stores only serve their product feed on one host (gymshark.com works, www.gymshark.com is walled); the actor tries the other one automatically.

### ⚠️ Worth knowing up front

- 🆓 **Shopify stores only.** Some large brands run a custom ("headless") storefront that switches the product feed off (e.g. ruggable.com in our tests) — those come back `NOT_SHOPIFY`, free. In our 12-store check, 8 served their product feed.
- 💸 **Some stores rate-limit or wall bots.** They come back `BLOCKED` with the reason (e.g. "rate limited by the store after retries"). Not charged.
- 📊 **Stock counts only where the store exposes them.** Many stores hide quantities; then you still get `available` per variant but no `inventoryQuantity`.
- ⭐ **No reviews.** Reviews live in third-party apps (Judge.me, Yotpo, Loox), not in Shopify's product feed.
- 🛑 **25,000 products per store at most** — Shopify's own page limit. `RUN_SUMMARY` warns when a store hits it.
- 💸 **Prices are in the store's currency** (`currency`, from the store's own settings), as shown to visitors from the actor's location.

### ⚡ Try the example

```json
{
  "storeUrls": ["allbirds.com", "deathwishcoffee.com"],
  "maxProductsPerStore": 100
}
```

### ⚙️ Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `storeUrls` | array of strings | required | Store domains or URLs. A collection URL (`https://store.com/collections/sale`) scrapes only that collection. Up to 200 stores. |
| `collections` | array of strings | — | Collection handles to scrape on every store instead of the whole catalog. |
| `maxProductsPerStore` | integer | `1000` | Products per store. `0` = whole catalog (max 25,000). |
| `includeProductDetails` | boolean | `false` | Adds barcodes, stock counts, inventory policy and selling plans. One extra request per product. |
| `proxyConfiguration` | object | Apify datacenter | Retries a 429 on a fresh IP. |
| `feedback` | string | — | A note to the maintainers. Read by a human. |

### 🧱 Stable fields for automations

Product rows have `recordType: "product"`; store status rows have `recordType: "store"`. Fields are **omitted** (not `null`) when the store does not publish them. New fields are only ever added.

| Field | Meaning |
|---|---|
| `recordType` | `product` or `store`. |
| `store`, `storeName`, `currency` | Host the products came from, the store's name, its currency. |
| `id`, `handle`, `title`, `url` | Shopify product id, handle, title, product page. |
| `vendor`, `productType`, `tags` | As set by the merchant. |
| `minPrice`, `maxPrice` | Lowest / highest variant price. |
| `onSale` | `true` when any variant has a compare-at price above its price. |
| `available` | `true` when any variant can be bought. |
| `variantCount`, `variants[]` | Each variant: `id`, `title`, `sku`, `price`, `compareAtPrice`, `available`, `options`, `grams`, `requiresShipping`, `image`. |
| `variants[].barcode`, `variants[].inventoryQuantity`, `variants[].inventoryManagement`, `variants[].inventoryPolicy`, `variants[].weightGrams` | With details only, where published. |
| `requiresSellingPlan`, `hasSellingPlans`, `mediaCount` | With details only: subscription / pre-order flags and number of media items. |
| `totalInventory` | Sum of positive variant quantities (details only). |
| `options` | e.g. `[{"name": "Size", "values": ["5", "6", …]}]`. |
| `image`, `images`, `description` | Main image, all images, description as plain text. |
| `createdAt`, `updatedAt`, `publishedAt` | Shopify timestamps. |
| `source` | `{source: "catalog" \| "collection", collection?, page}`. |
| Store rows: `status`, `reason`, `httpStatus` | Why a store was not scraped. |

### 📦 Output example

```json
{
  "recordType": "product",
  "store": "allbirds.com",
  "storeName": "Allbirds",
  "currency": "USD",
  "id": 7340901859408,
  "title": "Women's Allbirds Flip Flop - Dusty Pink",
  "handle": "womens-allbirds-flip-flop-dusty-pink",
  "url": "https://allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
  "vendor": "Allbirds",
  "productType": "Shoes",
  "minPrice": 25.0,
  "maxPrice": 25.0,
  "onSale": true,
  "available": true,
  "variantCount": 7,
  "variants": [
    { "id": 42146889203792, "title": "10", "sku": "A12513W100", "price": 25.0, "compareAtPrice": 50.0, "available": true,
      "options": ["10"], "grams": 594, "barcode": "196942293133", "inventoryQuantity": 9, "inventoryManagement": "shopify", "inventoryPolicy": "deny" }
  ],
  "options": [{ "name": "Size", "values": ["5", "6", "7", "8", "9", "10", "11"] }],
  "totalInventory": 9,
  "source": { "source": "catalog", "page": 1 }
}
```

(Variants abridged; with details off, the barcode/inventory fields are absent.) A store that could not be scraped:

```json
{ "recordType": "store", "store": "example.com", "status": "NOT_SHOPIFY", "reason": "no /products.json (not a Shopify store, or a headless storefront with the API disabled)", "httpStatus": 404 }
```

### 🧠 Advanced usage

- 💸 **Competitor price monitoring:** schedule the same `storeUrls` daily; join on `store` + `variants[].id` and compare `price`/`compareAtPrice`.
- 📈 **Sale tracker:** filter `onSale == true`, or scrape a store's `sale` collection only.
- 🔬 **Stock-out alerts:** with details on, watch `variants[].inventoryQuantity` / `available` flip.
- 🔬 **Product matching across retailers:** join on `variants[].barcode` with our Woolworths, Tesco or Coles actors.
- 💸 **Completion check:** read `RUN_SUMMARY` — `status` (`COMPLETE`, `PARTIAL`, `NO_PRODUCTS`, `BUDGET_LIMIT`, `TIME_LIMIT`, `STATE_SAVE_FAILED`, `INVALID_INPUT`, `NOT_CONFIGURED`), `products`, `charged`, and `stores[]` (each with `status`, `reason`, `delivered`, `pages`, `currency`, `publishedProductsCount`, `warnings`).

### 🔌 Integrations

Export as JSON, CSV, Excel or RSS; push to Google Sheets; trigger from Make, Zapier or n8n; webhook on run finish; schedule with the Apify Scheduler.

### 💸 How much does it cost?

1,000 products ≈ **$0.50** — every variant included, so a 2,720-variant store is still ~250 product rows. Details (barcodes/stock) $2.00 / 1,000. Dead stores are free. Platform usage is included.

### 💰 Pricing

| Event | Price | Covers |
|---|---|---|
| `product` | $0.50 / 1,000 | One product row, **all variants included** |
| `product-detail` | $2.00 / 1,000 | One product row that came back with barcodes or stock counts (`includeProductDetails`) |

Platform usage and proxy are included. Store status rows (`NOT_SHOPIFY`, `BLOCKED`, `UNREACHABLE`) are free, and nothing is charged for failed requests. If a detail request fails — or the store publishes neither barcodes nor stock for that product — it is delivered and charged as a plain `product`.

### ❓ FAQ

**How do I know if a site is a Shopify store?** Just add it. If it is not (or switched its feed off), you get a free `NOT_SHOPIFY` row.

**Why did a Shopify store come back `BLOCKED`?** It rate-limited or walled our requests after several retries on fresh IPs. Nothing was charged. Try again later.

**Do I pay per variant?** No — per product. A product with 30 variants is one `product` event.

**Can I get exact stock?** When the store's storefront publishes it, yes (`inventoryQuantity`, with details on). Many stores hide it; we never estimate.

**Can I get reviews or sales numbers?** No. Shopify does not publish them in the product feed.

**Why are some prices in EUR/GBP?** Prices are in the store's currency (`currency`).

### 🤖 For AI agents & LLM apps

`architekture/shopify-products-scraper` returns every product of any Shopify store — title, vendor, tags, and every variant's price, compare-at price, SKU and availability, optionally barcodes and stock counts — as deterministic, schema-stable JSON, one object per product, same fields on every run, so agents and RAG pipelines can parse it without defensive code. Invoke it from any agentic workflow via the Apify API or the Apify MCP server (`mcp.apify.com`) as a tool call; pay-per-event billing means a run costs a known amount before it starts ($0.50 per 1,000 products; `maxProductsPerStore` caps it). Every store gets a machine-readable status (`OK`, `NOT_SHOPIFY`, `BLOCKED`, `UNREACHABLE`, `PARTIAL`) in `RUN_SUMMARY`, so an agent can branch on it instead of parsing logs. Honest limits: Shopify storefronts only, no reviews, stock counts only where the store publishes them.

Works out of the box with LangChain, LlamaIndex and AI coding agents — call it as a tool from Claude Code, Cursor, or any MCP client via `mcp.apify.com`.

### 🧰 More Architekture scrapers

- 🔎 **E-commerce:** Shopify Products (you are here) · [AliExpress Search](https://apify.com/architekture/aliexpress-search-scraper)
- **Grocery:** [Woolworths AU](https://apify.com/architekture/woolworths-au-product-scraper) · [Coles AU](https://apify.com/architekture/coles-au-product-scraper) · [ALDI AU](https://apify.com/architekture/aldi-au-product-scraper) · [Pak'nSave NZ](https://apify.com/architekture/paknsave-nz-scraper) · [Tesco UK](https://apify.com/architekture/tesco-uk-scraper) · [Sainsbury's UK](https://apify.com/architekture/sainsburys-uk-scraper)

### 🙋 Support

Open an Issue on this actor's page; a human replies within a day.

### ⚖️ Legal compliance

This actor reads publicly available storefront product data, without logging in. You are responsible for how you use the data, including each store's terms of service, intellectual property in product content and images, and data-protection law where it applies.

### 📝 Changelog

- 🆓 **2026-10-18** — 1.0: probe-first store detection with free status rows; one row per product with variants nested; sale detection; optional barcodes and stock counts; collections; automatic www/bare-host retry; 429 retry on fresh IPs; clean stops at spending limit and timeout; resumable after platform migration.

# Actor input Schema

## `storeUrls` (type: `array`):

Store domains or URLs, e.g. allbirds.com or https://www.gymshark.com. A collection URL (https://store.com/collections/sale) scrapes just that collection. Each store is checked first; stores that are not Shopify (or block access) are reported, never charged. Up to 200 per run.

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

Collection handles to scrape on every store instead of the whole catalog, e.g. sale or mens-shoes (the part after /collections/ in the URL).

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

Stop each store after this many products (one product = one row with all its variants). 0 = whole catalog (Shopify serves at most 25,000).

## `includeProductDetails` (type: `boolean`):

Also fetch each product's storefront detail: per-variant barcode (GTIN/UPC), inventory quantity and inventory policy where the store exposes them, selling plans. One extra request per product; enriched rows are charged as product-detail.

## `proxyConfiguration` (type: `object`):

Apify datacenter proxy is the tested default. On HTTP 429 the actor retries on a fresh IP.

## `feedback` (type: `string`):

Tell the maintainers what you needed and did not get. Read by a human; does not change the output.

## Actor input object example

```json
{
  "storeUrls": [
    "allbirds.com",
    "deathwishcoffee.com"
  ],
  "maxProductsPerStore": 100,
  "includeProductDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

One row per product (variants nested); stores that could not be scraped add one recordType: store row.

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

RUN_SUMMARY: status per store (OK, NOT_SHOPIFY, BLOCKED, UNREACHABLE, PARTIAL) and charges.

# 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 = {
    "storeUrls": [
        "allbirds.com",
        "deathwishcoffee.com"
    ],
    "maxProductsPerStore": 100,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("architekture/shopify-products-scraper").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 = {
    "storeUrls": [
        "allbirds.com",
        "deathwishcoffee.com",
    ],
    "maxProductsPerStore": 100,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("architekture/shopify-products-scraper").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 '{
  "storeUrls": [
    "allbirds.com",
    "deathwishcoffee.com"
  ],
  "maxProductsPerStore": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call architekture/shopify-products-scraper --silent --output-dataset

```

## MCP server setup

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

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/PVLjT7haghyHnAQa1/builds/IU4KIyjB5myZdbQQN/openapi.json
