# Shopify Product Scraper & Shopify Price Monitor (`tidytools/shopify-store-products-scraper`) Actor

Every product of any Shopify store from its public products.json: variants, SKUs, prices, stock, images. Shopify price monitor with price-drop and back-in-stock alerts. $1/1k products, no start fee.

- **URL**: https://apify.com/tidytools/shopify-store-products-scraper.md
- **Developed by:** [Yukai Lin](https://apify.com/tidytools) (community)
- **Categories:** E-commerce, Marketing, 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.
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

### What does Shopify Product Scraper & Price Monitor do?

It returns **every product of any Shopify store** from the store's own **public product feed** (`/products.json`), with all variants, SKUs, prices, compare-at prices, stock status and images. Paste store URLs or domains, collection URLs or product URLs; get one clean row per product (or per variant). It can also tell you **whether a site is a Shopify store** and watch stores for **price and stock changes**.

- 🛍️ **The store's own JSON, not page scraping**: `/products.json`, `/collections/<handle>/products.json`, `/collections.json` and `/meta.json` are public endpoints every Shopify storefront serves. No login, no browser, no bot-check bypassing
- 🔗 **Any input**: `https://www.allbirds.com`, `kith.com`, `https://kith.com/collections/mens-footwear`, `https://store.com/products/handle`, `name.myshopify.com`, market paths like `/en-gb`
- 🧾 **Clean product rows**: `title`, `handle`, `url`, `vendor`, `productType`, `tags`, `price` (lowest variant), `priceMax`, `compareAtPrice`, `onSale`, `discountPercent`, `currency`, `available`, `variantsAvailable`/`variantsCount`, `skus`, `options`, `images`, `description` (plain text), `publishedAt`, `createdAt`, `updatedAt`, and `variants` (SKU, price, compare-at, availability, options, weight)
- 📊 **One row per product or per variant** (size/colour rows for spreadsheets). You pay once per product either way
- 💱 **Currency included**: products.json has no currency field; the Actor reads it from the store page (the currency the store serves, which follows Shopify Markets) and from `meta.json` (the store's default currency)
- 📉 **Price and stock monitoring**: only new and changed products since the last run, with `changes` (`price_drop`, `price_increase`, `back_in_stock`, `out_of_stock`, `compare_at_price`, `variant_added`, `variant_removed`), previous values per variant, and free rows for removed products
- 🔎 **"Is it a Shopify store?"**: one row per site with store name, myshopify domain, currency, country, product and collection counts; sites that are not Shopify stores are free
- 🎯 **Filters before charging**: keywords, excluded words, vendors, product types, tags, price range, in stock only, on sale only, new since a date, chosen collections
- 🤖 **Polite by design**: robots.txt is checked before reading the catalog (a disallowed products.json is skipped), one request per second per store by default, rate limits respected
- 💵 **$1 per 1,000 products** (all variants included), $0.30 per 1,000 collections, $1 per 1,000 Shopify stores detected. No start fee

### Shopify price monitor: daily price and stock alerts

Watch competitor stores for price drops, price increases, discounts and stock changes in three steps:

1. Paste the stores, turn on **Only new and changed products** (`onlyChanges`) and give the monitor a name (`monitorName`).
2. Run it once: this first run returns the products and saves them as the baseline.
3. Create a daily schedule in Apify Console (Schedules). Each later run returns only new and changed products with their `changes` and previous values, so you pay only for what changed. Add a Slack, email or webhook integration to get the changes as alerts.

```json
{
    "stores": ["https://www.allbirds.com"],
    "onlyChanges": true,
    "monitorName": "allbirds-daily",
    "onlyOnSale": false
}
```

**Price monitor or one-off scrape?** For a full product export (a catalog, SKUs, a competitor's whole range), leave **Only new and changed products** off. For daily or weekly competitor price tracking, turn it on: unchanged products are free, removed products get a free row, and the monitoring guard stops a store from being charged when most of its catalog changes at once.

### Where the data comes from, and the limits

| Data | Store endpoint | Limit |
|---|---|---|
| Products and variants | `/products.json?limit=250&page=N` or `/collections/<handle>/products.json` | 250 products per request; Shopify serves at most **100 pages (25,000 products)** per catalog or collection |
| One product | `/products/<handle>.json` | — |
| Collections | `/collections.json` | 250 per request |
| Store facts | `/meta.json` and the home page | Name, myshopify domain, default currency, country, product and collection counts |

What this means in practice:

- **Some stores turn the feed off.** Headless storefronts (Hydrogen, e.g. fashionnova.com in our tests) and a few stores that block data-center networks answer 404 or 403. They get a free error row; store detection still recognises them as Shopify.
- **Prices are in the currency the store serves to the requester.** Stores using Shopify Markets convert prices by visitor country. Runs on Apify come from the US, so such stores usually answer in USD; `currency` says which currency the numbers are in, and `storeCurrency` shows the store's default currency when it differs.
- **Inventory quantities are not public.** products.json says whether each variant is available, not how many units are left.
- **Order**: products come in the store's own order. `Max products per store` stops early on big catalogs; the run says so in `SUMMARY.perStore[].note`. When a catalog is cut by Shopify's own 25,000-product limit, the note says so, `catalogCapReached` is `true` and the log suggests reading the rest with **Only these collections**.

### Who is it for?

- **E-commerce brands and agencies**: track competitors' catalogs, prices, discounts and new arrivals
- **Price monitoring**: daily alerts for price drops, sales and back-in-stock variants
- **Dropshippers and resellers**: export full catalogs with SKUs, variants and images to spreadsheets
- **Market research and lead lists**: check which sites run on Shopify and how large their catalogs are
- **AI agents and analysts**: structured product data for comparisons and summaries

### How much does it cost?

Price per 1,000 events by Apify plan:

| Event | Free | Starter | Scale | Business and higher |
|---|---|---|---|---|
| Product (with all its variants) | **$1.00** | **$1.00** | **$0.90** | **$0.80** |
| Collection (collections mode) | $0.30 | $0.30 | $0.27 | $0.24 |
| Shopify store detected (detection mode) | $1.00 | $1.00 | $0.90 | $0.80 |

**No start fee.** Not charged: products removed by your filters, unchanged products in monitoring mode, removed-product rows, suspicious change sets in monitoring mode (see the monitoring guard below), sites that are not Shopify stores, password-protected stores, stores without a public catalog, catalogs disallowed by robots.txt, and invalid input lines.

For comparison (checked September 2026, Apify Store, Free plan prices): trovevault/shopify-products-scraper charges $0.001 per product plus $0.001 per run; autofacts/shopify $0.0018 per product and $0.0008 per collection; webdatalabs/shopify-product-scraper $0.01 per product; clearpath/shop-by-shopify-product-scraper $0.00249 per product; thirdwatch/shopify-store-scraper $0.002 per product; makework36/shopify-products-scraper $0.001 per product; ghostgrid/shopify-store-scraper $0.0008 per product plus $0.0012 per change and $0.004 per run. A few are cheaper per row (sleek_waveform/universal-shopify-scraper $0.0005) without monitoring or store detection.

### Control your cost

- **What is charged:** each product returned (once, whatever the number of variants or variant rows), each collection in collections mode, each Shopify store in detection mode.
- **Max products per store** (default 1,000) and **Max products** for the whole run (default 10,000: at most $8 on the Free plan) cap the charge.
- **Filters are applied before charging**, so "on sale, under $50, in stock" costs only for those products.
- **Only new and changed products** makes daily monitoring cheap: after the first run you pay only for products that changed.
- At the start, the run logs its worst case (e.g. `Plan: 2100 products × $0.001 = at most $2.10`) and warns when that is more than your **maximum charge per run**.
- When the maximum charge per run is reached, the run stops, keeps everything found so far, and says so. The `SUMMARY` record then has `status: "LIMIT_REACHED"` and `notProcessed` (up to 100 stores not processed).

**Worked example (real run, 30 September 2026):** allbirds.com, gymshark.com, a Kith collection, colourpop.com, one Tentree product URL, fashionnova.com (headless, no feed) and example.com (not Shopify), 300 products per store: 1,201 products from 5 stores in 12.6 seconds, 2 free error rows. Cost on the Free plan: 1,201 × $0.001 = **$1.20**. The same stores with **Only new and changed products** on the next day cost only for the products whose price or stock changed.

### How to use it

1. Enter your **stores, collections or products**, one per line.
2. Choose **What to return**: products, "Is it a Shopify store?", or collections.
3. Optional: **One row per** variant, **Max products per store**, filters, and **Only new and changed products** (then schedule the Actor daily).
4. Click **Start**. The **Products**, **Price and stock changes**, **Store detection** and **Collections** tables are in the Output tab; export as CSV, Excel or JSON, or call the Actor from the API.

#### Input example

```json
{
    "stores": ["https://www.allbirds.com", "gymshark.com", "https://kith.com/collections/mens-footwear"],
    "maxProductsPerStore": 300,
    "onlyOnSale": true
}
```

Daily price and stock monitor:

```json
{ "stores": ["https://www.tentree.com", "https://colourpop.com"], "onlyChanges": true, "monitorName": "competitors" }
```

Which of these sites are Shopify stores?

```json
{ "stores": ["allbirds.com", "gymshark.com", "kith.com", "example.com", "wikipedia.org"], "mode": "detect" }
```

#### Output example: product

```json
{
    "type": "product",
    "input": "https://www.allbirds.com",
    "store": "https://www.allbirds.com",
    "storeName": "Allbirds",
    "productId": 7340901859408,
    "title": "Women's Allbirds Flip Flop - Dusty Pink",
    "handle": "womens-allbirds-flip-flop-dusty-pink",
    "url": "https://www.allbirds.com/products/womens-allbirds-flip-flop-dusty-pink",
    "vendor": "Allbirds",
    "productType": "Shoes",
    "price": 25,
    "priceMax": 25,
    "compareAtPrice": 50,
    "onSale": true,
    "discountPercent": 50,
    "currency": "USD",
    "currencySource": "store page",
    "available": true,
    "variantsCount": 7,
    "variantsAvailable": 1,
    "skus": ["A12513W050", "A12513W060", "A12513W070", "A12513W080", "A12513W090", "A12513W100", "A12513W110"],
    "options": [{ "name": "Size", "values": ["5", "6", "7", "8", "9", "10", "11"] }],
    "imageUrl": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345",
    "imagesCount": 5,
    "description": "Sun on your feet. Comfort underneath. Light, easy, and made for warm weather, these flip flops bring everyday comfort to sunny days, beach walks, and everything in between. Just slip them on and go.",
    "publishedAt": "2026-09-25T23:58:13.000Z",
    "updatedAt": "2026-09-30T09:33:49.000Z",
    "variants": [
        { "variantId": 42146889039952, "title": "5", "sku": "A12513W050", "price": 25, "compareAtPrice": 50, "available": false, "option1": "5", "grams": 455 }
    ],
    "charged": true
}
```

#### Output example: price change (monitoring)

```json
{
    "type": "product",
    "title": "Women's Allbirds Flip Flop - Dusty Pink",
    "price": 20,
    "changeType": "changed",
    "changes": ["price_drop", "back_in_stock"],
    "previousPrice": 25,
    "previousAvailable": true,
    "variantChanges": [
        { "variantId": 42146889039952, "title": "5", "change": "price,available", "price": 20, "previousPrice": 25, "available": true, "previousAvailable": false }
    ],
    "charged": true
}
```

(Shape from the unit tests; in the real test runs, a second run of the Tentree monitor two minutes later returned 0 changed products and cost nothing.)

#### Output example: store detection

```json
{ "type": "store", "store": "https://gymshark.com", "isShopify": true, "detectedBy": ["meta.json", "powered-by header", "Shopify theme script", "products.json"], "storeName": "Gymshark US", "myshopifyDomain": "gymsharkusa.myshopify.com", "currency": "USD", "country": "US", "productsCount": 10088, "collectionsCount": 526, "productsJsonAvailable": true, "robotsAllowsProductsJson": true, "charged": true }
```

#### Errors (not charged)

```json
{ "type": "store", "input": "https://www.fashionnova.com", "success": false, "errorType": "not_found", "error": "products.json could not be read: HTTP 404 (not found) (the store has turned off its public product feed, or it is a headless storefront) (not charged)", "charged": false }
```

`errorType` is `invalid_input` (a line that is not a URL or domain), `not_shopify` (the site is not a Shopify store), `password_protected` (the store is behind its storefront password: HTTP 401 or a redirect to /password), `not_found` (no public catalog, collection or product, or the domain does not exist), `robots_disallowed` (robots.txt disallows the endpoint, so it is skipped), `blocked` (the store kept refusing requests), `network`/`timeout`.

The `SUMMARY` record (key-value store) has `status` (`SUCCESS`, `PARTIAL_RESULTS`, `FAILED`, `NO_RESULTS`, `LIMIT_REACHED`), totals and `perStore` (Shopify signals found, currency, products read, matched, returned, unchanged, removed, `changePercent`, and a `note` such as "stopped at Max products per store"). In monitoring mode it also has `storesWithSuspiciousChanges` and `suspiciousStores` (reasons, change percentage, whether the baseline was updated).

### Integrations

- **Schedules**: run daily with **Only new and changed products** for price-drop and back-in-stock alerts.
- **API and webhooks**: start runs and read the dataset from any language; a webhook tells you when a run finishes.
- **Exports**: CSV, Excel, JSON, XML and HTML from the dataset (use **One row per: Variant** for flat spreadsheets).
- **Ready-made views** in the Output tab: **Products (overview)**, **Prices and discounts** (lowest and highest price, compare-at price, discount %, stock), **Variants (SKU list)** (with **One row per: Variant**), **Price and stock changes** (monitoring), **Store detection** and **Collections**.
- **Apify integrations**: send results to Google Sheets, Slack, Zapier, Make, n8n and others from the Integrations tab.

#### Use with AI agents (MCP)

Connect Apify's MCP server (https://mcp.apify.com?tools=tidytools/shopify-store-products-scraper) to Claude, Cursor or any MCP client, then ask e.g. "Which Allbirds shoes are on sale right now, and by how much?"

```json
{ "stores": ["https://www.allbirds.com"], "onlyOnSale": true, "productTypes": ["Shoes"] }
```

Failed items are not charged and carry an `errorType`.

### Tips and limitations

- **Big catalogs**: Shopify serves at most 25,000 products per catalog or collection through products.json; read big stores collection by collection with **Only these collections**.
- **Collection URLs** read only that collection; a store URL reads the whole catalog.
- **Removed products** are reported only when the whole catalog was read and no filter was set, so "not read this time" is never reported as "removed".
- **Only new and changed products** remembers each product's variant prices and availability per monitor name. Use a different monitor name for a different store list.
- **Monitoring guard**: when a store moves (another myshopify.com shop or another domain than in the baseline) or more than **Monitoring guard: suspicious change above (%)** of its products (default 50%, stores with at least 10 products) change in one run, every row is still output with `suspiciousChange: true`, `suspiciousReason` and `charged: false`, and nothing is charged for that store in that run. The previous baseline is kept, so the next run compares with the old data again; once you have checked the store, run once with **Accept suspicious changes as the new baseline** on. A domain change with no product change is not flagged (it costs nothing anyway) and is remembered.
- **Pause between requests** (default 1 second per store) keeps runs polite; lower it only for your own stores.

### Terms and responsible use

- The Actor reads only public storefront endpoints that Shopify serves to every visitor, with no login and no bot-check bypassing. It checks each store's **robots.txt** first and skips endpoints it disallows; Shopify's default robots.txt (September 2026) does not disallow products.json.
- It returns **product data only**: no customer data, no order data, no personal contact details (store contact e-mails and phone numbers are not output).
- Product texts and images belong to the stores. Use the data for price monitoring, research and analysis; do not republish images or descriptions as your own. Respect each store's terms of service.

### FAQ

**Does the Actor collect usage data?** The Actor sends one anonymous usage counter per run (input type, item count range, outcome) to improve the tool; no URLs, content or account data.

**Why does a store return nothing?** It has turned off products.json (common for headless Hydrogen storefronts), blocks data-center networks, or disallows the endpoint in robots.txt. You get a free error row that says which.

**Does it give stock quantities?** No. Shopify's public feed says whether each variant is available, not how many units are left.

**Why is a store's currency not what I expected?** Stores using Shopify Markets show prices in the visitor's currency. `currency` tells you which currency the numbers are in; `storeCurrency` is the store's default.

**Can it detect Shopify stores in a list of domains?** Yes: set **What to return** to "Is it a Shopify store?". Only Shopify stores are charged.

**Can I run it on a schedule?** Yes. Turn on **Only new and changed products**, set a monitor name, and create a daily schedule in Apify Console.

Found this useful? A short review on the Store helps other users find it.

### Related Actors

More tools by TidyTools that work well with this one:

- [Website Tech Stack Detector](https://apify.com/tidytools/website-tech-stack-detector): find the CMS, shop platform and tools a site uses
- [Website Change Monitor](https://apify.com/tidytools/website-change-monitor): get an alert when a page, a price or a text changes
- [Company Website Enrichment](https://apify.com/tidytools/company-website-enrichment): add company details to a list of domains
- [Website Contact Extractor](https://apify.com/tidytools/website-contact-extractor): find company emails, phones and social profiles on websites

# Actor input Schema

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

Main input: the run needs at least one line here. One per line, any mix of: a store URL or domain (https://www.allbirds.com, kith.com), a collection URL (https://kith.com/collections/footwear: only that collection) or a product URL (https://store.com/products/handle). Market paths such as /en-gb are kept. Invalid lines, sites that are not Shopify stores and stores without a public catalog get an error row and are not charged.

## `mode` (type: `string`):

Store detection charges only sites that are Shopify stores; other sites get a free row with isShopify: false. Values: products = Products (with variants, prices, stock, images); detect = Is it a Shopify store? (one row per site: store name, currency, country, product count); collections = Collections (one row per collection with product count).

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

Products are read up to 250 per request, in the store's own order. Counted before your filters; Shopify serves at most 100 pages (25,000 products) per catalog or collection.

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

Stop after this many products returned in total: 10,000 products cost at most $8 on the Free plan.

## `rowPer` (type: `string`):

Variant rows are handy for spreadsheets. Either way you pay once per product. Values: product = Product (variants as a list inside the row); variant = Variant (size/colour rows with SKU, price and stock; product fields repeated).

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

Collection handles or URLs (e.g. sale, mens-footwear). For store inputs, read these collections instead of the whole catalog; each product row gets its collection.

## `keywords` (type: `array`):

Keep products whose title, tags, type, vendor or handle contain at least one of these (case-insensitive), e.g. runner, merino.

## `excludeKeywords` (type: `array`):

Drop products whose title, tags, type, vendor or handle contain any of these, e.g. gift card.

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

Keep products of these vendors only (exact name, case-insensitive).

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

Keep products of these types only (exact, case-insensitive), e.g. Shoes.

## `tags` (type: `array`):

Keep products with at least one of these tags (exact, case-insensitive).

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

In the store's currency. Keeps products with at least one variant at or above it.

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

In the store's currency. Keeps products with at least one variant at or below it.

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

Drop products where no variant is available.

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

Keep products with a compare-at price above the price (a discount).

## `sinceDate` (type: `string`):

A date (2026-09-01) or a period (7 days, 2 weeks, 3 months), compared with the date chosen below. Useful for new arrivals.

## `sinceField` (type: `string`):

Which product date "Products since" uses.

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

The product description as plain text.

## `includeHtml` (type: `boolean`):

Also return the description HTML exactly as the store has it (descriptionHtml).

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

Price and stock monitoring: remember every product's variant prices, compare-at prices and availability, and return only products that are new or changed since the previous run of the same monitor (with changeType, changes and the previous values). Unchanged products are not output and not charged. The first run returns everything and becomes the baseline.

## `reportRemoved` (type: `boolean`):

With monitoring on: a free row (changeType: removed) for products that disappeared since the last run. Only when the whole catalog was read and no filter is set, so that "not read" is never reported as "removed".

## `monitorName` (type: `string`):

Runs with the same name are compared with each other (letters, digits, dashes). Use different names for different store lists. Default: "default".

## `httpVia` (type: `string`):

Store JSON is read as plain HTTP. Some stores refuse data-center networks; Auto retries those from a second network. No browser, no bot-check bypassing.

## `requestIntervalMs` (type: `integer`):

Polite spacing: by default one request per second per store (a page holds 250 products, so 1,000 products take about 4 seconds). Rate-limited answers (HTTP 429) are retried after the time the store asks for.

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

How many stores are read at the same time (each store is still read one request at a time).

## `storeTimeoutSecs` (type: `integer`):

A store that takes longer gets a timeout error row (not charged) so one slow store cannot hold the run.

## `suspiciousChangePercent` (type: `integer`):

With monitoring on: when more than this share of a store's products are new, changed or removed in one run (stores with at least 10 products), or the store now answers as another shop or on another domain, its rows are still output but marked suspiciousChange and not charged. 0 turns the percentage check off (a store move is still flagged).

## `updateBaselineOnSuspiciousChange` (type: `boolean`):

Off (default): after a suspicious change set the previous baseline is kept, so the next run compares with the old data again and you can check the store first. On: the new data replaces the baseline (the suspicious rows stay free). Turn it on for one run after a store really moved or relaunched.

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

Only used for requests sent from Apify's network. Apify proxy usage is billed to your Apify account. Not needed normally.

## Actor input object example

```json
{
  "stores": [
    "https://www.allbirds.com",
    "https://www.tentree.com"
  ],
  "mode": "products",
  "maxProductsPerStore": 20,
  "maxProducts": 10000,
  "rowPer": "product",
  "onlyAvailable": false,
  "onlyOnSale": false,
  "sinceField": "publishedAt",
  "includeDescription": true,
  "includeHtml": false,
  "onlyChanges": false,
  "reportRemoved": true,
  "httpVia": "auto",
  "requestIntervalMs": 1000,
  "maxConcurrency": 3,
  "storeTimeoutSecs": 1800,
  "suspiciousChangePercent": 50,
  "updateBaselineOnSuspiciousChange": false
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

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

No description

## `prices` (type: `string`):

No description

## `variants` (type: `string`):

No description

## `changes` (type: `string`):

No description

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

No description

## `collections` (type: `string`):

No description

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

No description

# 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": [
        "https://www.allbirds.com",
        "https://www.tentree.com"
    ],
    "maxProductsPerStore": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("tidytools/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 = {
    "stores": [
        "https://www.allbirds.com",
        "https://www.tentree.com",
    ],
    "maxProductsPerStore": 20,
}

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidytools/shopify-store-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/99dLaQ0LJuZveBXc2/builds/7fjY0uOzg1uLK7oUx/openapi.json
