# Shopify Store Scraper · Products, Prices & Stock From Any Store (`reapx/shopify-store-products-scraper`) Actor

Shopify store products scraper to extract full product catalogs, prices, variants, discounts and stock from any Shopify store via public /products.json. Scrape product titles, SKUs, vendors, images, pricing and inventory. Ideal for dropshipping research, price monitoring and e-commerce intelligence.

- **URL**: https://apify.com/reapx/shopify-store-products-scraper.md
- **Developed by:** [Tarek Etman](https://apify.com/reapx) (community)
- **Categories:** E-commerce, Lead generation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.30 / 1,000 product returneds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

![reapX — public sources in, addressable records out](https://reapx.dev/reapx.gif)

## Shopify Store Products Scraper · Products, Prices & Stock From Any Store

A **Shopify store products scraper** and **Shopify store scraper** to extract any Shopify store's full product catalog — every product, price, discount, variant, SKU and stock state — straight from the storefront's public `/products.json`. Paste any Shopify store domain or collection URL to get structured product data. No login, no cookies, no API key, no headless browser, and no store owner permission needed.

> Maintained by **reapX**. Every row cites the Apify run that produced it — nothing is
> inferred, modelled or filled in, and a field absent from the source is absent from the row.
> The extracted archive for this source is browsable at
> [reapx.dev/data/shopify-store-products-scraper/](https://reapx.dev/data/shopify-store-products-scraper/) and mirrored as an open dataset on
> [Hugging Face](https://huggingface.co/datasets/reapxdev/shopify-store-products-scraper) and
> [Kaggle](https://www.kaggle.com/datasets/reapxdev/shopify-store-products-scraper). Questions: reapxdev@proton.me

Perfect for dropshipping product research, e-commerce competitor analysis, price monitoring, market intelligence, and product catalog synchronization.

### What this Shopify store scraper extracts

What this Shopify scraper returns

One row per product, with:

| Field | What it gives you |
| --- | --- |
| `title`, `handle`, `productUrl`, `productId` | The product and a permanent link to it |
| `price`, `maxPrice`, `currency` | Cheapest and dearest variant, in the store's own currency |
| `compareAtPrice`, `onSale`, `discountPercent` | The store's own "was" price and how deep the discount runs |
| `inStock`, `variantsCount`, `availableVariantsCount` | Live inventory: what is buyable right now |
| `vendor`, `productType`, `tags`, `options` | Merchandising and catalog structure |
| `publishedAt`, `daysSincePublished`, `updatedAt` | New-arrival windows and change detection |
| `sku`, `imageUrl`, `imagesCount` | Identifiers and creative |
| `variants[]`, `description` | Optional - per-variant price/SKU/stock, and plain-text copy |

Five named dataset views ship with it: **Products**, **Pricing & discounts**, **Inventory**,
**New arrivals** and **Full catalog**.

### How to scrape a Shopify store

1. Put one or more domains into **Shopify store URLs** - `gymshark.com`, `https://glossier.com`,
   whatever you have. A full URL works just as well as a bare domain.
2. Optionally narrow to **collections** - paste `sale`, `new-arrivals`, or the full
   `https://store.com/collections/sale` URL.
3. Set **Max products per store**. You are charged once per product returned, so this is your price cap.
4. Run it. The default configuration returns 100 real products from a live store in under 5 seconds.

### Filters that decide what you pay for

Every filter runs **before** charging, so products you filter out are never billed:

- **Price band** - minimum and maximum price in the store's currency
- **Discounted products only** and **minimum discount %** - sale and markdown monitoring
- **In-stock only** - drop sold-out products
- **Published in the last N days** - a new-arrivals feed for any store
- **Vendors**, **product types**, **tags**, **title keywords** - cohort and category slicing
- **Sort by** newest, oldest, cheapest, most expensive or biggest discount, then take the top N

### What you can build with it

- **Competitor price tracking** - run daily across a set of rival stores and diff `price` by `productId`.
- **Discount and promo monitoring** - `onSaleOnly` + `discountDesc` surfaces the deepest markdowns in a catalog.
- **Stock-out alerting** - watch `availableVariantsCount` fall to zero on the SKUs you care about.
- **New-product feeds** - `newerThanDays: 7` plus `sortBy: newest` gives you a weekly launch feed.
- **Ecommerce lead generation** - catalog size, price band and vendor mix qualify a store before you pitch it.
- **Market and assortment research** - price distribution and product-type mix across a whole vertical.
- **Product-feed building** - image, title, price and SKU are enough to seed a comparison site or affiliate feed.

### Input configuration

Required: `storeUrls`. Everything else is optional.

#### Stores & collections

Which storefronts to read. Everything here comes from the store's own public product feed - no login, no cookies, no API key.

| Field | Type | Accepts | What it does |
| --- | --- | --- | --- |
| `storeUrls` | list | one value per line | Domains of the Shopify stores to scrape. A bare domain or a full URL both work - https://gymshark.com, gymshark.com and https://gymshark.com/collections/all are all accepted. Prefilled with `["https://gymshark.com"]`. |
| `collectionHandles` | list | one value per line | Limit the scrape to specific collections instead of the whole catalogue. Use the handle from the URL or paste the URL itself - "sale", "new-arrivals" or https://gymshark.com/collections/sale all work. Leave empty to scrape every product in the store. Prefilled with `[]`. |

#### How much to fetch

Volume and ordering. You pay per product returned, so this section decides the price of the run.

| Field | Type | Accepts | What it does |
| --- | --- | --- | --- |
| `maxProductsPerStore` | integer | 1 to 20000 | Hard cap on how many products each store returns. This is also your cost cap: you are charged once per product returned. Prefilled with `100`. |
| `sortBy` | string | one of 6: `none`, `newest`, `oldest`, `priceAsc`, ... | How to pick which products you get when the cap is smaller than the catalogue. "Feed order" is the fastest and stops as soon as the cap is met; every other option scans the catalogue first, so it is slower on large stores. Prefilled with `"none"`. |

#### Filters

All filters are applied before charging - filtered-out products are never billed. Leave a filter empty to ignore it.

| Field | Type | Accepts | What it does |
| --- | --- | --- | --- |
| `minPrice` | integer | 0 to any | Keep only products whose lowest variant price is at least this much, in the store's own currency. |
| `maxPrice` | integer | 0 to any | Keep only products whose lowest variant price is at most this much, in the store's own currency. |
| `onSaleOnly` | true/false | checkbox | Keep only products where a compare-at price is higher than the current price - the store's own definition of "on sale". |
| `minDiscountPercent` | integer | 1 to 99 | Keep only products discounted by at least this percentage off the compare-at price. |
| `inStockOnly` | true/false | checkbox | Keep only products with at least one variant the store currently marks as available to buy. |
| `newerThanDays` | integer | 1 to 3650 | Keep only products published to the storefront within this many days - the new-arrivals window. |
| `vendors` | list | one value per line | Keep only products from these vendors (exact, case-insensitive match on the store's vendor field). |
| `productTypes` | list | one value per line | Keep only products of these types (exact, case-insensitive match on the store's product type field). |
| `requiredTags` | list | one value per line | Keep only products carrying at least one of these storefront tags. |
| `titleKeywords` | list | one value per line | Keep only products whose title contains at least one of these words or phrases (case-insensitive). |

#### Output detail

Extra columns. These change the row size, not the price.

| Field | Type | Accepts | What it does |
| --- | --- | --- | --- |
| `includeVariants` | true/false | checkbox | Add a variants array to each row with per-variant id, SKU, price, compare-at price, availability and option values. Off by default to keep rows small. |
| `includeDescription` | true/false | checkbox | Add the product description as plain text, stripped of HTML and capped at 5,000 characters. |

#### Network

Only touch this if a store blocks the default direct requests.

| Field | Type | Accepts | What it does |
| --- | --- | --- | --- |
| `proxyConfiguration` | object | free text | Optional. Most Shopify storefronts answer fine without a proxy, so this is off by default. Turn it on if a specific store returns 403 or 429 for the datacenter that runs the actor. Prefilled with `{"useApifyProxy": false}`. |

### Pricing

Pay per event. The primary event is **Product returned** (`product-returned`), charged at **$0.0035 per event** on the free plan, falling to $0.0014 on the highest tier.

Charged once per complete product row written to the default dataset. Blocked stores, non-Shopify hosts, empty feeds and products removed by your filters are never charged.

Minimum charge cap per run: $0.0035.

Tiered discounts apply automatically on every paid Apify plan. Platform usage is absorbed by the Actor, so the per-event price is the whole price.

### Usage examples

#### The prefilled run, which is what Start does with nothing changed

```json
{
  "storeUrls": [
    "https://gymshark.com"
  ],
  "maxProductsPerStore": 100,
  "sortBy": "none",
  "onSaleOnly": false,
  "inStockOnly": false,
  "includeVariants": false,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

#### Filter by collections only (optional)

```json
{
  "storeUrls": [
    "https://gymshark.com"
  ],
  "maxProductsPerStore": 100,
  "sortBy": "none",
  "onSaleOnly": false,
  "inStockOnly": false,
  "includeVariants": false,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "collectionHandles": [
    "sale",
    "new-arrivals"
  ]
}
```

#### Filter by sort products by

```json
{
  "storeUrls": [
    "https://gymshark.com"
  ],
  "maxProductsPerStore": 100,
  "sortBy": "discountDesc",
  "onSaleOnly": false,
  "inStockOnly": false,
  "includeVariants": false,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

#### Filter by minimum price

```json
{
  "storeUrls": [
    "https://gymshark.com"
  ],
  "maxProductsPerStore": 100,
  "sortBy": "none",
  "onSaleOnly": false,
  "inStockOnly": false,
  "includeVariants": false,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "minPrice": 25
}
```

#### Filter by maximum price

```json
{
  "storeUrls": [
    "https://gymshark.com"
  ],
  "maxProductsPerStore": 100,
  "sortBy": "none",
  "onSaleOnly": false,
  "inStockOnly": false,
  "includeVariants": false,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "maxPrice": 100
}
```

### Output example

One row, exactly as the actor wrote it to the dataset:

```json
{
  "storeDomain": "gymshark.com",
  "storeName": "Gymshark US",
  "currency": "USD",
  "productId": "6806953033930",
  "title": "Gymshark Everyday Seamless Zip Tracktop - Onyx Grey",
  "handle": "gymshark-everyday-seamless-zip-tracktop-jackets-outerwear-grey-aw26",
  "productUrl": "https://gymshark.com/products/gymshark-everyday-seamless-zip-tracktop-jackets-outerwear-grey-aw26",
  "vendor": "Gymshark | Be a visionary.",
  "productType": "Womens Jackets / Outerwear",
  "tags": [
    "06/08/26",
    "30/7/26-everyday-seamless",
    "activity:Conditioning",
    "akeneo_updated_at:1785528453",
    "all-products",
    "build-your-wishlist",
    "category:Jackets / Outerwear",
    "conditioning",
    "created_by:product-service",
    "division:Apparel",
    "everyday",
    "everyday-seamless",
    "feature: seamless",
    "feature:lightweight",
    "filter-colour:Grey",
    "garmentLength:Midi",
    "get-the-look:B1B7P-GB7N",
    "get-the-look:B4B8H-GB7N",
    "get-the-look:B6B1R-WB57",
    "get-the-look:B7A3L-GB7N",
    "get-the-look:B7C2L-YBZ5",
    "get-the-look:I2C5D-WB57",
    "INTL-06/08/26",
    "lower-bucket",
    "most-loved",
    "must-have",
    "NAM-06/08/26",
    "new-releases",
    "outerwear",
    "outlet",
    "over-40-off",
    "over-50-off",
    "over-60-off",
    "over-70-off",
    "percentageInStockV2",
    "range:Everyday Seamless",
    "seamtype:Seamless",
    "season:AW26",
    "segmentation:Conditioning",
    "sizeguide:top",
    "subcategory:Track Top",
    "test",
    "tier:3",
    "top-rated",
    "tracksuits",
    "trading",
    "winback-codes",
    "Womens",
    "zip-jackets"
  ],
  "price": 42,
  "maxPrice": 42,
  "compareAtPrice": null,
  "onSale": false,
  "discountPercent": null,
  "inStock": true,
  "variantsCount": 7,
  "availableVariantsCount": 7
}
```

That row carries 29 fields in total; 18 are shown.

#### Fields on every row

| Field | Type | What it is |
| --- | --- | --- |
| `storeDomain` | string | Host of the Shopify storefront this product was read from. |
| `storeName` | string | Shop name as published by the storefront. Null when the store hides its meta endpoint. |
| `currency` | string | ISO 4217 currency the storefront prices in. All price fields on the row use it. |
| `collectionHandle` | string | Collection the product was read from. Present only when the run was scoped to collections. |
| `productId` | string | Shopify product ID, unique within the store. Stable across runs. |
| `title` | string | Product name exactly as shown on the storefront. |
| `handle` | string | URL slug of the product within the store. |
| `productUrl` | string | Canonical storefront URL for the product page. |
| `vendor` | string | Brand or vendor the store assigns to the product. |
| `productType` | string | Store's own product type / category label. |
| `tags` | list | Storefront tags on the product. Often carries merchandising signals such as new-releases or sale. |
| `price` | number | Lowest current variant price, in the store's currency. |
| `maxPrice` | number | Highest current variant price. Equal to price when every variant costs the same. |
| `compareAtPrice` | number | Highest compare-at (was) price across variants. Null when the store publishes none. |
| `onSale` | true/false | True when the compare-at price is above the current price. |
| `discountPercent` | number | Percent off the compare-at price. Null when the product is not discounted. |
| `inStock` | true/false | True when at least one variant is currently available to buy. |
| `variantsCount` | integer | Number of variants published for the product. |
| `availableVariantsCount` | integer | How many of those variants the store currently marks as available. |
| `sku` | string | SKU of the first variant that publishes one. Null when the store hides SKUs. |
| `options` | list | Option axes and their values, e.g. Size and Colour. |
| `imageUrl` | string | First product image published by the store. |
| `imagesCount` | integer | Number of images on the product. |
| `publishedAt` | string | UTC ISO 8601 timestamp the product went live on the storefront. |
| `createdAt` | string | UTC ISO 8601 timestamp the product record was created. |
| `updatedAt` | string | UTC ISO 8601 timestamp of the store's last edit to the product. |
| `daysSincePublished` | integer | Whole days between publication and this scrape. Use it to size a new-arrivals window. |
| `description` | string | Plain-text product description, HTML stripped, capped at 5,000 characters. Present only when "Include the product description" is on. |
| `variants` | list | Per-variant id, title, SKU, price, compare-at price, availability, grams and option values. Present only when "Include the full variant list" is on. |
| `sourceUrl` | string | Exact public JSON endpoint this row came from. |
| `scrapedAt` | string | UTC ISO 8601 timestamp of this run. |

5 named dataset views ship with it: **Products**, **Pricing & discounts**, **Inventory**, **New arrivals**, **Full catalog**. They drive the Output tab in Console and the Output block on the Actor's `.md` page.

### FAQ

#### How do I scrape products from a Shopify store?

Put the store domain into **Shopify store URLs** and run. The actor reads
`https://{store}/products.json`, the public product feed Shopify serves on every storefront, and pages
through it until it hits your product cap.

#### Do I need an API key, a login or the store owner's permission?

No. This reads only the public storefront JSON that Shopify publishes for every store - the same data
your browser can fetch. There is no authentication step and no private data involved.

#### How do I know if a site is a Shopify store?

Open `https://thesite.com/products.json` in a browser. If you get JSON with a `products` array, this
actor will scrape it. If you get a 404 or an HTML page, the site is not a public Shopify storefront and
the run reports it and charges you nothing for it.

#### Can I scrape only one collection or category?

Yes. Add the collection handle - `sale`, `new-arrivals`, `mens-tops` - or paste the full
`/collections/...` URL into **Collections only**. Multiple collections per run are supported.

#### Can I scrape several Shopify stores in one run?

Yes. **Shopify store URLs** takes a list, and **Max products per store** applies to each store
independently, so a 10-store run at 200 products each returns up to 2,000 rows.

#### How do I track Shopify price changes over time?

Schedule the actor daily with the same input and compare `price` and `compareAtPrice` on matching
`productId` values between dataset runs. `updatedAt` tells you when the store itself last touched the
product.

#### How do I find products that are on sale?

Turn on **Discounted products only**, optionally set **Minimum discount %**, and sort by
**Biggest discount first**. Rows come back ranked by `discountPercent`.

#### Does it return stock levels?

It returns availability, not counts. Shopify's public feed publishes a per-variant `available` flag but
not exact inventory numbers, so you get `inStock`, `variantsCount` and `availableVariantsCount` - which
is enough to detect sell-outs and size-run gaps. Exact quantities exist only in the private admin API.

#### How fast is it and what does it cost?

The feed returns 250 products per request, so 1,000 products is four HTTP requests. A 100-product
default run finishes in a few seconds and costs $0.35 at the Free-tier rate, less on every paid plan.

#### What happens if a store blocks the request?

The run backs off and retries on 429 and 5xx responses, respecting Shopify's storefront rate limit. If
the store still refuses, that store is reported in the status message and charged at zero. Turn on
**Proxy** if one specific store blocks the datacenter the actor runs in.

#### Can an AI agent call this actor?

Yes. Every input field and every output field carries a title, description and example, so an agent can
fill the input and read the dataset without extra prompting.

***

Unofficial - not affiliated with Shopify. Collects public data only. reapx.
Contact reapxdev@proton.me.

### The full published archive

- **[shopify store products scraper archive](https://reapx.dev/data/shopify-store-products-scraper/)** — every entity this Actor has observed
- **[All reapx datasets](https://reapx.dev/data/)** — 20 sources, tens of thousands of pages
- **[llms.txt](https://reapx.dev/llms.txt)** · **[feed.json](https://reapx.dev/feed.json)** ·
  **[feed.xml](https://reapx.dev/feed.xml)** — the machine-readable index, for agents

Each archive page carries Dataset JSON-LD, a canonical URL and the identifiers used, so an agent
can resolve an entity without running anything. Nothing on those pages is estimated or modelled.

### 🧪 Example input

A real, runnable configuration — this is an actual input this Actor has run with.

```json
{
  "storeUrls": [
    "https://gymshark.com",
    "https://www.taylorstitch.com",
    "https://outdoorvoices.com"
  ],
  "includeVariants": true,
  "maxProductsPerStore": 40,
  "sortBy": "none",
  "onSaleOnly": false,
  "inStockOnly": false,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

### 📄 Sample output

One real row from a real run of this Actor, unedited.

```json
{
  "storeDomain": "gymshark.com",
  "storeName": "Gymshark US",
  "currency": "USD",
  "productId": "6806953033930",
  "title": "Gymshark Everyday Seamless Zip Tracktop - Onyx Grey",
  "handle": "gymshark-everyday-seamless-zip-tracktop-jackets-outerwear-grey-aw26",
  "productUrl": "https://gymshark.com/products/gymshark-everyday-seamless-zip-tracktop-jackets-outerwear-grey-aw26",
  "vendor": "Gymshark | Be a visionary.",
  "productType": "Womens Jackets / Outerwear",
  "tags": [
    "06/08/26",
    "30/7/26-everyday-seamless",
    "activity:Conditioning",
    "akeneo_updated_at:1785528453",
    "all-products",
    "build-your-wishlist",
    "category:Jackets / Outerwear",
    "conditioning",
    "created_by:product-service",
    "division:Apparel",
    "everyday",
    "everyday-seamless",
    "feature: seamless",
    "feature:lightweight",
    "filter-colour:Grey",
    "garmentLength:Midi",
    "get-the-look:B1B7P-GB7N",
    "get-the-look:B4B8H-GB7N",
    "get-the-look:B6B1R-WB57",
    "get-the-look:B7A3L-GB7N",
    "get-the-look:B7C2L-YBZ5",
    "get-the-look:I2C5D-WB57",
    "INTL-06/08/26",
    "lower-bucket",
    "most-loved",
    "must-have",
    "NAM-06/08/26",
    "new-releases",
    "outerwear",
    "outlet",
    "over-40-off",
    "over-50-off",
    "over-60-off",
    "over-70-off",
    "percentageInStockV2",
    "range:Everyday Seamless",
    "seamtype:Seamless",
    "season:AW26",
    "segmentation:Conditioning",
    "sizeguide:top",
    "subcategory:Track Top",
    "test",
    "tier:3",
    "top-rated",
    "tracksuits",
    "trading",
    "winback-codes",
    "Womens",
    "zip-jackets"
  ],
  "price": 42,
  "maxPrice": 42,
  "compareAtPrice": null,
  "onSale": false,
  "discountPercent": null,
  "inStock": true,
  "variantsCount": 7,
  "availableVariantsCount": 7,
  "sku": "B1B7P-GB7N-S",
  "options": [
    {
      "name": "Size",
      "values": [
        "Small",
        "Extra Small",
        "Extra Large",
        "Large",
        "Extra Extra Small",
        "Medium",
        "Extra Extra Large"
      ]
    }
  ],
  "imageUrl": "https://cdn.shopify.com/s/files/1/0156/6146/files/EverydaySeamlessZipTracktopGSOnyxGreyB1B7P_GB7N_4180.jpg?v=1784724702",
  "imagesCount": 6,
  "publishedAt": "2026-07-30T17:50:00Z",
  "createdAt": "2026-05-13T09:46:05Z",
  "updatedAt": "2026-08-02T23:14:07Z",
  "daysSincePublished": 3,
  "sourceUrl": "https://gymshark.com/products.json?limit=250&page=1",
  "scrapedAt": "2026-08-02T23:14:08.011644Z",
  "variants": [
    {
      "variantId": "39799577903306",
      "title": "Small",
      "sku": "B1B7P-GB7N-S",
      "price": 42,
      "compareAtPrice": null,
      "available": true,
      "grams": 0,
      "option1": "Small",
      "option2": null,
      "option3": null
    },
    {
      "variantId": "39799577968842",
      "title": "Extra Small",
      "sku": "B1B7P-GB7N-XS",
      "price": 42,
      "compareAtPrice": null,
      "available": true,
      "grams": 0,
      "option1": "Extra Small",
      "option2": null,
      "option3": null
    },
    {
      "variantId": "39799582097610",
      "title": "Extra Large",
      "sku": "B1B7P-GB7N-XL",
      "price": 42,
      "compareAtPrice": null
```

### How it works

1. You set the filters below, or none at all.
2. The Actor calls the source's own public endpoint and pages through the results.
3. Every row is pushed to the dataset **as it is built**, not buffered to the end — so a
   run that hits its time limit still returns everything it collected up to that point.
4. You are charged per row returned, so the maximum-results field is also your cost cap.

### 💬 Your feedback

Found a bug, or need a field this does not return yet? Open an issue on the Actor's **Issues**
tab, or write to **reapxdev@proton.me**. Bugs get fixed and reasonable field requests get
added.

# Actor input Schema

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

Domains of the Shopify stores to scrape. A bare domain or a full URL both work - https://gymshark.com, gymshark.com and https://gymshark.com/collections/all are all accepted.

## `collectionHandles` (type: `array`):

Limit the scrape to specific collections instead of the whole catalogue. Use the handle from the URL or paste the URL itself - "sale", "new-arrivals" or https://gymshark.com/collections/sale all work. Leave empty to scrape every product in the store.

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

Hard cap on how many products each store returns. This is also your cost cap: you are charged once per product returned.<br><br>The higher the number, the longer the run takes and the more you are charged, because you pay per row returned.

## `sortBy` (type: `string`):

How to pick which products you get when the cap is smaller than the catalogue. "Feed order" is the fastest and stops as soon as the cap is met; every other option scans the catalogue first, so it is slower on large stores.<br><br>Options: <code>none</code>, <code>newest</code>, <code>oldest</code>, <code>priceAsc</code>, <code>priceDesc</code>, <code>discountDesc</code>.

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

Keep only products whose lowest variant price is at least this much, in the store's own currency.

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

Keep only products whose lowest variant price is at most this much, in the store's own currency.<br><br>The higher the number, the longer the run takes and the more you are charged, because you pay per row returned.

## `onSaleOnly` (type: `boolean`):

Keep only products where a compare-at price is higher than the current price - the store's own definition of "on sale".

## `minDiscountPercent` (type: `integer`):

Keep only products discounted by at least this percentage off the compare-at price.

## `inStockOnly` (type: `boolean`):

Keep only products with at least one variant the store currently marks as available to buy.

## `newerThanDays` (type: `integer`):

Keep only products published to the storefront within this many days - the new-arrivals window.<br><br>The higher the number, the longer the run takes and the more you are charged, because you pay per row returned.

## `vendors` (type: `array`):

Keep only products from these vendors (exact, case-insensitive match on the store's vendor field).<br><br>Leave this field empty to include every option.

## `productTypes` (type: `array`):

Keep only products of these types (exact, case-insensitive match on the store's product type field).<br><br>Leave this field empty to include every option.

## `requiredTags` (type: `array`):

Keep only products carrying at least one of these storefront tags.<br><br>Leave this field empty to include every option.

## `titleKeywords` (type: `array`):

Keep only products whose title contains at least one of these words or phrases (case-insensitive).<br><br>Leave this field empty to include every option.

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

Add a variants array to each row with per-variant id, SKU, price, compare-at price, availability and option values. Off by default to keep rows small.

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

Add the product description as plain text, stripped of HTML and capped at 5,000 characters.

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

Optional. Most Shopify storefronts answer fine without a proxy, so this is off by default. Turn it on if a specific store returns 403 or 429 for the datacenter that runs the actor.

## Actor input object example

```json
{
  "storeUrls": [
    "https://gymshark.com"
  ],
  "collectionHandles": [
    "sale",
    "new-arrivals"
  ],
  "maxProductsPerStore": 100,
  "sortBy": "discountDesc",
  "minPrice": 25,
  "maxPrice": 100,
  "onSaleOnly": true,
  "minDiscountPercent": 30,
  "inStockOnly": true,
  "newerThanDays": 30,
  "vendors": [
    "Nike",
    "Adidas"
  ],
  "productTypes": [
    "T-Shirts",
    "Leggings"
  ],
  "requiredTags": [
    "new-releases",
    "bestseller"
  ],
  "titleKeywords": [
    "hoodie",
    "jacket"
  ],
  "includeVariants": true,
  "includeDescription": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One row per product: price, discount, stock and link.

## `pricing` (type: `string`):

Price range, compare-at price and discount depth per product.

## `inventory` (type: `string`):

Variant availability per product - what is buyable right now.

## `newArrivals` (type: `string`):

Recently published products with their age in days.

## `catalog` (type: `string`):

Every field on the row, including collection and source endpoint.

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

Every record this run produced, with all fields and no view applied. Field-level titles, types, descriptions and examples are declared in the dataset schema.

# 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://gymshark.com"
    ],
    "collectionHandles": [],
    "maxProductsPerStore": 100,
    "sortBy": "none",
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("reapx/shopify-store-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": ["https://gymshark.com"],
    "collectionHandles": [],
    "maxProductsPerStore": 100,
    "sortBy": "none",
    "proxyConfiguration": { "useApifyProxy": False },
}

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

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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://gymshark.com"
  ],
  "collectionHandles": [],
  "maxProductsPerStore": 100,
  "sortBy": "none",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call reapx/shopify-store-products-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=reapx/shopify-store-products-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Du5iH9XYIV3CiZj09/builds/W5OTZfsJmnYqvSdRR/openapi.json
