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

Shopify store scraper for any public Shopify storefront, worldwide: products, variant prices, compare-at (sale) prices, SKUs, availability, options, images, tags and vendor, plus optional barcodes and inventory counts. Input: store domains, collection URLs or handles. Pay per result, no login.

- **URL**: https://apify.com/architekture/shopify-products-scraper.md
- **Developed by:** [architek.earth](https://apify.com/architekture) (community)
- **Categories:** E-commerce, Developer tools, AI
- **Features:** Limited permissions
- **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 Scraper — Products, Prices, Variants, Stock

**Recorded run: 10 products from 1 store in 6 seconds** (2026-10-22, build 0.0.223, run `wWOvUfYNA9SIl8RCj`). A bounded canary sample, not a full-catalogue benchmark.

architek.earth — an independent data studio. Pay-per-result scrapers with recorded runs, published canaries and stable schemas, built for people and AI agents.

Shopify store products, prices, variants and stock from any public Shopify storefront: give it store domains or collection URLs and get one row per product with every variant's price, sale price, SKU and availability, plus barcodes and inventory counts where the store publishes them.

| Source | Market | From | Standout fields |
|---|---|---|---|
| Any public Shopify storefront | Global, in each store's currency | $0.50 / 1,000 products | `variants[].price` / `compareAtPrice`, `sku`, `barcode`, `inventoryQuantity` |

### ✅ Why choose this scraper

#### Scrape every product and variant from a Shopify store

One row per product with all variants nested in `variants[]` (`id`, `title`, `sku`, `price`, `compareAtPrice`, `available`, `options`, `grams`), so a shoe in 13 sizes is one charged row, not 13. On 2026-10-22, 20 allbirds.com products carried 243 variants (run `neeg0aBUmW8OwqjKW`).

#### Find products on sale in a Shopify store

`onSale` is `true` when any variant has a `compareAtPrice` above its `price`; `minPrice` and `maxPrice` give the product's price range.

#### Get Shopify barcodes and inventory counts

With `includeProductDetails`, variants add `barcode` (GTIN/UPC), `inventoryQuantity`, `inventoryManagement` and `inventoryPolicy`, and the product adds `totalInventory`. On 2026-10-22, 124 of 124 variants of 10 allbirds.com products came back with both a barcode and a stock count (run `lmFpkWiA0HLjV79Cw`).

#### Check whether a site runs Shopify before paying

Page 1 of each store's product feed is read first. Stores that are not Shopify, are walled or do not answer get a free row with `status` `NOT_SHOPIFY`, `BLOCKED` or `UNREACHABLE` and a `reason`; nothing is charged for them.

#### Scrape one Shopify collection, not the whole catalogue

Paste a collection URL such as `https://store.com/collections/sale` into `storeUrls`, or list handles in `collections`; rows then carry `source.collection`.

### ⚠️ Worth knowing up front

- Shopify storefronts only. Some brands run a custom ("headless") storefront with the product feed switched off (ruggable.com in our 2026-10-18 test); those come back `NOT_SHOPIFY`, free. In that 12-store check, 8 served their product feed.
- Some stores rate-limit or wall automated requests. They come back `BLOCKED` with the reason (for example "rate limited by the store after retries") and are not charged. A few stores serve the feed on only one host (gymshark.com works, www.gymshark.com answers 403); the actor tries the other host automatically.
- Stock counts and barcodes appear only where the store publishes them in its product detail endpoint. Many stores hide quantities; then you still get `available` per variant but no `inventoryQuantity`. A detail request that fails, or a product with neither barcodes nor stock counts, is delivered with listing data and charged as `product`.
- No reviews and no sales numbers: reviews live in third-party apps (Judge.me, Yotpo, Loox), not in Shopify's product feed.
- Shopify serves at most 25,000 products per store through this feed; `RUN_SUMMARY` warns when a store hits that limit. Prices are in the store's own `currency`, as served to the actor's location. Fields a store does not publish are omitted from the row, not set to null.
- Incremental mode needs a `stateKey`: a rerun then delivers and charges only products that are new or changed (title, variants, SKUs, prices, sale prices or availability; Shopify's `updatedAt` is ignored because stores bump it constantly) since the last run under that key. Without it, a scheduled run delivers and charges every product again. A store whose later page fails is marked `PARTIAL` in `RUN_SUMMARY`, keeping the products delivered before it. The run stops cleanly 45 seconds before its timeout (`TIME_LIMIT`) and at your spending limit (`BUDGET_LIMIT`, only when products were left that the limit could not pay for), keeping what it delivered; with a `stateKey`, `continuePreviousRun: true` resumes the collection after a stop or an abort without delivering or charging any product twice.
- Free rows are never charged. Stores that could not be scraped get a `recordType: "store"` row with `status` `NOT_SHOPIFY`, `BLOCKED` or `UNREACHABLE`. Invalid input (`INVALID_INPUT`), a store that lists no products after a retry (`NO_DATA`), the spending limit (`BUDGET_LIMIT`), the timeout (`TIME_LIMIT`) a `stateKey` still held by another live run (`STATE_BUSY`) and saved-state storage that cannot be reached (`STATE_UNAVAILABLE`) get a `recordType: "status"` row with `status` and `message`.
- One run owns a `stateKey` at a time. If two runs start together on the same key, exactly one collects; the other stops at once with a free `STATE_BUSY` row and exits normally. A run that is killed without finishing (for example a forced abort or a crash) keeps its key until its own timeout plus 60 seconds; until then, runs on that key stop with `STATE_BUSY`. Use a different `stateKey` for each schedule that may overlap.

### ⚡ Try the example

This actor has no free preview; the example below costs about $0.01.

Open the actor's input page, switch to JSON, paste this and start the run:

```json
{
  "storeUrls": ["allbirds.com"],
  "maxProductsPerStore": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

Add stores or raise `maxProductsPerStore` (`0` = whole catalogue) for more rows; add `"includeProductDetails": true` for barcodes and stock counts.

### ⚙️ Input

| Parameter | Type | Default | Description |
|---|---|---|---|
| `storeUrls` | array | required (prefill `["allbirds.com", "deathwishcoffee.com"]`) | Store domains or URLs. A collection URL (`https://store.com/collections/sale`) scrapes only that collection. Up to 200 stores per run. |
| `collections` | array | — | Collection handles (the part after `/collections/`) to scrape on every store instead of the whole catalogue. An unknown handle returns no products and a warning in `RUN_SUMMARY`. |
| `maxProductsPerStore` | integer | `1000` (prefill `100`) | Products per store. `0` = whole catalogue, up to Shopify's 25,000. |
| `includeProductDetails` | boolean | `false` | One extra request per product for barcodes, stock counts, inventory policy and selling plans. Enriched rows are charged as `product-detail`. |
| `incrementalMode` | boolean | `false` | Needs `stateKey`. Delivers only products new or changed since the last run under that key; unchanged products are skipped, not charged, and still count toward `maxProductsPerStore`. |
| `stateKey` | string | — | Any name, e.g. `my-stores`. Saves progress and the incremental baseline in your account so a stopped run can be resumed and reruns can skip unchanged products. Use one key per store list. |
| `continuePreviousRun` | boolean | `false` | Resume the unfinished collection saved under `stateKey` (same stores) after a spending limit, timeout or abort. Products already delivered are not delivered or charged again. |
| `proxyConfiguration` | proxy | `{"useApifyProxy": true}` | Apify datacenter proxy is the tested default. An HTTP 429 is retried on a fresh IP. |
| `feedback` | string | — | Optional note for the maintainers. It is logged and read by a human, and it never changes output. |

### 🧱 Stable fields

These top-level fields keep the same name and the same kind of value in every release. Fields are only ever added, never renamed or removed, so an n8n, Make, Zapier or spreadsheet pipeline can bind to them.

| Field | Type | Meaning |
|---|---|---|
| `store` / `storeName` / `currency` | string | Host the products came from, the store's name and its currency (name and currency omitted if the store's `meta.json` does not answer). |
| `id` / `handle` / `title` / `url` | number / string / string / string | Shopify product id, handle, title and product page. |
| `vendor` / `productType` / `tags` | string / string / array, optional | As set by the merchant. |
| `minPrice` / `maxPrice` | number | Lowest and highest variant price. |
| `onSale` | boolean | `true` when any variant has a `compareAtPrice` above its `price`. |
| `available` | boolean | `true` when any variant can be bought. |
| `variantCount` / `variants` | number / array | Each variant: `id`, `title`, `sku`, `price`, `compareAtPrice`, `available`, `options`, `grams`, `requiresShipping`, `image` (each only when published). |
| `variants[].barcode` / `variants[].inventoryQuantity` / `variants[].inventoryManagement` / `variants[].inventoryPolicy` / `variants[].weightGrams` | string / number / string / string / number, optional | With `includeProductDetails` only, where the store publishes them. |
| `totalInventory` | number, optional | Sum of positive variant quantities (details only, when quantities are published). |
| `requiresSellingPlan` / `hasSellingPlans` / `mediaCount` | boolean / boolean / number, optional | Details only: subscription and pre-order flags, number of media items. |
| `options` | array, optional | For example `[{"name": "Size", "values": ["5", "6"]}]`. |
| `image` / `images` / `description` | string / array / string, optional | Main image, all images, description as plain text. |
| `createdAt` / `updatedAt` / `publishedAt` | string | Shopify timestamps. |
| `source` | object | `{"source": "catalog" or "collection", "collection", "page"}`. |
| `status` / `reason` / `httpStatus` | string / string / number | On store rows only: why a store was not scraped. |
| `status` / `message` / `store` | string / string / string, optional | On status rows only: `INVALID_INPUT`, `NO_DATA`, `BUDGET_LIMIT`, `TIME_LIMIT`, `STATE_BUSY` or `STATE_UNAVAILABLE`, a plain-language message, and the store when the row is about one store. |
| `recordType` | string | `"store"` on free store rows, `"status"` on free run status rows; `"product"` on data rows. |

Other fields are extras, not part of the contract. To have one pinned, say so in `feedback`.

### 📦 Output example

Abridged real row from run `lmFpkWiA0HLjV79Cw` on 2026-10-22 (`includeProductDetails: true`, 2 of 7 variants shown); values are a historical snapshot.

```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,
  "maxPrice": 25,
  "onSale": true,
  "available": true,
  "variantCount": 7,
  "variants": [
    {
      "id": 42146889039952,
      "title": "5",
      "sku": "A12513W050",
      "price": 25,
      "compareAtPrice": 50,
      "available": false,
      "options": ["5"],
      "grams": 455,
      "requiresShipping": true,
      "barcode": "196942293089",
      "inventoryQuantity": 0,
      "inventoryManagement": "shopify",
      "inventoryPolicy": "deny",
      "weightGrams": 455
    }
  ],
  "options": [{"name": "Size", "values": ["5", "6", "7", "8", "9", "10", "11"]}],
  "image": "https://cdn.shopify.com/s/files/1/1104/4168/files/A12513_26Q2_Allbirds-Flip-Flop-Dusty-Pink_PDP_LEFT.png?v=1774646345",
  "createdAt": "2026-03-27T14:03:50-07:00",
  "updatedAt": "2026-10-22T02:43:19-07:00",
  "publishedAt": "2026-09-25T16:58:13-07:00",
  "source": {"source": "catalog", "page": 1},
  "totalInventory": 9,
  "requiresSellingPlan": false,
  "hasSellingPlans": false,
  "mediaCount": 5
}
```

A free store row looks like this (run `eKgv0KYfQDx6cj1Mx`, 2026-10-18):

```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}
```

A free run status row (run `WuMPuJqBJNcffoxLz`, 2026-10-22, a store that listed no products after a retry):

```json
{"recordType": "status", "status": "NO_DATA", "store": "drsquatch.com", "message": "the store listed no products (checked twice); nothing was charged"}
```

The run's status message and the `RUN_SUMMARY` record of its key-value store hold the outcome. An incremental rerun where nothing changed (run `tLOXweVtcMasMe4VZ`, 2026-10-22, nothing charged):

```json
{
  "status": "COMPLETE",
  "message": "Done: 0 rows, 0 details, 0 status rows (COMPLETE; 0 products from 1 of 1 store; 30 unchanged since the last run (skipped, not charged).)",
  "products": 0,
  "details": 0,
  "statusRows": 0,
  "statusReasons": {},
  "unchanged": 30,
  "charged": {"product": 0, "product-detail": 0},
  "stores": [{"store": "deathwishcoffee.com", "status": "OK", "delivered": 0, "pages": 1, "storeName": "Death Wish Coffee", "currency": "USD", "publishedProductsCount": 152, "unchanged": 30}],
  "requests": 2
}
```

### 🧠 Advanced usage

#### Monitor competitor Shopify prices daily

Schedule the same `storeUrls` daily and join runs on `store` + `variants[].id`, comparing `price` and `compareAtPrice`. Add `incrementalMode: true` with a `stateKey` and each run charges only products that changed or are new; `maxProductsPerStore` or `collections` bound the watched set.

#### Track Shopify stock-outs

With `includeProductDetails: true`, watch `variants[].inventoryQuantity` and `available` change between runs; `totalInventory` gives the product total where quantities are published.

#### Match Shopify products to other retailers by barcode

Join `variants[].barcode` (details on) with the `barcode` field of other retail datasets, such as our Tesco UK or Woolworths scrapers.

#### Check that a run finished

Read `RUN_SUMMARY.status`: `COMPLETE`, `PARTIAL`, `NO_PRODUCTS`, `BUDGET_LIMIT`, `TIME_LIMIT`, `STATE_SAVE_FAILED`, `ABORTED`, `STATE_BUSY`, `STATE_UNAVAILABLE`, `INVALID_INPUT` or `PRICING_MISMATCH` (an operator fault: nothing is collected or charged and the run ends FAILED); `statusRows`, `statusReasons` and `unchanged` count the free rows and skipped products. `stores[]` lists each store's `status` (`OK`, `NOT_SHOPIFY`, `BLOCKED`, `UNREACHABLE`, `NO_DATA`, `PARTIAL`, `ERROR`, `DUPLICATE`), `reason`, `delivered`, `pages`, `publishedProductsCount` and `warnings`.

### 🔌 Integrations

Results land in a dataset you can export as JSON, CSV or Excel. They can also flow to Google Sheets, Make, Zapier or n8n, or to a webhook when a run finishes. The Apify Scheduler runs it on a timetable. Daily suits price and stock monitoring; weekly suits catalogue snapshots. Copy-paste code in Python, Node, curl and n8n: [architek-earth/examples/shopify-products-scraper](https://github.com/architek-earth/examples/tree/main/shopify-products-scraper).

### 💸 Pricing

Pay per result. Platform and proxy usage are included. Status rows and failed requests are free. After a platform migration the run resumes from its saved progress; with a `stateKey`, `continuePreviousRun: true` resumes a collection stopped by the spending limit, the timeout or an abort. Either way, products already delivered are skipped and not charged again, and with `incrementalMode` unchanged products are never charged.

| Event | Price | What it covers |
|---|---|---|
| `product` | $0.50 / 1,000 products | One delivered product row, all its variants included; also a row kept at listing data when its detail request failed or returned no barcode or stock count. |
| `product-detail` | $2.00 / 1,000 products | One delivered product row that came back with barcodes or stock counts (`includeProductDetails`). |

1,000 products cost $0.50 however many variants they have; 1,000 products with barcodes or stock counts cost $2.00. A row is charged once, as one event only.

### ❓ FAQ

**Is there a Shopify API for product data from any store?** This actor is one: it reads each store's public storefront product feed and returns the products as JSON through the Apify API, callable from any language or from n8n, Make and Zapier. It does not use the Shopify Admin API.

**Do I need a Shopify account, API key or store owner access?** No. No account, login or API key; it reads what any visitor's browser can read.

**Can I get stock levels and inventory counts from a Shopify store?** Yes, where the store publishes them: `inventoryQuantity` per variant with `includeProductDetails: true`. Many stores hide quantities; the product-level `available` flag is always there. The actor never estimates stock.

**Can I get barcodes (GTIN, UPC, EAN) for Shopify products?** Yes, `variants[].barcode` with `includeProductDetails: true`, where the store publishes one.

**Do I pay per variant?** No. You pay per product; a product with 30 variants is one `product` event.

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

### 🤖 For AI agents & LLM apps

Paste this into your AI assistant:

`architekture/shopify-products-scraper` on Apify. Python: `from apify_client import ApifyClient; client = ApifyClient("TOKEN"); run = client.actor("architekture/shopify-products-scraper").call(run_input={"storeUrls": ["allbirds.com"], "maxProductsPerStore": 20, "proxyConfiguration": {"useApifyProxy": True}}); items = client.dataset(run["defaultDatasetId"]).list_items().items` (page with `offset`/`limit` for large exports). Rows with `recordType == "store"` or `"status"` are free and explain why a store was not scraped or why the run stopped; for scheduled monitoring pass `"incrementalMode": true, "stateKey": "<name>"` to get and pay for only new or changed products; the run outcome is in the `RUN_SUMMARY` record of the run's key-value store. Full input schema and README: `GET https://api.apify.com/v2/acts/architekture~shopify-products-scraper/builds/default`. Token: https://console.apify.com/settings/integrations. The actor is also reachable through the Apify MCP server (`mcp.apify.com`). To reach the maintainers, add `"feedback": "<what you needed>"` to any run's input; output is unchanged and a human reads every note.

### 🧰 More from architek.earth

**E-commerce and marketplaces**

- Shopify Scraper — *you are here*
- [AliExpress Scraper](https://apify.com/architekture/aliexpress-search-scraper) — prices, orders, ratings by keyword
- [TikTok Shop Scraper](https://apify.com/architekture/tiktok-shop-products-scraper) — prices, sellers, variants

Full catalogue: [github.com/architek-earth](https://github.com/architek-earth).

architek.earth · code like concrete

### 🙋 Support & legal

Questions, or a field you need that is not here? Open an issue on the actor's Issues tab or email north@architek.earth. A human replies within a day.

This actor collects only publicly available data from public Shopify storefronts (each store's `/products.json`, `/meta.json` and, with details on, `/products/<handle>.js`), the same data any visitor's browser loads without logging in. It does not bypass authentication: password-protected stores are reported `BLOCKED`. It is not affiliated with or endorsed by Shopify or any store. You are responsible for how you use the output, including each store's terms, copyright in product content and images, and privacy law such as GDPR and CCPA where they apply.

### 📝 Changelog

- **2026-10-25** — \[internal] Studio core 1.0.5 (from 1.0.1). This actor keeps its own block handling, so nothing changes for users: a 403 on `www.` still falls back to the bare domain (and the reverse), a 429 still moves to a new proxy session for up to four tries before a free `BLOCKED` store row, and input, output and prices are unchanged.
- **2026-10-22** — 1.1.3: the status message carries the outcome inside the parentheses, `Done: N rows, M details, K status rows (STATUS; outcome)`; a pricing mismatch on the platform now ends the run FAILED (`PRICING_MISMATCH`, nothing collected or charged) instead of finishing as `NOT_CONFIGURED`.
- **2026-10-22** — 1.1.2: a run whose last product uses your spending limit exactly now ends `COMPLETE` with no status row; `BUDGET_LIMIT` is reported only when products were left that the limit could not pay for.
- **2026-10-22** — 1.1.1: one run per `stateKey`: runs that start together on the same key resolve to exactly one owner, the other gets a free `STATE_BUSY` row; a killed run releases its key at its timeout plus 60 seconds. Saved progress and incremental baselines carry over.
- **2026-10-22** — 1.1: free `recordType: "status"` rows for invalid input, empty stores, the spending limit and the timeout; `incrementalMode` and `stateKey` (reruns charge only new or changed products); `continuePreviousRun` resumes a stopped or aborted collection without charging twice; status message `Done: N rows, M details, K status rows (STATUS)`.
- **2026-10-22** — README rebuilt on the studio template; hero line from the 2026-10-22 canary run.
- **2026-10-18** — 1.0: store check before any charge with free status rows; one row per product with variants nested; sale detection; optional barcodes and stock counts; collections; automatic www and bare-host retry; HTTP 429 retried on fresh IPs; clean stops at the spending limit and timeout; resumable after a 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.

## `incrementalMode` (type: `boolean`):

Requires stateKey. Delivers only products that are new to this stateKey, or whose title, variants, SKUs, prices, sale prices or availability changed since they were last delivered. Unchanged products are skipped and not charged; they still count toward maxProductsPerStore.

## `stateKey` (type: `string`):

Any name (e.g. my-stores). Saves progress and the incremental baseline in your account under this name, so a stopped or aborted collection can be resumed and reruns can skip unchanged products. Use a different key for different store lists.

## `continuePreviousRun` (type: `boolean`):

Resume an unfinished collection saved under stateKey (after a spending limit, timeout or abort) with the same stores. Products already delivered are not delivered or charged again.

## `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,
  "incrementalMode": false,
  "stateKey": "",
  "continuePreviousRun": 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/tVCzKZTy761JjPwOt/openapi.json
