# Shopify Store Scraper & Products API — Prices & Variants (`highbrow_fame/shopify-store-products-api`) Actor

Full product catalogue of any Shopify store from its public products.json: title, vendor, type, tags, variants with price, SKU and stock, images, collections. Unofficial Shopify API.

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

## Pricing

$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 Store Scraper & Products API — Prices & Variants

The full product catalogue of any Shopify store as clean JSON records, straight from the public `products.json` every Shopify storefront serves without a key: title, vendor, product type, tags, dates, price range and currency, every variant with its price, compare-at price, SKU, options and availability, images, description as plain text, product URL. Read the whole store or just the collections you name, list the store's collections, filter by stock, price or vendor — and find out in one record whether a site is a Shopify store at all.

**Try it in 1 minute:**

1. Press **Start** — the prefilled input reads the first 250 products of allbirds.com.
2. Seconds later the **Output** tab shows one record per product with vendor, type, tags, price range in USD, variants with sizes and stock, images and the description.
3. Download JSON, CSV or Excel, or add more **Store URLs**, name **Collections**, raise **Max products per store** and switch the filters on.

Price: **$0.50 per 1,000 products** (one product = one record with all its variants and images). Stores that are not Shopify, blocked stores, empty stores and collections, unknown collections, invalid inputs and the optional collection list are free.

**Use it to:**

- Monitor competitors' catalogues on a schedule: new products, price and compare-at changes, what went out of stock — per variant.
- Build a price-comparison or market-research dataset across dozens of Shopify brands in one run.
- Feed a dropshipping, affiliate or AI product-recommendation pipeline with structured products, options and images.
- Audit your own store or a client's: missing SKUs, products without images, empty descriptions, unpublished items.
- Check in one go which of 500 domains run Shopify and how many products each one lists (`NOT_SHOPIFY` is free).

### How it works

1. **Store URLs** are reduced to their origin (`allbirds.com`, `https://www.allbirds.com/products/x` → `https://www.allbirds.com`). Unless you switch it off, the store's `robots.txt` is read first and a store that disallows `/products.json` gets a free `ROBOTS_DISALLOWED` record (Shopify's default allows it).
2. **Detection**: `<store>/products.json?limit=1` must answer JSON with a `products` array. If it does not, the homepage is checked for the markers every Shopify theme has (`cdn.shopify.com`, `Shopify.theme`, `shopify-section`): no markers → one free `NOT_SHOPIFY` record; markers but no catalogue (HTTP 403/404, a password page, HTML instead of JSON) → one free `FAILED` record that says so.
3. **Currency**: `products.json` carries none, so it is read once per store from `<store>/meta.json` (which also names the store) or the homepage's `Shopify.currency` marker; `null` when the store says neither.
4. **Products**: `<store>/products.json?limit=250&page=n` — or `<store>/collections/<handle>/products.json?…` for each named collection — page after page, with **Delay between pages** in between, until the page comes back short, **Max products per store** is reached, or 200 pages (50,000 products). A product listed in several collections is delivered once, under the first collection it appeared in. Products that fail the **in stock / price / vendor** filters are not delivered and not charged.
5. **Collections** (optional): `<store>/collections.json` → one free record per collection with id, handle, title, product count and dates, status `COLLECTION`.

Requests go directly to the store (no proxy unless a connection drops, or **Proxy from the first request** is on). A store that throttles (HTTP 429) is waited out on the same IP for up to 90 seconds; a store that refuses a request (HTTP 403, a bot check) is never retried from another IP. The record says what happened.

### Input

| Field | What it does |
|---|---|
| `storeUrls` | Stores to read: a domain or any URL on the store, one per line. |
| `collections` | Optional. Collection handles (`mens`) or collection URLs, one per line; a handle applies to every store, a URL to its own store. |
| `maxProducts` | Products to deliver per store after filters, up to 50,000 (default 250 = one page). |
| `includeCollectionsList` | Also list the store's collections as free `COLLECTION` records (default off). |
| `onlyAvailable` | Keep only products with at least one variant in stock. |
| `minPrice`, `maxPrice` | Keep products with at least one variant priced in the range (store currency). |
| `vendor` | Keep only this vendor (case-insensitive). |
| `includeDescription` | Plain-text description, HTML removed, contacts scrubbed, cut at 5,000 characters (default on). |
| `includeDescriptionHtml` | Also the original `body_html` (default off). |
| `maxConcurrency` | Stores read in parallel (default 3); pages within a store are sequential. |
| `delayMs` | Pause between two pages of the same store (default 500 ms). |
| `respectRobots` | Skip stores whose robots.txt disallows `/products.json` (default on). |
| `proxyFromFirstRequest` | Send every request through the proxy from the first try (default off: the first try is direct). |
| `proxyConfiguration` | The proxy for retries after a network error — or for every request with the switch above on. |

### Output

One record per product:

| Field | What it is |
|---|---|
| `productId`, `handle`, `title`, `url` | Identity and product page |
| `vendor`, `productType`, `tags[]` | How the store classifies it |
| `createdAt`, `publishedAt`, `updatedAt` | ISO 8601 dates (`publishedAt` null when unpublished) |
| `priceMin`, `priceMax`, `currency`, `available` | Price range as numbers, currency from the store's meta/homepage, any variant in stock |
| `variants[]` | `{ id, title, sku, price, compareAtPrice, available, options { Color, Size … }, grams, requiresShipping }` |
| `options[]` | `{ name, values[] }` — the product's option names and values |
| `images[]`, `imageCount` | `{ src, alt, width, height }`, featured image first |
| `description`, `descriptionHtml` | Plain text (phones and e-mails removed, max 5,000 chars) and, on request, the original HTML |
| `store`, `collection`, `position` | The store's origin, the collection handle it was read from (or null), 1-based position in the store's listing |
| `input`, `status`, `error`, `scrapedAt` | What you gave, `OK` / `NOT_SHOPIFY` / `FAILED` / `NO_RESULTS` / `NOT_FOUND` / `ROBOTS_DISALLOWED` / `INVALID_INPUT` / `COLLECTION`, the reason, and when |

A `COLLECTION` record (with **Include the collection list** on) has `collection`, `collectionId`, `title`, `productsCount`, `publishedAt`, `updatedAt`, `url`.

#### Example

```json
{
  "input": "https://www.allbirds.com",
  "store": "https://www.allbirds.com",
  "productId": "7340901859408",
  "handle": "womens-tree-runner-natural-white",
  "title": "Women's Tree Runner - Natural White",
  "vendor": "Allbirds",
  "productType": "Shoes",
  "tags": ["womens", "tree", "runner"],
  "createdAt": "2026-03-27T21:03:50.000Z",
  "publishedAt": "2026-09-25T23:58:13.000Z",
  "updatedAt": "2026-10-19T18:19:16.000Z",
  "url": "https://www.allbirds.com/products/womens-tree-runner-natural-white",
  "priceMin": 98,
  "priceMax": 98,
  "currency": "USD",
  "available": true,
  "variants": [
    { "id": "42146889039952", "title": "7", "sku": "TR1W070", "price": 98, "compareAtPrice": null, "available": true, "options": { "Size": "7" }, "grams": 455, "requiresShipping": true },
    { "id": "42146889072720", "title": "8", "sku": "TR1W080", "price": 98, "compareAtPrice": null, "available": false, "options": { "Size": "8" }, "grams": 460, "requiresShipping": true }
  ],
  "options": [{ "name": "Size", "values": ["7", "8"] }],
  "images": [
    { "src": "https://cdn.shopify.com/s/files/1/1104/4168/files/TR1W_PDP_LEFT.png?v=1774646345", "alt": null, "width": 4000, "height": 4000 }
  ],
  "imageCount": 1,
  "description": "Light, breathable and machine-washable. Made with eucalyptus tree fiber.",
  "descriptionHtml": null,
  "collection": null,
  "position": 1,
  "status": "OK",
  "error": null,
  "scrapedAt": "2026-10-19T12:00:00.000Z"
}
```

### FAQ

**Is this an official API?** No, it's an unofficial API built on public pages and endpoints — the `products.json`, `collections.json` and `meta.json` files every Shopify storefront serves to any visitor, with no key and no login. The data is what the store itself publishes; this Actor only shapes it into stable JSON records.

**Does it work on WooCommerce, Magento or other platforms?** No — this version is Shopify only. A site that is not a Shopify store gets one free `NOT_SHOPIFY` record, which makes the Actor a cheap way to check which of your domains run Shopify.

**Why is a store FAILED?** Some stores disable `/products.json`, sit behind a Cloudflare bot check, or are password-protected (pre-launch). The record says which. A store that throttles direct requests (HTTP 429) is waited out for up to 90 seconds on the same IP; if it keeps refusing, run again with **Proxy from the first request** on, ideally with a residential proxy group. A refused request is never retried from another IP mid-run: there is no bypass, by design.

**Where do the currency and inventory counts come from?** `products.json` has neither. The currency is read once per store from `meta.json` or the homepage marker (most stores have one; otherwise `null`). Stock is the per-variant `available` flag — exact inventory quantities and barcodes are not public.

**How are collections handled?** Name handles or collection URLs and only those are read; each product carries `collection`. A product in several of your collections is delivered once, under the first. With **Include the collection list** on, every collection of the store is one free record with its product count, so you can pick handles for the next run.

**Like it?** A short review on the Store page helps other people find this Actor. Something missing or broken? Tell us on the Issues tab — we read every one.

### Related Actors

- [Website Tech Stack Detector](https://apify.com/highbrow_fame/website-tech-stack) — CMS, frameworks and analytics of any site
- [Sitemap Extractor](https://apify.com/highbrow_fame/sitemap-url-extractor) — every URL, date and image from a site's sitemaps
- [Web Fetch API](https://apify.com/highbrow_fame/web-fetch-api) — any URL as HTML, text, Markdown or JSON
- [Website Screenshot API](https://apify.com/highbrow_fame/website-screenshot-api) — full page and mobile screenshots
- [Article Extractor API](https://apify.com/highbrow_fame/article-extractor-api) — title, author, date and full text of any article

# Actor input Schema

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

Shopify stores to read, one per line: a domain (allbirds.com) or any URL on the store — it is reduced to the store's origin. The whole catalogue is read, 250 products per page, unless Collections narrows it.

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

Optional. Collection handles (mens, sale) or collection URLs (https://store.com/collections/sale), one per line. A handle applies to every store above; a URL to its own store (added when it is not listed). Then only those collections are read and each product carries its collection.

## `maxProducts` (type: `integer`):

Products to deliver per store, after the filters, up to 50,000. Pages of 250 are read until that many are collected or the catalogue ends.

## `includeCollectionsList` (type: `boolean`):

Also list the store's collections — id, handle, title, product count, dates — as free records with status COLLECTION, one per collection (from /collections.json).

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

Keep only products with at least one variant available.

## `minPrice` (type: `integer`):

Keep products with at least one variant priced at or above this, in the store's currency.

## `maxPrice` (type: `integer`):

Keep products with at least one variant priced at or below this, in the store's currency.

## `vendor` (type: `string`):

Keep only products of this vendor (brand): exact match, case-insensitive.

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

The product description as plain text: HTML removed, e-mail addresses and phone numbers scrubbed, cut at 5,000 characters.

## `includeDescriptionHtml` (type: `boolean`):

Also the original body_html of the product (e-mail addresses and phone numbers scrubbed).

## `maxConcurrency` (type: `integer`):

Stores read at the same time. Pages within one store are always read one after another, with the delay below between them.

## `delayMs` (type: `integer`):

Pause between two pages of the same store. 500 ms keeps a run under most stores' rate limits.

## `respectRobots` (type: `boolean`):

Skip a store whose robots.txt disallows /products.json for crawlers (free ROBOTS_DISALLOWED record). Shopify's default robots.txt allows it.

## `proxyFromFirstRequest` (type: `boolean`):

Send every request through the proxy from the first try instead of trying directly first. Switch on — ideally with a residential proxy group below — when stores answer HTTP 429 or 403 to direct requests. A refused request is still never retried from another IP.

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

Which proxy to use. By default the first try of every request goes out without a proxy and the proxy serves only retries after a network error; with Proxy from the first request on, every request goes through it. A store that refuses a request (HTTP 403, bot check) is never retried on another IP.

## Actor input object example

```json
{
  "storeUrls": [
    "https://www.allbirds.com"
  ],
  "collections": [],
  "maxProducts": 250,
  "includeCollectionsList": false,
  "onlyAvailable": false,
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "maxConcurrency": 3,
  "delayMs": 500,
  "respectRobots": true,
  "proxyFromFirstRequest": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Every result as a JSON record, with a status for each input.

## `resultsCsv` (type: `string`):

The same records as CSV, for a spreadsheet.

# 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": [
        "https://www.allbirds.com"
    ],
    "collections": [],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("highbrow_fame/shopify-store-products-api").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": ["https://www.allbirds.com"],
    "collections": [],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("highbrow_fame/shopify-store-products-api").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": [
    "https://www.allbirds.com"
  ],
  "collections": [],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call highbrow_fame/shopify-store-products-api --silent --output-dataset

```

## MCP server setup

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

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/0zwI6vbvN60NhXa3a/builds/UhLUcEhOxMsx6wfBC/openapi.json
