# Shopify Store Catalog & Pricing Extractor (`nimbryxo-studio/shopify-store-catalog-and-pricing-extractor`) Actor

Extract a Shopify store's product catalog with titles, prices, compare-at prices, variants, SKUs, images and stock availability from any custom or myshopify.com domain.

- **URL**: https://apify.com/nimbryxo-studio/shopify-store-catalog-and-pricing-extractor.md
- **Developed by:** [Nimbryxo-studio](https://apify.com/nimbryxo-studio) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

## Shopify Store Catalog & Pricing Extractor

Turn any public Shopify storefront into a structured product feed. Point the actor at one store or a list of stores, choose a mode, and get prices, compare-at prices, variants, stock status, images, options, collection metadata and optional barcode/weight/inventory detail.

No Shopify account, admin API key, cookie or login is required. The actor reads the same public storefront JSON that a shopper's browser receives. Proxy setup is also optional: requests go direct first, and the actor switches to Apify's datacenter proxy automatically only after a real 403 or 429 block.

### Modes

| Mode | What it does | Source endpoint |
|---|---|---|
| `catalog` | Full published product feed for every store | `/products.json?limit=250&page=N` |
| `collection` | Products from one or more collection handles/URLs | `/collections/{handle}/products.json` |
| `product` | Exact products by handle or product URL | `/products/{handle}.json` |
| `search` | In-store product search, up to 10 results per store | `/search/suggest.json` |
| `collections` | Collection directory with titles, handles and reported counts | `/collections.json?limit=250&page=N` |
| `auto` | Mixed `startUrls`; each URL selects its own job | Detected per URL |

`auto` treats `/products/<handle>` as a product job, `/collections/<handle>` as a collection job, `/collections` as a collection-directory job, and any other store URL as a catalog job.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `mode` | select | `catalog` | One of the six modes above. |
| `storeUrls` | string list | - | Store domains or full URLs; `*.myshopify.com` is supported. The prefill uses `deathwishcoffee.com`. |
| `startUrls` | string list | empty | Mixed store/product/collection URLs. Used mainly by `auto`. |
| `collectionHandles` | string list | empty | Handles or collection URLs for `mode=collection`. |
| `productHandles` | string list | empty | Handles or product URLs for `mode=product`. |
| `searchQuery` | string | empty | Required for `mode=search`. |
| `currency` | string | `store` | ISO 4217 code such as `USD` or `EUR`; `store` uses `/meta.json`. |
| `country` | string | empty | Optional ISO-2 country passed as `?country=`. |
| `vendor` | string | empty | Case-insensitive vendor substring filter. |
| `productType` | string | empty | Case-insensitive product-type substring filter. |
| `tag` | string | empty | Keep products with a tag containing this substring. |
| `minPrice` | number | empty | Drop products below this lowest-variant price. |
| `maxPrice` | number | empty | Drop products above this lowest-variant price. |
| `available` | select | `any` | `any`, `available` or `unavailable`. |
| `maxItems` | integer | `1000` | 1-50,000 dataset rows; variant rows count individually when flattening. |
| `maxPages` | integer | `100` | 1-100 pages per feed. Shopify refuses offsets above 25,000. |
| `includeDetails` | boolean | `false` | One extra `/products/{handle}.json` request per product for barcode, weight and inventory. |
| `flattenVariants` | boolean | `false` | Emit one `recordType: variant` row per variant instead of nested product rows. |
| `proxyConfiguration` | object | empty | Direct requests are always tried first. After a 403/429 the actor retries via AUTO, or via this configuration if provided. |

The smallest useful run is also the daily prefill: `deathwishcoffee.com`, three records, no detail enrichment.

#### Examples

Full catalog from one store:

```json
{
  "mode": "catalog",
  "storeUrls": ["https://deathwishcoffee.com"],
  "currency": "USD",
  "maxItems": 100
}
```

Collection and sale filters:

```json
{
  "mode": "collection",
  "storeUrls": ["allbirds.com"],
  "collectionHandles": ["mens-sale"],
  "maxPrice": 100,
  "available": "available",
  "flattenVariants": true
}
```

Exact products by URL:

```json
{
  "mode": "product",
  "startUrls": [
    "https://deathwishcoffee.com/products/valhalla-java",
    "https://deathwishcoffee.com/products/dark-roast"
  ],
  "includeDetails": true
}
```

Search a set of stores:

```json
{
  "mode": "search",
  "storeUrls": ["allbirds.com", "rothys.com"],
  "searchQuery": "wool runner",
  "maxItems": 20
}
```

Mixed URLs in one run:

```json
{
  "mode": "auto",
  "startUrls": [
    "https://allbirds.com",
    "https://allbirds.com/collections/mens",
    "https://allbirds.com/products/wool-runner"
  ]
}
```

### Product output

Product records use `recordType: "product"` and contain:

- `productId` and legacy `id`, `handle`, `title`, `url`
- `vendor`, `productType`, `tags`
- `bodyHtml` (raw) and `description` (plain text)
- `createdAt`, `updatedAt`, `publishedAt`, `publishedScope`
- `price`, `priceMin`, `priceMax`, `compareAtPrice`, `compareAtPriceMax`, `discountPercent`, `onSale`, `currency`
- `available`, `variantCount`, `availableVariantCount`
- `variants`: variant id, title, SKU, price, compare-at price, availability, option values and named `options`
- `images` (URL list), `imageDetails` (URL/width/height/position), `featuredImage`
- `options`: option names and their values
- `store`: `{url, name, currency}`, plus legacy `storeUrl`
- `source`, `sourceUrl`, `scrapedAt`, `recordType`

With `includeDetails` enabled, variants can also contain `barcode`, `weight`, `weightUnit`, `grams`, `inventoryQuantity`, `inventoryPolicy`, `inventoryManagement`, `quantityRule`, `requiresShipping` and `taxable`. Inventory quantities are only emitted when the store exposes `inventory_management` and the value is not Shopify's `999999` untracked sentinel.

When the detail endpoint omits the `available` flag but publishes inventory management, availability is derived from stock: positive quantity, or `inventory_policy=continue`, means available. Without published inventory the flag stays omitted rather than being guessed.

### Variant output

With `flattenVariants: true`, each variant becomes its own row with `recordType: "variant"`, plus the parent `productId`, `productHandle`, `productTitle`, `productUrl`, `vendor` and `productType`. Product and variant IDs are de-duplicated.

### Collection output

`mode=collections` emits `recordType: "collection"` rows with `collectionId`, `handle`, `url`, `title`, `description`, `productsCount` (the store's reported count), timestamps, image, `store`, `sourceUrl` and `scrapedAt`.

### Run summary

The dataset stays clean: products, variants and collections only. A separate key-value record named `OUTPUT` holds the run summary with each store's status, HTTP code, pages fetched, record counts, detail failures and capped flag.

Per-store statuses:

| Status | Meaning |
|---|---|
| `OK` | The feed was read normally. |
| `CAPPED` | The page cap or Shopify's 25,000-offset ceiling was reached. |
| `PARTIAL` | Some pages or detail requests failed after data was collected. |
| `PASSWORD_PROTECTED` | The store returned 401. |
| `FROZEN` | The store returned 402. |
| `BLOCKED` | The store returned 403, including after the automatic proxy retry. |
| `NO_JSON` | The store returned 404 or a non-JSON response. |
| `RATE_LIMITED` | The store returned 429 after retries. |

If every store fails, the run fails with the per-store map instead of finishing silently with an empty dataset. If some stores succeed, the run succeeds and the summary reports the failures.

### Pagination notes

- 250 products per page, the Shopify maximum.
- Short pages are not the end of the feed. The walker continues until an empty page, an error, `maxPages`, or the 25,000-offset ceiling.
- Products are de-duplicated by store and product ID; variants by store and variant ID.
- `maxItems` is a global cap across all stores and jobs.

### Currency

Shopify localizes prices by IP. The actor pins `?currency=` on every list, collection and product request. The default `store` setting reads the store's base currency from `/meta.json`; an explicit ISO code overrides it. Each product records the currency reported by its variants when available.

### FAQ

**Do I need a Shopify account or API key?** No. The actor only reads public storefront JSON.

**Do I need a proxy?** No. Direct requests are tried first. Apify's datacenter proxy is used only after a 403 or 429.

**Why does search return at most 10 products per store?** That is Shopify's public suggest endpoint limit. There is no pagination on it. Use `catalog` or `collection` mode for larger feeds.

**Why is the product count lower than the collection page says?** Collection product endpoints expose only purchasable/published products, while `products_count` counts more. The reported count is kept as `productsCount` and is not treated as ground truth.

**Why are some inventory fields missing?** Inventory is a merchant setting. Stores that do not publish `inventory_quantity` cannot be forced to reveal it, and the untracked `999999` sentinel is never emitted as stock.

**What happens on password-protected stores?** Their public JSON returns 401. The actor reports `PASSWORD_PROTECTED` and does not retry with a proxy.

**Why did a large store stop at 25,000 products?** Shopify rejects page offsets above 25,000 on `/products.json`. Split large catalogs by collection to go deeper.

**Are descriptions HTML or plain text?** Both are available: `bodyHtml` keeps the original markup and `description` is a plain-text version.

**Is this affiliated with Shopify?** No. It is an independent third-party tool that reads publicly available storefront data.

### Limits

- Password-protected stores are not scrapable through the public JSON.
- Stores that disable the JSON endpoints return `NO_JSON`.
- Collection membership is not exposed publicly; the actor emits collection records instead of inventing per-product membership.
- Search is capped at 10 products per store.
- Inventory counts are available only where the store publishes them.
- The product feed is limited to the newest 25,000 products per store/feed.

# Actor input Schema

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

What to scrape. `catalog` walks the public product feed, `collection` walks specific collections, `product` fetches exact product handles, `search` uses the storefront search suggestions, `collections` lists collection metadata, and `auto` detects the type of each URL in `startUrls`.

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

Store domains or URLs, e.g. `deathwishcoffee.com` or `https://www.allbirds.com`. Accepts custom domains and `*.myshopify.com`. A product or collection URL is reduced to its store for catalog/collections/search modes.

## `startUrls` (type: `array`):

Store, collection or product URLs. In `auto` mode each URL selects its own job: `/products/<handle>` fetches one product, `/collections/<handle>` fetches a collection, any other store URL fetches the catalog. In the other modes these URLs add stores and, for collection/product modes, their handles.

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

Collection handles or collection URLs, e.g. `new-arrivals`, `sale`, or `https://store.com/collections/sale`. Handles are applied to every store in `storeUrls`; a full collection URL is applied to its own store.

## `productHandles` (type: `array`):

Product handles or product URLs, e.g. `dark-roast-coffee` or `https://store.com/products/dark-roast-coffee`. Handles are applied to every store in `storeUrls`; a full product URL is applied to its own store.

## `searchQuery` (type: `string`):

In-store search term. Shopify's public suggest endpoint returns at most 10 products per store and has no pagination.

## `currency` (type: `string`):

ISO 4217 code to pin prices to, e.g. `USD`, `EUR`, `GBP`. Use `store` to use the store's base currency from `/meta.json`. Pinning is important because Shopify localizes prices by IP.

## `country` (type: `string`):

Two-letter ISO country code passed as `?country=` to the storefront, e.g. `US`, `GB`, `DE`.

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

Keep products whose vendor contains this text (case-insensitive).

## `productType` (type: `string`):

Keep products whose product type contains this text (case-insensitive).

## `tag` (type: `string`):

Keep products with at least one tag containing this text (case-insensitive).

## `minPrice` (type: `number`):

Drop products whose lowest variant price is below this value.

## `maxPrice` (type: `number`):

Drop products whose lowest variant price is above this value.

## `available` (type: `string`):

Keep all products, only products with at least one purchasable variant, or only sold-out products.

## `maxItems` (type: `integer`):

Hard cap on dataset rows across all stores. When `flattenVariants` is on, each variant row counts as one.

## `maxPages` (type: `integer`):

Page cap per product/collection feed. Each page holds up to 250 items and Shopify refuses offsets above 25,000, so the effective maximum is 100.

## `includeDetails` (type: `boolean`):

Fetch `/products/{handle}.json` for each emitted product to add barcode, weight/grams and inventory fields. Costs one extra request per product and is off by default. Product mode always uses the detail endpoint.

## `flattenVariants` (type: `boolean`):

Emit one `recordType: variant` row per variant instead of one product row with nested variants. Variant and product IDs are de-duplicated.

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

Leave empty to fetch directly. After a 403 or 429 block the actor retries through Apify's datacenter proxy (AUTO) automatically. If you set this, your configuration is used for that retry instead of AUTO; direct requests are still tried first.

## Actor input object example

```json
{
  "mode": "catalog",
  "storeUrls": [
    "https://deathwishcoffee.com"
  ],
  "startUrls": [],
  "collectionHandles": [],
  "productHandles": [],
  "currency": "USD",
  "available": "any",
  "maxItems": 3,
  "maxPages": 100,
  "includeDetails": false,
  "flattenVariants": false
}
```

# Actor output Schema

## `items` (type: `string`):

Dataset containing product, variant and collection records (filtered by recordType).

## `runSummary` (type: `string`):

Key-value store record named OUTPUT with per-store OK/CAPPED/PARTIAL and error statuses.

# 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 = {
    "mode": "catalog",
    "storeUrls": [
        "https://deathwishcoffee.com"
    ],
    "startUrls": [],
    "collectionHandles": [],
    "productHandles": [],
    "currency": "USD",
    "available": "any",
    "maxItems": 3,
    "maxPages": 100,
    "includeDetails": false,
    "flattenVariants": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("nimbryxo-studio/shopify-store-catalog-and-pricing-extractor").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 = {
    "mode": "catalog",
    "storeUrls": ["https://deathwishcoffee.com"],
    "startUrls": [],
    "collectionHandles": [],
    "productHandles": [],
    "currency": "USD",
    "available": "any",
    "maxItems": 3,
    "maxPages": 100,
    "includeDetails": False,
    "flattenVariants": False,
}

# Run the Actor and wait for it to finish
run = client.actor("nimbryxo-studio/shopify-store-catalog-and-pricing-extractor").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 '{
  "mode": "catalog",
  "storeUrls": [
    "https://deathwishcoffee.com"
  ],
  "startUrls": [],
  "collectionHandles": [],
  "productHandles": [],
  "currency": "USD",
  "available": "any",
  "maxItems": 3,
  "maxPages": 100,
  "includeDetails": false,
  "flattenVariants": false
}' |
apify call nimbryxo-studio/shopify-store-catalog-and-pricing-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nimbryxo-studio/shopify-store-catalog-and-pricing-extractor"
        }
    }
}
```

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/MaKZQdevxgpAmpJ8l/builds/rfdRrnEMOeqFiOPHE/openapi.json
