# Shopify Product Scraper - Prices, Variants & Stock (`harvestlab/shopify-scraper`) Actor

Shopify product scraper for public stores: export products, prices, variants, SKUs, images, collections, stock signals, discounts, and webhook/MCP price-drop alerts. Built for catalog exports, competitor tracking, and scheduled monitoring.

- **URL**: https://apify.com/harvestlab/shopify-scraper.md
- **Developed by:** [Nick](https://apify.com/harvestlab) (community)
- **Categories:** E-commerce, AI, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 product scrapeds

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## Shopify Product Scraper - Prices, Variants & Stock

Use this Shopify product scraper to export public Shopify store catalogs with products, prices, variants, SKUs, images, collections, stock signals, discounts, and price-drop alerts. It is built for clean product datasets first, then scheduled competitor monitoring once your export shape is proven.

**Best first run**

```json
{
  "storeUrls": ["https://allbirds.com"],
  "maxProducts": 50,
  "includeVariants": true,
  "includeImages": true
}
````

Use this actor when you need a Shopify product scraper, Shopify price scraper, Shopify variant exporter, or Shopify price drop monitor. Start with one store, then schedule watch mode once the output matches your workflow. Product exports stay clean in the default dataset; collections, alert rollups, AI analysis, and diagnostics are separated into their own datasets.

**What you get back**

- A clean product dataset with normalized `url`, `title`, `vendor`, `product_type`, `currency`, price ranges, variants, SKUs, image URLs, tags, availability, and discount fields.
- Separate `collections`, `alerts`, `analysis`, and `diagnostics` datasets so catalog exports are not mixed with rollups or operational records.
- Good starter run: one store, 50 products, variants and images on, collections off. Add collection graphs, watch mode, webhooks, MCP connectors, or AI after the base product export looks right.

**Copy-paste workflows**

- **Competitor catalog export:** run one store with `maxProducts: 50`, then export products and variants to CSV.
- **Sale scanner:** set `discountedOnly: true` to pull only products with active compare-at discounts.
- **Price-drop monitor:** schedule the actor daily with `watchMode: true`, then connect `alertWebhookUrl` or `outputConnectors` to Slack, Notion, Supabase, Zapier, Make, or n8n.
- **Market map:** scrape several stores with `includeCollections: true` to compare category depth, vendors, pricing bands, and stock signals.

**Related marketplace actors:** use `amazon-scraper` for Amazon ASINs and bestseller ranks, `ebay-scraper` for marketplace listings and sold-history research, and `bol-com-scraper` for Benelux marketplace price tracking.

Scrape product catalogs from public Shopify stores and export titles, prices, variants, SKUs, stock signals, images, discounts, and collection graphs in seconds. Optional price-watch mode tracks price changes across runs and fires webhook or MCP connector alerts when products drop. AI-powered catalog analysis delivers competitive positioning and pricing strategy insights on demand.

### Shopify Product Scraper

This actor pulls product catalog data from public Shopify-powered stores and returns structured product rows with full variant, price, image, collection, and availability fields. No Shopify admin access or Shopify API key is required for public storefront data.

Each product comes back with full variant data including SKUs, prices, compare-at prices, inventory quantities, option values (size, color, material), and a derived `discount_percent` per variant. Prices are normalized from Shopify's string format to floats. The actor resolves each store's ISO currency code from `/meta.json`, the `X-Shopify-Shop-Currency` response header, or ccTLD inference, and emits it as a top-level `currency` field on every product.

### Shopify Price Drop Monitor

Price-watch mode (`watchMode: true`) persists each product's price history across runs in a named Apify key-value store (60-snapshot FIFO, approximately 2 months at daily cadence). On every subsequent run the actor computes `price_delta_pct`, direction (`up/down/flat`), and `first_seen_at` for every product. Add `alertWebhookUrl` for fire-and-forget JSON POSTs, or select `outputConnectors` to send alerts through user-authorized MCP connectors such as Slack, Notion, Supabase, or GitHub without exposing third-party credentials to the actor. This replaces a custom backend that would otherwise require a database, a cron job, and a webhook server.

**Collection scraping** walks each store's `/collections.json` to enumerate categories and build a product-to-collection membership graph, complete with per-collection discount heatmaps. **Discounted-only filtering** lets you pull only sale items for arbitrage and reseller research. **AI catalog analysis** - via OpenRouter, Anthropic, Google AI, OpenAI, or Ollama - generates a structured report on pricing strategy, product gaps, and competitive positioning.

The actor supports multi-store runs, processing each store sequentially with polite 1-second delays between requests and exponential backoff on rate limits.

### Features

- **Public Shopify catalog data** - exports product and collection data from public storefronts. No browser, no theme scraping, no fragile selectors that break on theme updates.
- **Full product data** - Titles, handles, descriptions (HTML stripped to plain text), vendors, product types, tags, creation and update dates.
- **Variant extraction** - Prices (as floats), compare-at prices, SKUs, inventory quantities, weight, option values (size, color, material), plus a derived `discount_percent` per variant.
- **Image collection** - All product images with dimensions and alt text, plus featured image identification.
- **Derived analytics** - Price ranges (min/max), average price, on-sale detection, variant counts, `has_variants` flag, `available_variant_count` (in-stock variants), `min_weight_g`/`max_weight_g` normalized to grams, `max_discount_percent`, `avg_discount_percent`, `compare_at_min_price`, `compare_at_max_price`.
- **Storefront currency detection** - Resolves each store's ISO 4217 currency code from `/meta.json`, the `X-Shopify-Shop-Currency` response header, or ccTLD fallback (28 country codes mapped).
- **Collections scraping** - Optional `/collections.json` scrape. Each collection (id, title, handle, description, products\_count, image, URL) is emitted as a dataset item with `type="collection"`, ideal for category-navigation analysis.
- **Discounted-only filter** - Set `discountedOnly: true` to receive only products with at least one variant on sale. Built for arbitrage, reseller research, and sale-intensity dashboards. Skipped products are not charged.
- **Product-to-collection graph + discount heatmap** - Set `fetchCollectionProducts: true` to walk collection product pages and build a membership graph. Every product gains a `collection_memberships` array and `collection_count`. Every collection gains a `discount_stats` block (product\_count, on\_sale\_pct, avg/max discount percent, price range). No other Apify Shopify scraper exposes this graph.
- **Price-watch mode + drop alerts** - Set `watchMode: true` to persist each product's min/max/avg price across runs. Every product receives a `price_watch` block with `{price_previous, price_now, price_delta_pct, direction, price_drop, first_seen_at, history_sample_count}`. Add `alertWebhookUrl` to POST JSON to Slack/Zapier/n8n/Make, or select `outputConnectors` to send alerts through MCP connectors when a product drops by >= `alertMinDropPct`.
- **Multi-store support** - Scrape multiple Shopify stores in a single run.
- **AI catalog analysis** - LLM-powered analysis of pricing strategy, product gaps, competitive positioning, and actionable recommendations. When graph mode is on, adds `collection_discount_heatmap` and `merchandising_signals`.
- **Multi-LLM support** - Choose between OpenRouter (recommended - 300+ models, cheapest), Anthropic (Claude), Google AI (Gemini), OpenAI (GPT), or Ollama (self-hosted).
- **Pay-per-event pricing** - Only pay for products actually scraped. Webhook failures are not charged.

### Use Cases

#### Dropshippers and Product Researchers

Discover winning products across Shopify stores in your niche. Compare pricing, identify trending items, and find suppliers by scanning vendor data across multiple stores in a single run. Export product catalogs to CSV for side-by-side comparison with your own inventory.

#### Competitive Price Monitoring with Alerts

Run the actor on a daily schedule with `watchMode: true` and `alertWebhookUrl` pointing to a Slack channel. The first run builds a baseline. From the second run onward, any product that drops by your configured percentage fires an instant Slack notification with the product title, old price, new price, and percentage change. No custom backend required.

#### Market Research and Trend Analysis

Build datasets of products across an entire market segment. Analyze pricing distributions, popular product types, common tags, and vendor landscapes. Use the AI-powered catalog analysis to identify underserved categories and pricing gaps without reviewing products manually.

#### Sale and Arbitrage Research

Enable `discountedOnly: true` to pull only products currently on sale. The actor emits full variant-level discount percentages and compare-at prices, making it easy to identify reseller arbitrage opportunities, clearance liquidation, and deep-discount patterns.

#### E-commerce Data Aggregation

Aggregate product data from multiple Shopify stores into a single dataset for marketplace platforms, comparison shopping engines, or product discovery tools. The structured JSON output integrates directly with databases, APIs, and data pipelines. Currency normalization means all prices are labeled with their ISO code regardless of the store's country.

#### Inventory and Availability Tracking

Monitor variant-level inventory data to detect stock levels, out-of-stock products, and restocking patterns. Useful for affiliate marketers who need to promote in-stock items and for resellers timing purchasing decisions based on inventory signals.

#### Collection Category Analysis

Enable `fetchCollectionProducts: true` to build the full product-to-collection membership graph. This exposes which collections are deep-discount clearance zones versus full-price premium ranges - merchandising intelligence that normally requires manual store browsing or a Shopify partner account.

### Input

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `storeUrls` | array | required | List of Shopify store URLs to scrape (e.g. `https://kith.com`) |
| `maxProducts` | integer | 100 | Max products per store (1-500) |
| `includeVariants` | boolean | true | Include variant data (prices, SKUs, inventory, options) |
| `includeImages` | boolean | true | Include product image URLs with dimensions |
| `includeCollections` | boolean | false | Also scrape `/collections.json` for each store |
| `maxCollections` | integer | 100 | Max collections per store (1-250) |
| `discountedOnly` | boolean | false | Only emit products with at least one on-sale variant |
| `fetchCollectionProducts` | boolean | false | Build product-to-collection graph + per-collection discount heatmap |
| `maxProductsPerCollection` | integer | 250 | Max products walked per collection in graph mode (1-1000) |
| `watchMode` | boolean | false | Track price changes across runs using a named KV store |
| `alertWebhookUrl` | string | - | HTTPS webhook URL for price-drop alerts (auto-enables `watchMode`) |
| `outputConnectors` | array | - | Optional MCP connectors for price-drop alerts (auto-enables `watchMode`) |
| `connectorAlertTarget` | string | - | Optional channel, page, table, or destination ID passed to MCP output tools |
| `alertMinDropPct` | integer | 5 | Minimum price drop % to trigger a webhook or connector alert |
| `enableAiAnalysis` | boolean | false | Run AI-powered catalog analysis |
| `llmProvider` | string | openrouter | LLM provider: `openrouter`, `anthropic`, `google`, `openai`, `ollama` |
| `llmModel` | string | (auto) | Override the provider's default model |
| `openrouterApiKey` | string | - | OpenRouter API key (get one free at openrouter.ai/keys) |
| `anthropicApiKey` | string | - | Anthropic API key |
| `googleApiKey` | string | - | Google AI (Gemini) API key |
| `openaiApiKey` | string | - | OpenAI API key |
| `ollamaBaseUrl` | string | http://localhost:11434 | Ollama base URL for self-hosted LLM |
| `proxyConfiguration` | object | Apify Proxy | Proxy settings (datacenter is sufficient for most Shopify stores) |

**CLI aliases accepted:** `shopUrl`, `storeUrl`, `url` (single URL); `shopUrls` (array); `maxItems` (alias for maxProducts).

### Output

The actor writes to multiple datasets so exports stay clean:

- **Products Dataset** - default buyer-facing product rows with variants, images, prices, stock signals, discounts, and optional `price_watch` blocks.
- **Collections Dataset** - collection rows from `/collections.json`, including optional `discount_stats` when graph mode is enabled.
- **Alerts Dataset** - price-drop webhook and MCP connector dispatch summaries from watch mode.
- **AI Analysis Dataset** - optional LLM catalog-analysis records.
- **Diagnostics Dataset** - no-result and operational diagnostic records.

Each product is output as a separate item in the Products Dataset.

**Product item:**

```json
{
    "store_url": "https://hydrogen-preview.myshopify.com",
    "product_id": 6730943955025,
    "title": "The Collection Snowboard: Hydrogen",
    "handle": "the-collection-snowboard-hydrogen",
    "vendor": "Hydrogen Vendor",
    "product_type": "Snowboard",
    "tags": ["Premium", "Snow", "Winter"],
    "description": "A high-performance snowboard designed for experienced riders.",
    "created_at": "2023-01-01T00:00:00-05:00",
    "updated_at": "2024-06-15T12:30:00-05:00",
    "options": [{"name": "Size", "position": 1, "values": ["154cm", "158cm", "162cm"]}],
    "currency": "USD",
    "min_price": 599.95,
    "max_price": 749.95,
    "average_price": 674.95,
    "on_sale": true,
    "max_discount_percent": 14.29,
    "avg_discount_percent": 14.29,
    "compare_at_min_price": 699.95,
    "compare_at_max_price": 749.95,
    "total_variants": 3,
    "has_variants": true,
    "available_variant_count": 2,
    "min_weight_g": 3200.0,
    "max_weight_g": 3500.0,
    "url": "https://hydrogen-preview.myshopify.com/products/the-collection-snowboard-hydrogen",
    "scraped_at": "2026-04-22T10:30:00+00:00",
    "variants": [
        {
            "id": 39888274382929,
            "title": "154cm",
            "price": 599.95,
            "compare_at_price": 699.95,
            "discount_percent": 14.29,
            "sku": "SNOW-HYD-154",
            "available": true,
            "inventory_quantity": 12,
            "weight": 3200.0,
            "weight_unit": "g",
            "option1": "154cm",
            "option2": null,
            "option3": null
        }
    ],
    "images": [
        {
            "id": 28505021718609,
            "src": "https://cdn.shopify.com/s/files/1/0551/4566/0577/products/snowboard.jpg",
            "width": 1200,
            "height": 800,
            "alt": "Hydrogen Snowboard"
        }
    ],
    "featured_image": "https://cdn.shopify.com/s/files/1/0551/4566/0577/products/snowboard.jpg"
}
```

**Price-watch block** (added to each product when `watchMode: true`):

```json
{
    "price_watch": {
        "price_previous": 49.00,
        "price_now": 29.00,
        "price_delta_pct": -40.82,
        "direction": "down",
        "price_drop": true,
        "first_seen_at": "2026-04-01T00:00:00+00:00",
        "history_sample_count": 14
    }
}
```

**Collection item** (when `includeCollections: true`, written to the Collections Dataset):

```json
{
    "type": "collection",
    "store_url": "https://hydrogen-preview.myshopify.com",
    "collection_id": 387214442552,
    "title": "Backcountry Collection",
    "handle": "backcountry",
    "description": "Go further with our most technologically advanced boards.",
    "products_count": 26,
    "url": "https://hydrogen-preview.myshopify.com/collections/backcountry",
    "scraped_at": "2026-04-22T10:30:00+00:00"
}
```

**AI analysis item** (when `enableAiAnalysis: true`, written to the AI Analysis Dataset):

```json
{
    "type": "ai_analysis",
    "store_url": "https://hydrogen-preview.myshopify.com",
    "total_products_analyzed": 50,
    "analysis": {
        "store_overview": "A specialty winter sports retailer focused on premium snowboards.",
        "pricing_strategy": "Premium pricing with selective discounting on older SKUs.",
        "competitive_positioning": "Premium segment targeting experienced riders.",
        "product_gaps": "No entry-level boards, limited accessories.",
        "recommendations": ["Expand into beginner boards", "Add bundle pricing"]
    },
    "llm_provider": "openrouter",
    "llm_model": "google/gemini-2.0-flash-001",
    "analyzed_at": "2026-04-22T10:35:00+00:00"
}
```

**Pay-per-event pricing:**

| Event | Price | Description |
|-------|-------|-------------|
| `product-scraped` | $0.004 | Per product successfully extracted |
| `collection-scraped` | $0.001 | Per collection extracted (when `includeCollections=true`) |
| `price-drop-detected` | $0.005 | Per product whose price fell >= `alertMinDropPct` (watchMode only; first-run products not charged) |
| `alert-dispatched` | $0.002 | Per price-drop webhook successfully delivered (2xx response) |
| `connector-alert-dispatched` | $0.002 | Per price-drop alert successfully delivered through an MCP output connector |
| `ai-analysis-completed` | $0.05 | Per store catalog analyzed by LLM |

**vs. commercial alternatives**: Shopify Partner API requires store-owner approval per store; DataForSEO e-commerce endpoints charge $1.50/1,000 requests; Prisync charges $99/mo for competitor price tracking. This actor uses pay-per-event with no subscription - $0.004/product and zero monthly fees.

### Quick Start

**Scrape a single store:**

```json
{
    "storeUrls": ["https://hydrogen-preview.myshopify.com"]
}
```

**Scrape multiple stores with a product limit:**

```json
{
    "storeUrls": [
        "https://hydrogen-preview.myshopify.com",
        "https://shop.example.com"
    ],
    "maxProducts": 200,
    "includeVariants": true,
    "includeImages": true
}
```

**Sale-hunting mode - only discounted products:**

```json
{
    "storeUrls": ["https://shop.example.com"],
    "maxProducts": 200,
    "discountedOnly": true,
    "includeCollections": true,
    "maxCollections": 50
}
```

**Price-watch + drop alerts (schedule this daily):**

```json
{
    "storeUrls": ["https://shop.example.com"],
    "maxProducts": 200,
    "watchMode": true,
    "alertWebhookUrl": "https://hooks.slack.com/services/T000/B000/XXX",
    "alertMinDropPct": 10
}
```

The first run builds the history baseline - every product emits `price_watch.direction: "new"` and no alert fires. From the second run onward, the actor compares each product's current min\_price against the prior snapshot and fires a webhook for every product that drops by at least 10%.

Webhook payload shape:

```json
{
    "store_domain": "shop.example.com",
    "product_id": 1234567890,
    "title": "Classic Tee",
    "handle": "classic-tee",
    "price_previous": 49.00,
    "price_now": 29.00,
    "price_delta_pct": -40.82,
    "direction": "down",
    "currency": "USD",
    "on_sale": true,
    "url": "https://shop.example.com/products/classic-tee",
    "scraped_at": "2026-04-22T23:42:00+00:00"
}
```

Works with Slack Incoming Webhooks, Zapier, Make/Integromat, n8n, or any HTTP endpoint that accepts JSON POSTs.

**Price-watch + MCP connector alerts:**

```json
{
    "storeUrls": ["https://shop.example.com"],
    "maxProducts": 200,
    "watchMode": true,
    "outputConnectors": ["conn_your_slack_or_notion_connector"],
    "connectorAlertTarget": "#price-alerts",
    "alertMinDropPct": 10
}
```

The connector picker appears in Apify Console when you run the actor. The actor calls only the connector ID you provide, through Apify's MCP proxy, and never receives your Slack, Notion, Supabase, GitHub, or other third-party credentials.

**AI-powered catalog analysis:**

```json
{
    "storeUrls": ["https://hydrogen-preview.myshopify.com"],
    "maxProducts": 100,
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "sk-or-v1-your-key-here"
}
```

**Collection graph + AI merchandising signals:**

```json
{
    "storeUrls": ["https://hydrogen-preview.myshopify.com"],
    "maxProducts": 200,
    "fetchCollectionProducts": true,
    "maxCollections": 30,
    "enableAiAnalysis": true,
    "llmProvider": "openrouter",
    "openrouterApiKey": "sk-or-v1-your-key-here"
}
```

#### MCP Quickstart - call this actor from Claude / Cursor / ChatGPT

Open Apify's hosted MCP server with this actor selected:

```text
https://mcp.apify.com?tools=harvestlab/shopify-scraper
```

Then prompt the agent:

> "Use the harvestlab/shopify-scraper actor on Apify to monitor these five Shopify stores for product price drops, new variants, and sold-out items. Send the changed products back as JSON for a Slack alert workflow."

Through Apify MCP, the agent will discover the actor schema, generate the right input, run it, and pipe the typed output back into your conversation.

### Use with AI agents

shopify-scraper output is agent-ready: typed product rows with variants, SKUs, inventory signals, currency, `price_watch` deltas, and a product-to-collection graph from public Shopify storefront data. DTC research, brand-protection, and arbitrage workflows get cross-store catalog exports and drop-alert intelligence at $0.004 per product instead of $99-299/mo Prisync or Visualping seats. Structured JSON pairs cleanly with `alertWebhookUrl` or `outputConnectors` for n8n, Zapier, Slack, Notion, Supabase, or GitHub alert workflows.

#### LangChain tool wrapper

```python
from langchain_core.tools import Tool
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

def _track_shopify_store(args: dict) -> list:
    run = client.actor("harvestlab/shopify-scraper").call(run_input={
        "storeUrls": args["storeUrls"],
        "watchMode": args.get("watchMode", True),
        "alertWebhookUrl": args.get("alertWebhookUrl"),
        "alertMinDropPct": args.get("alertMinDropPct", 5),
        "fetchCollectionProducts": True,
    })
    return list(client.dataset(run["defaultDatasetId"]).iterate_items())

track_shopify_store = Tool(
    name="track_shopify_store",
    description="Scrape Shopify products, variants, stock signals, price drops, and collection graph. Input: {storeUrls: [str], watchMode: bool, alertWebhookUrl: str, alertMinDropPct: int}.",
    func=_track_shopify_store,
)
## track_shopify_store.invoke({"storeUrls": ["https://hydrogen-preview.myshopify.com"], "watchMode": True, "alertWebhookUrl": "https://hooks.slack.com/services/T000/B000/XXX"})
```

#### LangGraph arbitrage / brand-protection node

```python
from langgraph.graph import StateGraph, END
from typing import TypedDict
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

class StoreState(TypedDict):
    storeUrls: list[str]
    alertMinDropPct: int
    products: list[dict]
    drops: list[dict]

def track_node(state: StoreState) -> StoreState:
    run = client.actor("harvestlab/shopify-scraper").call(run_input={
        "storeUrls": state["storeUrls"],
        "watchMode": True,
        "alertMinDropPct": state["alertMinDropPct"],
        "discountedOnly": False,
    })
    items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
    drops = [i for i in items if (i.get("price_watch") or {}).get("price_drop")]
    return {**state, "products": items, "drops": drops}

graph = StateGraph(StoreState)
graph.add_node("track", track_node)
graph.set_entry_point("track")
graph.add_edge("track", END)
## graph.compile().invoke({"storeUrls": ["https://shop.example.com"], "alertMinDropPct": 10, "products": [], "drops": []})
```

### Troubleshooting

#### Store URL returns no product catalog

The target site is not a Shopify store, or the store owner has disabled public product catalog access. A 404 or HTML page means the site uses a different platform (WooCommerce, Magento, BigCommerce) or has explicitly restricted product export access. The actor logs the error and skips that store without failing the entire run.

#### Store is password-protected and returns no products

Shopify stores in development mode show a login page at `/password` before any other route. Product catalog access returns an empty response or a redirect in this state. There is no workaround - password-protected stores do not expose public product data. Wait until the store goes live or contact the store owner for access.

#### Product variants appear incomplete or prices show null

Shopify usually exposes published variant data for public products. If variant prices appear as `null`, the store may be running a headless Shopify setup with custom storefront behavior or restricted product data. Inventory quantities can also be hidden by the store owner via Shopify admin settings.

#### maxProducts cap hit but the store has more products

The actor's hard cap is 500 products per store per run. If the store has more than 500 products, use `fetchCollectionProducts: true` to walk collection-level product lists - large catalogs are often split across collections, each with fewer than 500 items.

#### Price-watch alert not firing after a price change

Alerts require all four conditions simultaneously: (1) `watchMode: true`, (2) a valid `alertWebhookUrl` returning a 2xx response or an `outputConnectors` MCP connector with a supported output-writing tool, (3) the same store URL used across at least 2 runs so a prior baseline exists, and (4) the price delta meets or exceeds `alertMinDropPct`. The first run always seeds the baseline silently - no alert fires. Failed webhook or connector dispatches are logged and are not charged as dispatched alerts.

#### AI analysis returns an error about missing API key

Provide the API key either in the actor input field (e.g. `openrouterApiKey`) or as an environment variable (`OPENROUTER_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`, or `OPENAI_API_KEY`). Set `enableAiAnalysis: false` to skip AI entirely - the product scrape completes normally without it.

#### No products scraped from any store

Check the run log for HTTP errors. Common causes: (1) the URLs are not Shopify stores, (2) the stores have restricted public product catalog access, (3) aggressive rate limiting - try enabling the Apify proxy in the proxy settings field. Datacenter proxies are sufficient for the vast majority of Shopify stores.

### Frequently Asked Questions

**Can I scrape products from any Shopify store?**
You can scrape public product data from most live Shopify storefronts. Password-protected stores, non-Shopify sites, and stores that restrict public product catalog access will return diagnostics instead of product rows.

**Does it return variants and SKUs?**
Yes. When `includeVariants: true`, each product includes variant prices, compare-at prices, SKUs, option values, availability, and stock signals when the store exposes them.

**Can it monitor stock or inventory signals?**
Yes. The actor emits availability and variant-level inventory fields when they are public. Some store owners hide exact inventory quantities, so treat stock data as a signal rather than a guaranteed warehouse count.

**Can it detect Shopify price drops?**
Yes. Enable `watchMode: true` to persist price history across runs, then use `alertWebhookUrl` or `outputConnectors` with `alertMinDropPct` to send Slack, Notion, Supabase, Zapier, Make, n8n, or other MCP-compatible alerts when products drop by your threshold.

**Do I need a Shopify API key?**
No Shopify admin API key is needed for public storefront product data. AI analysis is optional and requires only the LLM provider key you choose.

### Legal and Compliance

This actor accesses product data that public Shopify storefronts expose without authentication, but you are responsible for confirming that your use complies with the target store's terms and applicable laws.

**User Responsibility.** You are responsible for ensuring your use of this actor complies with all applicable laws and regulations, including data protection laws such as GDPR, CCPA, and other privacy regulations. If you collect data that may include personal information (e.g. vendor names that identify individuals), ensure you have a lawful basis for processing it.

**Data Handling.** Product data scraped by this actor is stored in your Apify dataset. You control the retention, export, and deletion of this data through the Apify platform. The price-watch KV store (`shopify-price-history`) persists across runs and can be manually deleted from your Apify account storage at any time.

**Rate Limiting.** The actor implements polite crawling with 1-second delays between requests and exponential backoff on rate limit responses (HTTP 429/403). This prevents excessive load on store servers.

**Third-party review widgets** (Yotpo, Judge.me, Stamped.io, Okendo) are not scraped. These platforms have separate Terms of Service and often require authentication.

### Pair with amazon-scraper

Shopify Scraper covers direct-to-consumer Shopify stores; [Amazon Scraper](https://apify.com/harvestlab/amazon-scraper) covers the marketplace side of the same SKUs. Run both on a daily schedule with `watchMode: true` to build a unified cross-channel price-history dataset: same product, two channels, side-by-side delta. Common workflows:

- **Brand price-policing** - detect when a Shopify brand's product is undercut on Amazon by a third-party seller. Pipe both datasets into a single Slack channel via `alertWebhookUrl`.
- **Reseller arbitrage** - pull `discountedOnly: true` from Shopify, cross-reference Amazon Buy Box price, surface profitable flips.
- **MAP enforcement** - manufacturers track Minimum Advertised Price compliance across both channels in one pipeline.

Both actors share the canonical `url` and `currency` output fields, so a join on product handle or SKU is one query.

### Related Actors

- [Amazon Scraper](https://apify.com/harvestlab/amazon-scraper) - Scrape product data, prices, reviews, and Buy Box across 10+ Amazon domains
- [Website Contact Extractor](https://apify.com/harvestlab/contact-extractor) - Extract emails, social profiles, and tech stack from any website
- [Trustpilot Scraper](https://apify.com/harvestlab/trustpilot-scraper) - Scrape reviews and ratings from Trustpilot business profiles
- [Bol.com Scraper](https://apify.com/harvestlab/bol-com-scraper) - Scrape product data from the largest Dutch e-commerce marketplace
- [Review Analyzer](https://apify.com/harvestlab/review-analyzer) - AI-powered sentiment analysis for product and business reviews

# Actor input Schema

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

List of Shopify store URLs to scrape (e.g. https://allbirds.com, https://kith.com). The actor auto-detects the /products.json endpoint. Accepts aliases: 'shopUrls' (array), 'shopUrl'/'storeUrl'/'url' (single URL).

## `shopUrl` (type: `string`):

CLI alias for storeUrls (single URL). Hidden from Console form.

## `storeUrl` (type: `string`):

CLI alias for shopUrl/storeUrls (single URL). Hidden from Console form.

## `url` (type: `string`):

CLI alias for shopUrl/storeUrls (single URL). Hidden from Console form.

## `shopUrls` (type: `array`):

CLI alias for storeUrls (array of URLs). Hidden from Console form.

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

Maximum number of products to scrape per store (1-500). Shopify stores can have thousands of products - start with 50 to test. Accepts alias: 'maxItems'.

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

CLI alias for maxProducts. Hidden from Console form.

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

Include all product variants (sizes, colors, etc.) with individual pricing and inventory data. Recommended for price monitoring.

## `includeImages` (type: `boolean`):

Include product image URLs in the output (primary image plus gallery images).

## `includeCollections` (type: `boolean`):

Also scrape the store's public /collections.json endpoint. Each collection (title, handle, description, products\_count, image) is emitted as a dataset item with type='collection'. Useful for category-mapping and navigation analysis.

## `maxCollections` (type: `integer`):

Maximum number of collections to scrape per store when includeCollections=true (1-250). Shopify paginates at 250 per page.

## `discountedOnly` (type: `boolean`):

If true, only emit products that have at least one variant on sale (compare\_at\_price > price). Useful for bargain-hunting, reseller arbitrage, and sale-tracking workflows.

## `fetchCollectionProducts` (type: `boolean`):

If true, walk each collection's /collections/{handle}/products.json endpoint to build a product-to-collection membership graph. Every product gets a 'collection\_memberships' array ({collection\_handle, collection\_title, position}) and 'collection\_count'. Every collection gets a 'discount\_stats' block (per-collection discount heatmap). When AI analysis is enabled, adds 'collection\_discount\_heatmap' and 'merchandising\_signals' to the output. Auto-enables includeCollections. Cost: one extra HTTP per collection - no existing Apify Shopify competitor exposes this graph.

## `maxProductsPerCollection` (type: `integer`):

When fetchCollectionProducts=true, cap the number of products fetched per individual collection (1-1000). Default 250 covers most mid-size collections in a single API call.

## `watchMode` (type: `boolean`):

Persist each product's min/max/avg price across runs in a named Apify key-value store (shopify-price-history). Every product receives a `price_watch` block with {price\_previous, price\_now, price\_delta\_pct, direction (up/down/flat), price\_drop, first\_seen\_at, history\_sample\_count}. 60-snapshot FIFO per product (~2 months at daily cadence). Bills the `price-drop-detected` event ($0.005) when a product's price falls by >= alertMinDropPct (default 5%). First-run products emit direction='new' and are not billed.

## `alertWebhookUrl` (type: `string`):

HTTPS webhook URL that receives a JSON POST for every product whose price falls >= alertMinDropPct. Payload: {store\_domain, product\_id, title, handle, vendor, price\_previous, price\_now, price\_delta\_pct, direction, currency, on\_sale, url}. Works with Slack Incoming Webhooks, Zapier, Make/Integromat, n8n, or any HTTP endpoint. Fire-and-forget with 5s timeout + 1 retry - webhook failures never fail the scraper. Setting this auto-enables watchMode. Bills `alert-dispatched` ($0.002) per successfully delivered webhook.

## `outputConnectors` (type: `array`):

Optional MCP connectors that receive price-drop alerts without exposing third-party credentials to this actor. Select Slack, Notion, Supabase, GitHub, or another MCP-compatible destination with output-writing tools. Setting this auto-enables watchMode. Bills `connector-alert-dispatched` ($0.002) per successful MCP tool call.

## `connectorAlertTarget` (type: `string`):

Optional target passed to MCP output tools. For Slack, use a channel such as #price-alerts or a channel ID. For database/page connectors, use the destination identifier expected by that connector.

## `alertMinDropPct` (type: `integer`):

Minimum price drop (in percent) required to fire a webhook and bill price-drop-detected. Default 5% - fires when a product's min\_price falls at least 5% vs. its last snapshot. Set higher (e.g. 10) for fewer, bigger alerts, or lower (e.g. 2) to catch smaller moves. Requires watchMode=true.

## `enableAiAnalysis` (type: `boolean`):

Run AI-powered analysis on the store's product catalog. Provides pricing strategy insights, product gap analysis, and competitive positioning. Requires an API key.

## `llmProvider` (type: `string`):

Choose the LLM provider for AI analysis. OpenRouter is cheapest.

## `llmModel` (type: `string`):

Specific model to use. Leave empty for the provider default (google/gemini-2.0-flash-001 for OpenRouter, claude-sonnet-4-20250514 for Anthropic, gemini-2.0-flash for Google AI, gpt-4o-mini for OpenAI, llama3.1 for Ollama).

## `openrouterApiKey` (type: `string`):

OpenRouter API key missing - set OPENROUTER\_API\_KEY env var OR openrouterApiKey input. Get one at https://openrouter.ai/keys.

## `anthropicApiKey` (type: `string`):

Anthropic API key missing - set ANTHROPIC\_API\_KEY env var OR anthropicApiKey input. Get one at https://console.anthropic.com/settings/keys.

## `googleApiKey` (type: `string`):

Google AI API key missing - set GOOGLE\_API\_KEY env var OR googleApiKey input. Get one at https://aistudio.google.com/app/apikey.

## `openaiApiKey` (type: `string`):

OpenAI API key missing - set OPENAI\_API\_KEY env var OR openaiApiKey input. Get one at https://platform.openai.com/api-keys.

## `ollamaBaseUrl` (type: `string`):

Ollama base URL. Set ollamaBaseUrl input or run Ollama locally. Install at https://ollama.com/download.

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

Apify proxy configuration. Shopify stores generally work without residential proxies - datacenter proxies are sufficient.

## Actor input object example

```json
{
  "storeUrls": [
    "https://allbirds.com"
  ],
  "maxProducts": 50,
  "includeVariants": true,
  "includeImages": true,
  "includeCollections": false,
  "maxCollections": 100,
  "discountedOnly": false,
  "fetchCollectionProducts": false,
  "maxProductsPerCollection": 250,
  "watchMode": false,
  "alertMinDropPct": 5,
  "enableAiAnalysis": false,
  "llmProvider": "openrouter",
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Buyer-facing Shopify product rows with variants, images, prices, stock signals, discounts, and optional price\_watch blocks.

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

Collection rows from /collections.json, including optional discount\_stats when product-collection graph mode is enabled.

## `alerts` (type: `string`):

Price-drop webhook dispatch summaries emitted by watch mode.

## `analysis` (type: `string`):

Optional AI catalog-analysis records.

## `diagnostics` (type: `string`):

No-result and operational diagnostic records.

# 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://allbirds.com"
    ],
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("harvestlab/shopify-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://allbirds.com"],
    "ollamaBaseUrl": "http://localhost:11434",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("harvestlab/shopify-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://allbirds.com"
  ],
  "ollamaBaseUrl": "http://localhost:11434",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call harvestlab/shopify-scraper --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Shopify Product Scraper - Prices, Variants & Stock",
        "description": "Shopify product scraper for public stores: export products, prices, variants, SKUs, images, collections, stock signals, discounts, and webhook/MCP price-drop alerts. Built for catalog exports, competitor tracking, and scheduled monitoring.",
        "version": "1.8",
        "x-build-id": "TAlfnODhoYoxNsPpt"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/harvestlab~shopify-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-harvestlab-shopify-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/harvestlab~shopify-scraper/runs": {
            "post": {
                "operationId": "runs-sync-harvestlab-shopify-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/harvestlab~shopify-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-harvestlab-shopify-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "storeUrls": {
                        "title": "Shopify Store URLs",
                        "type": "array",
                        "description": "List of Shopify store URLs to scrape (e.g. https://allbirds.com, https://kith.com). The actor auto-detects the /products.json endpoint. Accepts aliases: 'shopUrls' (array), 'shopUrl'/'storeUrl'/'url' (single URL).",
                        "items": {
                            "type": "string"
                        }
                    },
                    "shopUrl": {
                        "title": "Single Store URL (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for storeUrls (single URL). Hidden from Console form."
                    },
                    "storeUrl": {
                        "title": "Store URL (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for shopUrl/storeUrls (single URL). Hidden from Console form."
                    },
                    "url": {
                        "title": "URL (CLI alias)",
                        "type": "string",
                        "description": "CLI alias for shopUrl/storeUrls (single URL). Hidden from Console form."
                    },
                    "shopUrls": {
                        "title": "Shop URLs (CLI alias)",
                        "type": "array",
                        "description": "CLI alias for storeUrls (array of URLs). Hidden from Console form.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "maxProducts": {
                        "title": "Max Products Per Store",
                        "minimum": 1,
                        "maximum": 500,
                        "type": "integer",
                        "description": "Maximum number of products to scrape per store (1-500). Shopify stores can have thousands of products - start with 50 to test. Accepts alias: 'maxItems'.",
                        "default": 50
                    },
                    "maxItems": {
                        "title": "Max Items (CLI alias)",
                        "minimum": 1,
                        "maximum": 1000,
                        "type": "integer",
                        "description": "CLI alias for maxProducts. Hidden from Console form."
                    },
                    "includeVariants": {
                        "title": "Include Variants",
                        "type": "boolean",
                        "description": "Include all product variants (sizes, colors, etc.) with individual pricing and inventory data. Recommended for price monitoring.",
                        "default": true
                    },
                    "includeImages": {
                        "title": "Include Images",
                        "type": "boolean",
                        "description": "Include product image URLs in the output (primary image plus gallery images).",
                        "default": true
                    },
                    "includeCollections": {
                        "title": "Include Collections",
                        "type": "boolean",
                        "description": "Also scrape the store's public /collections.json endpoint. Each collection (title, handle, description, products_count, image) is emitted as a dataset item with type='collection'. Useful for category-mapping and navigation analysis.",
                        "default": false
                    },
                    "maxCollections": {
                        "title": "Max Collections Per Store",
                        "minimum": 1,
                        "maximum": 250,
                        "type": "integer",
                        "description": "Maximum number of collections to scrape per store when includeCollections=true (1-250). Shopify paginates at 250 per page.",
                        "default": 100
                    },
                    "discountedOnly": {
                        "title": "Discounted Products Only",
                        "type": "boolean",
                        "description": "If true, only emit products that have at least one variant on sale (compare_at_price > price). Useful for bargain-hunting, reseller arbitrage, and sale-tracking workflows.",
                        "default": false
                    },
                    "fetchCollectionProducts": {
                        "title": "Build Product-to-Collection Graph",
                        "type": "boolean",
                        "description": "If true, walk each collection's /collections/{handle}/products.json endpoint to build a product-to-collection membership graph. Every product gets a 'collection_memberships' array ({collection_handle, collection_title, position}) and 'collection_count'. Every collection gets a 'discount_stats' block (per-collection discount heatmap). When AI analysis is enabled, adds 'collection_discount_heatmap' and 'merchandising_signals' to the output. Auto-enables includeCollections. Cost: one extra HTTP per collection - no existing Apify Shopify competitor exposes this graph.",
                        "default": false
                    },
                    "maxProductsPerCollection": {
                        "title": "Max Products Per Collection (graph mode)",
                        "minimum": 1,
                        "maximum": 1000,
                        "type": "integer",
                        "description": "When fetchCollectionProducts=true, cap the number of products fetched per individual collection (1-1000). Default 250 covers most mid-size collections in a single API call.",
                        "default": 250
                    },
                    "watchMode": {
                        "title": "Price-Watch Mode (track price changes across runs)",
                        "type": "boolean",
                        "description": "Persist each product's min/max/avg price across runs in a named Apify key-value store (shopify-price-history). Every product receives a `price_watch` block with {price_previous, price_now, price_delta_pct, direction (up/down/flat), price_drop, first_seen_at, history_sample_count}. 60-snapshot FIFO per product (~2 months at daily cadence). Bills the `price-drop-detected` event ($0.005) when a product's price falls by >= alertMinDropPct (default 5%). First-run products emit direction='new' and are not billed.",
                        "default": false
                    },
                    "alertWebhookUrl": {
                        "title": "Alert Webhook URL (POST on price drop)",
                        "type": "string",
                        "description": "HTTPS webhook URL that receives a JSON POST for every product whose price falls >= alertMinDropPct. Payload: {store_domain, product_id, title, handle, vendor, price_previous, price_now, price_delta_pct, direction, currency, on_sale, url}. Works with Slack Incoming Webhooks, Zapier, Make/Integromat, n8n, or any HTTP endpoint. Fire-and-forget with 5s timeout + 1 retry - webhook failures never fail the scraper. Setting this auto-enables watchMode. Bills `alert-dispatched` ($0.002) per successfully delivered webhook."
                    },
                    "outputConnectors": {
                        "title": "Output Connectors (MCP price-drop alerts)",
                        "minItems": 0,
                        "maxItems": 5,
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Optional MCP connectors that receive price-drop alerts without exposing third-party credentials to this actor. Select Slack, Notion, Supabase, GitHub, or another MCP-compatible destination with output-writing tools. Setting this auto-enables watchMode. Bills `connector-alert-dispatched` ($0.002) per successful MCP tool call."
                    },
                    "connectorAlertTarget": {
                        "title": "Connector Alert Target",
                        "type": "string",
                        "description": "Optional target passed to MCP output tools. For Slack, use a channel such as #price-alerts or a channel ID. For database/page connectors, use the destination identifier expected by that connector."
                    },
                    "alertMinDropPct": {
                        "title": "Alert Minimum Drop %",
                        "minimum": 1,
                        "maximum": 90,
                        "type": "integer",
                        "description": "Minimum price drop (in percent) required to fire a webhook and bill price-drop-detected. Default 5% - fires when a product's min_price falls at least 5% vs. its last snapshot. Set higher (e.g. 10) for fewer, bigger alerts, or lower (e.g. 2) to catch smaller moves. Requires watchMode=true.",
                        "default": 5
                    },
                    "enableAiAnalysis": {
                        "title": "Enable AI Analysis",
                        "type": "boolean",
                        "description": "Run AI-powered analysis on the store's product catalog. Provides pricing strategy insights, product gap analysis, and competitive positioning. Requires an API key.",
                        "default": false
                    },
                    "llmProvider": {
                        "title": "LLM Provider",
                        "enum": [
                            "openrouter",
                            "anthropic",
                            "google",
                            "openai",
                            "ollama"
                        ],
                        "type": "string",
                        "description": "Choose the LLM provider for AI analysis. OpenRouter is cheapest.",
                        "default": "openrouter"
                    },
                    "llmModel": {
                        "title": "LLM Model Override",
                        "type": "string",
                        "description": "Specific model to use. Leave empty for the provider default (google/gemini-2.0-flash-001 for OpenRouter, claude-sonnet-4-20250514 for Anthropic, gemini-2.0-flash for Google AI, gpt-4o-mini for OpenAI, llama3.1 for Ollama)."
                    },
                    "openrouterApiKey": {
                        "title": "OpenRouter API Key",
                        "type": "string",
                        "description": "OpenRouter API key missing - set OPENROUTER_API_KEY env var OR openrouterApiKey input. Get one at https://openrouter.ai/keys."
                    },
                    "anthropicApiKey": {
                        "title": "Anthropic API Key",
                        "type": "string",
                        "description": "Anthropic API key missing - set ANTHROPIC_API_KEY env var OR anthropicApiKey input. Get one at https://console.anthropic.com/settings/keys."
                    },
                    "googleApiKey": {
                        "title": "Google AI API Key",
                        "type": "string",
                        "description": "Google AI API key missing - set GOOGLE_API_KEY env var OR googleApiKey input. Get one at https://aistudio.google.com/app/apikey."
                    },
                    "openaiApiKey": {
                        "title": "OpenAI API Key",
                        "type": "string",
                        "description": "OpenAI API key missing - set OPENAI_API_KEY env var OR openaiApiKey input. Get one at https://platform.openai.com/api-keys."
                    },
                    "ollamaBaseUrl": {
                        "title": "Ollama Base URL",
                        "type": "string",
                        "description": "Ollama base URL. Set ollamaBaseUrl input or run Ollama locally. Install at https://ollama.com/download."
                    },
                    "proxyConfiguration": {
                        "title": "Proxy Configuration",
                        "type": "object",
                        "description": "Apify proxy configuration. Shopify stores generally work without residential proxies - datacenter proxies are sufficient."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
