# Shopify Price Tracker | $0.80/1K | Exact Variants (`x_guru/shopify-price-stock-monitor`) Actor

Shopify price tracker for exact sizes, colors and SKUs. Compare competitor prices and stock in the same market and currency. Keep price history, detect sales and restocks, and monitor product, collection or catalog URLs. From $0.80/1K product-check units on paid Apify plans.

- **URL**: https://apify.com/x_guru/shopify-price-stock-monitor.md
- **Developed by:** [Hundevmode Labs](https://apify.com/x_guru) (community)
- **Categories:** E-commerce, Automation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 paid plans: product check (100 variants / 20 output rows)s

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 Price Tracker & Stock Monitor

Shopify Price Tracker & Stock Monitor tracks competitor prices and availability for exact product variants: a size, color, SKU or configuration. Monitor product URLs, collections or public Shopify catalogs, keep price history between scheduled runs, and compare explicitly linked offers in the same verified market and currency.

Product checks start at **$0.80 per 1,000 check units on paid Apify plans**. Collection and catalog checks have separate pricing. There is no extra Actor subscription. See [pricing and check units](#pricing-and-check-units) before running a large watchlist.

**On this page:** [Quick start](#quick-start-track-one-shopify-variant) · [Inputs](#input-product-urls-variants-collections-and-stores) · [CSV import and preview](#import-a-shopify-watchlist-from-excel-or-google-sheets) · [Competitor comparisons](#compare-exact-competitor-variants-in-the-same-market) · [Output](#output-prices-stock-history-and-change-alerts) · [History and alerts](#shopify-price-history-stock-monitoring-and-restock-alerts) · [Pricing](#pricing-and-check-units) · [Schedules and API](#schedule-checks-or-use-the-shopify-price-monitoring-api) · [FAQ](#frequently-asked-questions)

### What can you track?

- **Exact variants:** monitor the price and availability of the size, color or pack you actually sell.
- **Competitor offers:** compare linked equivalent variants, identify the cheapest eligible offer and see whether a competitor undercuts your own price.
- **Restocks and sales:** detect price increases, price drops, sale changes, sold-out variants and backorders.
- **Observed price history:** retain previous snapshots and report observed 30-day price ranges and sample counts.
- **Categories and catalogs:** watch a collection or store, including products leaving a category.
- **Source health:** distinguish a confirmed disappearance from a blocked, incomplete or unsuccessful check. Failed checks preserve the last successful history.

#### Choose your first workflow

| You want to… | Start here | What to expect |
| --- | --- | --- |
| Watch one size, color or pack | Paste its `?variant=ID` product link; keep **Track price and stock changes** | First run: baseline and current variant. Later runs: changes and source health |
| Find the right variant first | Paste a plain product link; choose **Export / discover variants** and **One row per variant** | Current prices, SKUs, option names and exact variant links; no monitor history writes |
| Watch a category or catalog | Paste a clean collection URL or store root | All public products in that scope, subject to coverage and spending limits |

**For a first try, use one product.** A store domain requests the entire public catalog. Keep optional settings unchanged until you have checked the first result.

**Alerts are data records.** The Actor does not send email, Telegram or Slack messages itself. Use [the notification workflow](#connect-price-and-stock-events-to-notifications) to connect your chosen destination.

### Quick start: track one Shopify variant

#### Use the form without editing JSON

1. In **Shopify URLs**, paste your product, collection or store links, one per entry. The example link is prefilled so you can try a first check immediately.

2. To watch one size or color, use its product link with `?variant=123`, replacing `123` with the real variant ID. A plain product link watches all variants. Copy the address after choosing the desired variant, or use the optional **Exact SKUs** / **Exact size, color or pack** fields below. If the storefront does not expose a selected variant in its URL, follow [the discovery steps](#how-to-find-a-shopify-variant-id-sku-or-option-name).

3. For a specific market, enter **Market country**, such as `US`, and optionally **Expected currency**, such as `USD`. Leave them empty only if the store's returned market is appropriate for your watchlist. Setting currency alone does not switch the market or convert prices. For a localized link, explicitly set country or **Storefront language** so the requested context can be checked.

4. Keep **Track price and stock changes** selected and run once. Save the input as a Task and add a schedule for repeat checks.

After the run, open **Readable change and source-health report** in the Output tab for the digest and next steps. Open **Source coverage** for the detailed verification records. `BASELINE` means the first complete selection was saved; it is normal to have no price-change events yet. Then open the observations to check that the size, SKU, country and currency are the ones you intended. If you see `NO_MATCH`, review the variant suggestions before scheduling.

The other settings are grouped into exact selectors, results, alerts, competitor matching, history and advanced limits. Defaults are suitable for a first product check. This equivalent API input selects one variant without a detailed target object:

```json
{
  "urls": ["https://www.deathwishcoffee.com/products/valhalla-java-single-serve-pods?variant=392715452"],
  "country": "US",
  "expectedCurrency": "USD",
  "monitorId": "coffee-watch"
}
```

#### How to find a Shopify variant ID, SKU or option name

1. Paste the **plain product URL**, without `variant` or `option_values`, into **Shopify URLs**. Clear the optional SKU and named-option selectors.

2. Choose **Export / discover variants (no history writes)**, then **One row per variant (flat CSV)** under Results.

3. Run once and open **Variant observations**. Each `observation` includes `variantId`, `sku`, `selectedOptions`, price, availability and a direct variant `url`. Check the exact size, color and pack; sold-out variants are included.

4. Copy the desired `url` into **Shopify URLs**, switch back to **Track price and stock changes**, and save a scheduled Task. Keep the same Task for repeat checks.

Discovery uses normal check pricing. It does not update monitor history, and a large flat export can require additional units. Start with one product rather than an entire store.

#### Select an exact SKU, size or color in the simple form

You can keep using **Shopify URLs**. Enter exact merchant SKUs in **Exact SKUs**, one per entry, or a small named-option object such as `{"Size":"10 count"}` in **Exact size, color or pack**. These optional fields apply to all supplied URLs. Use the names and values from discovery; the Actor does not guess translations, size equivalents or similar SKUs.

```json
{
  "urls": ["https://www.deathwishcoffee.com/products/valhalla-java-single-serve-pods"],
  "country": "US",
  "expectedCurrency": "USD",
  "options": {"Size": "10 count"},
  "monitorId": "coffee-size-watch"
}
```

SKU, option and URL variant restrictions are combined. For example, a URL for size M plus `{"Size":"L"}` produces `NO_MATCH`, rather than silently monitoring size L. If nothing matches a complete source, **Source coverage** includes up to five variant examples and copyable links for review. The Actor never changes your selection automatically.

#### Set different selectors per store with Advanced targets

1. Clear **Shopify URLs** and use **Advanced targets** to select a variant by its ID, SKU or named options. Each target can define its own market and currency.
2. Run the Actor. The first successful check creates a baseline and returns the selected variant; it does not label the baseline as a new-product alert.
3. Save the input as an Apify Task and schedule repeat runs. Changes are compared with the previous successful check automatically.

This example selects a 10-count coffee pack in the US market. The merchant can change its product options; adapt the selection if needed.

```json
{
  "targets": [{
    "targetId": "coffee-us",
    "url": "https://www.deathwishcoffee.com/products/valhalla-java-single-serve-pods",
    "country": "US",
    "expectedCurrency": "USD",
    "options": {"Size": "10 count"}
  }],
  "mode": "monitor",
  "monitorId": "coffee-watch",
  "outputMode": "changesOnly"
}
```

With the default `emitInitialSnapshot: true`, the first run includes an observation, source status and check receipt. Repeat runs return changes, source statuses and receipts; unchanged variants do not generate price alerts. Use `outputMode: "all"` to export current observations on every check, or disable `emitInitialSnapshot` for a silent first baseline.

Task IDs isolate history automatically when `monitorId` is omitted. Ad hoc runs use the account's default monitor. Use a separate `monitorId` for each client or independent watchlist.

### Shopify competitor price monitoring: common use cases

| Goal | How to use the Actor |
| --- | --- |
| Watch a competitor's price for your exact size or SKU | Select the variant on a product URL and schedule repeat checks |
| Know when a competing offer becomes available | Watch availability events, with separate backorder states |
| Compare your price with equivalent competing offers | Link one variant per store in a `comparisonGroup`; mark your offer with `role: "own"` |
| Follow a category's assortment | Monitor a collection URL and distinguish collection departures from market unavailability |
| Export current prices for analysis | Run `mode: "snapshot"` with `outputMode: "all"`; download Dataset JSON or CSV |
| Build an alert workflow | Filter `change` records and deduplicate by `eventId`; route source errors separately |

### Import a Shopify watchlist from Excel or Google Sheets

Export your spreadsheet as CSV and paste its text into **Import a CSV watchlist**. Clear **Shopify URLs** (including the prefilled example) and **Advanced targets** first. The actor accepts one watchlist format at a time.

```csv
url,variantId,country,expectedCurrency
https://www.deathwishcoffee.com/products/valhalla-java-single-serve-pods,392715452,US,USD
```

Use the real product and selector values from your watchlist. CSV supports comma, semicolon or tab delimiters, UTF-8 BOM, quoted cells and up to **100 targets / 128 KiB**. A downloadable one-row **CSV watchlist template** is also included in each run's Output tab.

| Column | What to enter |
| --- | --- |
| `url` | Required public product, collection or store URL. Product URLs can already include `?variant=ID`. |
| `variantId` | One exact numeric Shopify variant ID, stored as **text** in your spreadsheet. Must agree with a URL variant if both are present. |
| `sku` | One exact SKU, stored as text to keep leading zeros. |
| `option:Size`, `option:Color`, `option:NAME` | Exact option name after the colon and exact value in the cell. Example: `option:Size` = `M`. At most three options. |
| `options` | Alternative JSON object in a quoted CSV cell. Use this or `option:NAME` columns, not both in one row. |
| `country`, `language`, `expectedCurrency` | Per-row market constraints; blank cells inherit the shared form settings. |
| `targetId`, `productId` | Optional stable label / known numeric product ID. Labels must be unique; product IDs must be text. |
| `comparisonGroup`, `role` | Reviewed equivalent-product group and `own` or `competitor` role. Groups require specific product URLs. |

**Do not let Excel round IDs or convert them into scientific notation.** `3.927E+8` and `392715452.0` are rejected. Unknown columns, conflicting selectors, duplicate scopes and malformed cells cause an input error instead of silently changing what is tracked. Empty selector cells inherit shared settings; without a selector, a product URL selects all its variants.

#### Preview a watchlist before checking stores

Choose **Preview watchlist** in **What do you want to do?**. This works with URLs, CSV or Advanced targets. Review canonical URLs, exact selectors, requested countries and warnings in the `watchlist_preview` rows or **Parsed watchlist** output.

Preview makes **zero requests to stores**, does not create or update monitor history, and incurs **zero product/catalog check units**. The Apify start fee still applies. It validates input structure only: it cannot confirm that a variant exists, that a store supports the market, or what a full check will cost. Switch to **Track price and stock changes** to verify the sources and start monitoring.

The same exact URL/market/selector scope imported from CSV reuses its existing monitor history; changing the format does not reset the baseline. Keep the same Task or `monitorId`.

### Input: product URLs, variants, collections and stores

| Input | Purpose |
| --- | --- |
| `urls` | Simple form: up to 100 public HTTPS links, one per entry. A `?variant=` link selects one variant |
| `targets` | Advanced alternative to `urls`: up to 100 URL strings or target objects with exact selectors/comparison groups. Use one populated list: `urls`, `targets`, or `watchlistCsv` |
| `targetId` | Optional readable target label |
| `country` | Requested market: uppercase code such as `US`. A top-level value applies to all links; per-target values override it. The source must confirm the country |
| `language` | Optional requested language, such as `EN` or `FR`. Top-level values apply to all links; target values override them. The returned context must confirm it |
| `expectedCurrency` | Reject a different returned currency. Top-level values apply to all links; target values override them. No market switching or FX conversion |
| `skus`, `options` | Simple shared selectors at the top level, or individual selectors inside each Advanced target. Target values override shared values |
| `variantIds` | Select exact Shopify variant IDs inside an Advanced target. Numeric IDs are strings and belong to that store |
| `productId` | Optional Shopify product ID for a known product |
| `comparisonGroup`, `role` | Link equivalent offers and optionally identify your own offer |
| `watchlistCsv` | Optional CSV text instead of `urls` / `targets`; see the spreadsheet import example. |
| `mode` | `monitor` retains history; `snapshot` exports current data without history writes; `preview` checks input structure without source requests or history writes |
| `outputMode` | `changesOnly` by default; `all` includes current observations on every successful check |
| `observationLayout` | `product` groups variants compactly by default; `variant` exports one observation per variant |
| `emitInitialSnapshot` | Show the selected variants on the first successful monitor check; enabled by default |
| `monitorId` | Optional stable watchlist ID. Saved Tasks isolate history automatically when this is omitted |
| `minimumPriceChangePercent` | Decimal text such as `"1"` for a 1% cumulative price-alert threshold; default `"0"` |
| `historyLimit` | Retained observations per variant: 90 by default, configurable from 2 to 365 |
| `removalConfirmations` | Consecutive successful absence checks required: 2 by default, configurable to 5 |
| `emitNewLowAlerts`, `includeMetadataChanges` | Optional new-low and metadata alerts; disabled by default |
| `emitMatchCandidates` | Optional GTIN-based suggestions for human review; disabled by default |
| `advanced` | Coverage, request, timeout, pagination and concurrency limits; Free-plan ceilings still apply |

In Advanced targets, `targetId`, `productId`, `variantIds`, `comparisonGroup` and `role` belong inside each target object. Country, language, currency, SKU and named-option defaults can also be set at the top level; a target's own values override those defaults. Empty URL lists are ignored, but supplying more than one nonempty watchlist (`urls`, `targets`, `watchlistCsv`) is rejected to prevent unintended duplicate checks.

A URL containing `?variant=123` selects that variant automatically. Multiple values within one option are OR; separate options and selector types are AND. Availability is never an output filter, so a sold-out variant stays in the tracked set.

#### Which Shopify links should you paste?

The URLs below are placeholders. Replace the domain, handle and IDs with real storefront values.

| Link | What gets checked |
| --- | --- |
| `https://store.example` | The store's entire public catalog, subject to coverage limits |
| `https://store.example/collections/shoes` | All products and variants in that collection, before optional SKU/options restrictions |
| `https://store.example/products/shoe` | Every public variant of that product, before optional SKU/options restrictions |
| `https://store.example/products/shoe?variant=123` | Only variant `123`, including when sold out |
| `https://store.example/products/shoe?option_values=10,20` | The variant matching those option-value IDs, in product-option order, when the source verifies them |
| `https://store.example/collections/shoes/products/shoe?variant=123` | The same exact product variant; the collection prefix is normalized away |
| `https://store.example/fr-fr/products/shoe?variant=123` | A localized product link; explicitly set country and/or language for verification |

Shopify documents both `variant` and `option_values` deep links. **Option-value IDs differ from variant IDs:** include one ID for every product option, in its option order. An incomplete option set cannot establish a selected variant. Unknown IDs or an impossible combination return `NO_MATCH` when the source is complete; the Actor does not substitute the storefront's default variant. If the public source cannot map option-value IDs, the check is unverifiable. See [Shopify variant deep-link documentation](https://shopify.dev/docs/storefronts/themes/product-merchandising/variants).

**Check the URL before saving a Task:** collection `filter.*` parameters, tagged collections, search pages, `?selling_plan=` subscription offers, cart/checkout links and unknown selection parameters are rejected with an explanation. They can imply a different selection or price. Copy a clean supported URL only if that is the scope you intend. A conflicting URL variant and Advanced `variantIds` is rejected as well.

Known tracking parameters such as `utm_source`, `gclid` and `fbclid` are ignored. Ordinary HTTP links are upgraded to HTTPS. `page`, `sort_by` and `limit` do not restrict coverage: the Actor checks the entire target and reports a warning. A sorted collection page 2 is not a watchlist for that page alone. Store and collection links cannot use product `variant` selectors.

Shopify's `/collections/all` catalog page can be virtual or merchant-customized. If an actual public collection with handle `all` exists, the Actor checks that collection. If the API does not expose it, the check returns `SOURCE_UNVERIFIABLE` with `collection_all_not_exposed_use_store_root_for_entire_catalog`. Paste the store root instead **if you intend to monitor its entire public catalog**. The Actor does not replace a possibly customized category silently. See [Shopify catalog-page documentation](https://help.shopify.com/en/manual/online-store/themes/customizing-themes/common-customizations/change-catalog-page).

Adding or reordering targets preserves existing target histories. Changing a target's URL, market, language, expected currency, product ID or selectors creates a new target baseline. After a product is observed, its ID is used on repeat checks, so a renamed handle does not silently lose it.

#### Monitor a Shopify collection or whole catalog

Use a collection URL such as `https://store.example/collections/shoes` or the store domain `https://store.example`. These are placeholders; replace them with public Shopify URLs.

```json
{
  "targets": [{
    "targetId": "shoes-us",
    "url": "https://store.example/collections/shoes",
    "country": "US",
    "expectedCurrency": "USD"
  }],
  "monitorId": "category-watch",
  "outputMode": "all"
}
```

Every product and variant page must complete. A cap, failed page, repeated cursor or duplicate ID produces a partial/failed status and preserves history. When a product leaves a collection, the Actor checks its product ID in the same market: `LEFT_COLLECTION` means the product still exists outside that category.

### Compare exact competitor variants in the same market

Link products only after checking that they represent the same model, size, color, quantity, condition and purchasing terms. Matching titles or SKU strings alone do not establish equivalence across stores.

```json
{
  "targets": [
    {
      "targetId": "own-store",
      "url": "https://retailer-a.example/products/the-same-model",
      "country": "US",
      "expectedCurrency": "USD",
      "options": {"Size": "42", "Color": "Black"},
      "role": "own",
      "comparisonGroup": "model-black-42"
    },
    {
      "targetId": "competitor",
      "url": "https://retailer-b.example/products/the-same-model",
      "country": "US",
      "expectedCurrency": "USD",
      "variantIds": ["123456789"],
      "comparisonGroup": "model-black-42"
    }
  ],
  "monitorId": "competitor-watch"
}
```

The retailer URLs and variant ID are placeholders. Each group needs at least two targets, and each target must resolve to exactly one variant. At most one member can have `role: "own"`. Ranking requires complete successful checks with the same applied country and currency. Mixed currencies, unknown markets, ambiguous selections and failed sources return an explicit comparison status without a best-price recommendation.

A valid comparison includes all offers, availability, the best eligible price, ties and price gaps. With an own offer, it also reports the cheapest eligible competitor, absolute/percentage gaps and signals such as `COMPETITOR_UNDERCUTS_OWN_PRICE` and `OWN_UNAVAILABLE_COMPETITOR_AVAILABLE`. Backorders remain visible and are excluded from ranking by default; enable `includeBackordersInComparison` if appropriate.

Prices are public unit prices. Checkout discounts, shipping, duties and tax adjustments are not calculated, and currencies are not converted. The Actor does not automatically change your selling price or calculate profit margins.

#### Find candidate matches by GTIN

Enable `emitMatchCandidates` to find cross-store variants sharing a checksum-valid GTIN in the same verified country and currency. UPC/EAN formats normalize to 14 digits. Suggestions include available product/options/pack details and `proposedTargets` to copy after review. Reused barcodes within a store are marked ambiguous.

Candidates are never automatically ranked: seller barcodes can be incorrect, and equivalent model, pack and terms still require review. The default limit is 100 suggestions. No barcode match does not prove that no equivalent product exists.

### Output: prices, stock, history and change alerts

Results are stored in the Apify Dataset. The following is a shortened observation example; values illustrate the fields and are not a current price guarantee.

```json
{
  "recordType": "observation",
  "targetId": "coffee-us",
  "productId": "171104582",
  "variantId": "392715452",
  "productTitle": "Valhalla Java Single-Serve Pods",
  "sku": "5094SSC",
  "selectedOptions": {"Size": "10 count"},
  "price": "14.99",
  "compareAtPrice": null,
  "onSale": false,
  "currency": "USD",
  "appliedCountry": "US",
  "availableForSale": true,
  "availability": "IN_STOCK",
  "backorder": false,
  "lowestObservedPrice30d": "14.99",
  "highestObservedPrice30d": "14.99",
  "observations30d": 1,
  "url": "https://www.deathwishcoffee.com/products/valhalla-java-single-serve-pods?variant=392715452"
}
```

Prices use decimal strings. Each variant retains its ID, selected options, availability and history. With the default compact layout, multiple variants are grouped inside `product_observation.variants`. One selected variant on a product URL stays a flat `observation`. Large arrays are split into bounded batches with `variantBatchIndex` and `variantBatchCount`; variants are not dropped. Change records are always flat.

| `recordType` | Contents |
| --- | --- |
| `observation` | Current values for a selected variant |
| `product_observation` | Product with its selected variants and their individual history fields |
| `change` | Event type, stable `eventId`, before/after values, old/new prices and a direct variant URL |
| `source_status` | Coverage, completeness, market, errors and whether the baseline advanced |
| `comparison` | Linked offers, comparison status, best eligible price and own-offer signals |
| `check_receipt` | Successfully billed target, check units and requested fees, including quiet checks |
| `match_candidate` | Optional reviewable GTIN match suggestions |

#### Read the run digest and source health

Every completed scan writes **REPORT** (JSON) and **REPORT-MARKDOWN** (readable Markdown) into the run's default key-value store. Open them directly from the Output tab.

- **Change counts** cover unique delivered event IDs, including price drops/increases, stock events and competitor-position changes.
- **Examples** show the product, exact variant, before/after price or availability, and market. The digest contains up to **50 event examples**; `digestTruncated` and `omittedEventCount` explicitly tell you when more events remain in the full paginated dataset.
- **Source issues** explain failed/incomplete checks, empty selections and pending disappearance confirmations, with a suggested next step. These are health notices, not confirmed product-removal events.
- **Comparison issues** identify groups that cannot be ranked safely.
- **`shouldNotify`** is false for a healthy baseline or quiet check, and true when changes or issues need attention. **`deliveryId`** identifies the whole run report; event consumers use each event's **`eventId`**.

The report summarizes rows already delivered to the dataset, including rows delivered before a run migration. Report generation adds no separately charged product/catalog event. Startup and normal check charges still apply. If the process fails before a report is written, inspect the run status/log; the notification template handles failed runs without a report separately.

A source failure preserves that source's prior history. A partially successful run can still advance history for other fully verified, billed targets. The digest does not turn a blocked storefront into a deletion alert.

#### Shopify product scraping for current prices and availability

For a current price export, use `mode: "snapshot"` and `outputMode: "all"`. This reads supported public product data without updating monitor history. Use the compact JSON layout for large catalogs. A flat export with `observationLayout: "variant"` is convenient for spreadsheets but can increase invoice units; see [pricing](#pricing-and-check-units).

Dataset Console views select columns; they do not filter record types. Filter records explicitly in your integration. Download JSON for nested variants or choose a flat layout for variant CSV rows. The local `tools/export_variants.py` helper can flatten downloaded compact JSON without another paid Actor run.

### Shopify price history, stock monitoring and restock alerts

Each retained observation records price and availability. Observation fields include `firstSeenAt`, `lastPriceChangeAt`, `historyStartedAt`, observed 30-day low/high prices and sample count. These describe actual retained samples; they do not prove continuous observation or a legally certified reference price.

Retention defaults to 90 observations per variant, up to 365. At hourly schedules, 90 samples cover fewer than 30 days. Inspect the history start and sample count before interpreting a 30-day figure. Removed variant histories are retained for up to 30 days to detect reappearance.

Price thresholds accumulate from the last price alert: successive small moves can cross the configured percentage. Quiet checks still advance observed history. Optional new-low alerts work independently of the percentage threshold.

For Shopify price change alerts, set **Price change alert threshold (%)** to `0` for any price move or `1` for a cumulative 1% change; omit the percent sign. Availability events do not depend on this price threshold.

| Events | Meaning |
| --- | --- |
| `PRICE_DECREASE`, `PRICE_INCREASE` | Price crossed the configured threshold since its last price alert |
| `SOLD_OUT`, `RESTOCKED` | Variant stopped or started accepting purchases, excluding separate backorder transitions |
| `BACKORDER_STARTED`, `BACKORDER_ENDED`, `BACKORDER_AVAILABLE` | Purchasability and physical stock state changed with a backorder distinction |
| `SALE_STARTED`, `SALE_ENDED`, `SALE_REFERENCE_CHANGED` | Selling/compare-at price relationship changed |
| `VARIANT_ADDED`, `VARIANT_REAPPEARED` | Matching variant appeared or returned |
| `VARIANT_REMOVED` | Product remains present but its variant is absent after confirmation |
| `PRODUCT_UNAVAILABLE_IN_MARKET` | Product is absent from the verified public market after confirmation |
| `LEFT_COLLECTION` | Product left the monitored category but still exists in that market |
| `NO_LONGER_MATCHES` | Existing variant no longer matches the selected SKU/options |
| `BEST_OFFER_CHANGED`, `COMPETITIVE_POSITION_CHANGED` | Verified best offer or your own offer's undercut/availability position changed |

Disappearances require two consecutive successful absence checks by default. Failed and partial runs do not count. Empty or sharply reduced catalogs are checked again within the run before counting toward disappearance. If verification disagrees or fails, history stays intact. Public-market absence is not an assertion that a merchant deleted the product in Shopify Admin.

### Pricing and check units

| Target type | Paid Apify plans | Free Apify plan | One check unit covers |
| --- | --- | --- | --- |
| Specific product URL | **$0.0008 ($0.80 / 1,000 units)** | **$0.001 ($1 / 1,000 units)** | Up to 100 observed variants and 20 delivered product/change rows |
| Collection or whole catalog | **$0.01 / unit** | **$0.02 / unit** | Up to 500 current/previous product IDs, 5,000 observed variants and 500 delivered product/change rows |

Unit count is the **largest rounded-up bucket count**, not the sum. A product with 101–200 observed variants uses two product units. A flat 100-variant export uses five units because it writes 100 observation rows. A complete compact catalog with 692 products and 7,454 observed variants uses two catalog units ($0.02 on a paid plan); its 7,454-row flat export uses 15 ($0.15). All variants must still paginate completely.

Check fees include nonmatching products/variants scanned and successful absence checks for previously known IDs. Verified empty targets cost one unit. Separate selections of the same product are independent checks, even when they share an HTTP read; select multiple desired option values in one target when practical. Baselines and unchanged checks are billed.

History, comparisons, match suggestions and alerts have no separate event fees, but delivered product/change rows participate in the unit calculation. The automatic start fee is **$0.00005** at the default 256 MB allocation: minimum one event, then one per allocated GB. Apify charges it on starts, including failed runs and resurrection. Resuming the same run does not invoice already completed check units again.

For **100 ordinary one-unit product targets checked daily for 30 days**, the paid-plan event total is **$2.4015**, including 30 starts. A 15-product category normally uses one catalog unit, so its Free-plan total is $0.02005 including the start. There is no extra Actor subscription; any Apify plan subscription is separate. Free means the account tier, not a zero Actor price: checks consume its available credits.

Failed source verification, incomplete checks and budget-skipped targets have no check fee. Verified work already delivered remains billed if a later storage write or run step fails. Runtime platform usage is included; post-run storage access can still incur normal Apify platform charges.

Set a sufficient `maxTotalChargeUsd` in Apify's run options. The configured minimum is $0.00105, sufficient for one Free-plan product unit and one start. A whole target must fit the remaining budget and allowance before its check fee or history write; collection/catalog checks require a larger budget.

#### Free-plan limits

Free accounts have a shared limit of **100 invoice units per run** across product and catalog events. Additional ceilings are 100 products and 3,000 observed variants per target, 1,000 variants per product, 80 HTTP requests and 180 seconds per run. Input cannot raise them. Resurrection shares the same allowance; a fresh run resets it. There is no Actor-level monthly free quota.

### Schedule checks or use the Shopify price monitoring API

Save a configured Task in Apify Console and attach a schedule. Keep the same Task or `monitorId` for repeat checks. History is scoped by Actor, account and monitor in restricted storage. Overlapping runs of the same monitor are prevented by a Cloud lease.

To start an asynchronous run with the Apify API, save a valid input example as `input.json` and use an API token from your environment:

```bash
curl --request POST \
  'https://api.apify.com/v2/acts/bsyo7tfeD9bzJc38x/runs?maxTotalChargeUsd=0.10' \
  --header "Authorization: Bearer $APIFY_TOKEN" \
  --header 'Content-Type: application/json' \
  --data-binary @input.json
```

The response contains the run ID and Dataset ID. Wait for a successful run, then fetch Dataset items through the API or export them in Console. Paginate large datasets. See [Apify API run documentation](https://docs.apify.com/api/v2/act-runs-post) and [Dataset items](https://docs.apify.com/api/v2/dataset-items-get).

For email, Slack, n8n or another alert destination, connect your own workflow and filter `recordType == "change"`. Send failed/partial `source_status` records to a separate health channel. The Actor returns data; it does not send messages or configure integrations for you.

Use `eventId` as an upsert/deduplication key. Delivery is at least once: interrupted delivery can replay an event with the same ID. Completed target checkpoints are reused after Actor migration; start a fresh run to retry failed sources. `SUMMARY` reports coverage and request counts, while `BILLING` records confirmed charges. A run with no verified source fails explicitly rather than succeeding with an unexplained empty result.

#### Connect price and stock events to notifications

Use an Apify run-finished integration/webhook and a workflow such as n8n or Make. No destination is configured by this Actor.

1. Save the monitor input as a Task. Add a daily or hourly schedule according to how often you need observations. Changes between scheduled checks can be missed; this is polling, not continuous real-time monitoring.

2. Trigger your workflow after a successful Actor run. Fetch that finished run's Dataset and paginate the response when needed. A successful run can still contain a failed individual source, so inspect source statuses too.

3. Keep rows with `recordType: "change"`. For a simple price/restock feed, select `PRICE_DECREASE`, `PRICE_INCREASE`, `SOLD_OUT` and `RESTOCKED`. Add sale/backorder events if relevant. Use `minimumPriceChangePercent: "1"` to reduce small price-move noise.

4. Deduplicate on `eventId`. Build the notification from product title, selected options, market/currency, `oldPrice` → `newPrice` or `oldAvailability` → `newAvailability`, and the exact variant URL. A stock event can have identical old/new prices.

5. Route incomplete or failed `source_status` rows separately. A failed run also needs a health notification. A quiet successful run with no change rows should not generate a price-change message.

For larger lists, collect these events into a daily digest rather than sending one message for every row. The Actor supplies a run digest in REPORT. Your workflow can forward it or build a daily digest across multiple runs. See [Apify webhook documentation](https://docs.apify.com/platform/integrations/webhooks).

#### Download and connect the n8n Slack digest template

In any run's Output tab, download **n8n notification template** as JSON and import it into n8n. It is **inactive**, contains no tokens or account credentials, and schedules no checks by itself. You connect your own accounts and choose when to publish it.

1. On **Apify terminal run**, select a Header Auth credential. Add the same secret header to the Apify webhook's **Headers template**. Keep the default Apify webhook payload.

2. On both HTTP Request nodes, select the same Apify API credential with access to your own runs and their output. The template uses **Simplified Custom Auth** (`httpTemplatedCustomAuth`): configure its authentication template as `{"headers":{"Authorization":"Bearer {{api_key}}"}}` and enter your token in the credential's secret field. Do not paste tokens into the workflow code, report, query strings or Actor input. Older n8n installations may require an update or an equivalent Header Auth credential.

3. In **Configure and validate event**, enter your Slack channel ID. The expected Actor ID is preconfigured. On **Send Slack digest**, select your Slack credential and invite its bot to that channel. Choose your own destination; the template does not assume an account or channel.

4. Configure the Apify Task's webhook for `ACTOR.RUN.SUCCEEDED`, `ACTOR.RUN.FAILED`, `ACTOR.RUN.TIMED_OUT` and `ACTOR.RUN.ABORTED`, using the n8n production webhook URL. The workflow reads the run back from Apify, verifies its Actor/run identity, and then reads that run's report. It does not trust report content supplied in the webhook body.

5. Test with your own small watchlist and destination, then publish the workflow. Healthy baseline/quiet checks stop without a Slack message. Changes and source-health issues produce one digest. A failed run with no report produces a run-failure notice; API/auth failures stop the workflow instead of fabricating healthy data.

**Delivery limits:** this is a small-volume, at-least-once template. It remembers confirmed Slack deliveries using n8n workflow static data, for **30 days / at most 1,000 reports**. Static data persists in production executions; manual tests do not prove persistence. It is unsuitable as a strict exactly-once guarantee under concurrent/high-volume execution. Keep executions sequential for a small watchlist; use a transactional external deduplication store for stronger guarantees. A failure after Slack accepts a message but before acknowledgement can produce a duplicate. Successful delivery is marked only after Slack returns a message timestamp.

The webhook responds after processing. Apify may retry requests that fail or time out. Inspect and retry failed n8n executions; report-read HTTP errors and credentials need attention. If you want n8n failure notifications, configure your own Error Trigger handler. The template does not activate an error workflow or send messages until you connect and publish it. Merchant text is treated as plain text; Slack mentions, markup and link previews are disabled.

For a different notification destination, use the same `REPORT.shouldNotify`, `REPORT.message` and `REPORT.deliveryId` contract with your own email/Telegram/HTTP step. Fetch the paginated dataset if your downstream action needs every event rather than the bounded digest.

References: [Apify webhook actions and duplicate delivery](https://docs.apify.com/integrations/webhooks/actions), [n8n HTTP Request authentication and responses](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.httprequest).

#### Start with a small watchlist and a predictable budget

Five product targets checked once daily for 30 days cost **$0.1215 on paid Apify plans** or **$0.1515 on Free**, including 30 starts at the default memory allocation, if each target uses one product-check unit. Five targets checked every six hours cost **$0.486 paid** or **$0.606 Free**, including 120 starts under the same assumptions.

These are Actor event estimates, not an Apify plan subscription quote. Large variant counts, flat exports, delivered change rows or more allocated memory can increase units/start fees; catalog URLs use catalog pricing. See [the unit rules](#pricing-and-check-units) before scaling.

Check a small set of important comparable variants first. Keep quantity, purchase terms, country and currency aligned. Public unit-price monitoring does not establish the final delivered cost of a bundle, coupon or subscription offer.

### Supported sources and practical limits

The main source is Shopify's public tokenless Storefront API. Applied market and currency are checked against the response. A store that ignores a requested country fails that check explicitly. Product and variant pagination must finish; GraphQL errors, including errors delivered with HTTP 200, do not become empty catalogs.

Ajax product JSON is a fallback only for Storefront endpoint 404/405 responses. It cannot verify an explicit country/language request, and responses with 250 variants cannot prove completeness. Such checks are rejected rather than treated as a complete catalog. See [Shopify's product Ajax API](https://shopify.dev/docs/api/ajax/reference/product).

No store login, Shopify Admin credentials, proxy configuration or cart/checkout modifications are needed. Password-protected, blocked or incompatible/headless stores may fail. Exact inventory quantities are not guaranteed; availability and backorders are reported when exposed by the source.

Paid-plan defaults allow 5,000 products and 50,000 variants per target, 5,000 variants per product, 1,000 HTTP requests, 900 seconds and 50,000 delivered product/change rows per run. Supported `advanced` limits can be adjusted; Free ceilings remain enforced. Changes-only output still requires source checks. Three target reads can overlap by default; each host is rate-paced.

Default memory is 256 MB, configurable up to 1 GB. For large catalogs with long retained histories, allocate 512 MB–1 GB and reduce history retention or selection size if needed. A successful short-history run does not establish capacity for months of history. Network conditions and store compatibility affect speed and coverage.

### Understand quiet results and source errors

Always check `source_status` alongside alerts. A quiet successful check and a failed source mean different things.

| Result | What it means and what to do |
| --- | --- |
| `BASELINE` | First successful check, or a changed target scope. Repeat with the same watchlist to start detecting changes |
| `OK` with no `change` rows | Successful check with no reportable change. Choose **All observations and changes** if you need current variant rows every time |
| `NO_MATCH` | The source loaded completely, but no current variant matched the selection, including on the first check. Review `selectionHints` for available variant examples/links, then check exact selectors and pending absence confirmations |
| `MARKET_UNSUPPORTED` | The returned country/language/currency did not satisfy the request. Verify that the merchant serves that market |
| `ACCESS_RESTRICTED`, `SOURCE_FAILED`, `SOURCE_UNVERIFIABLE` | Source could not be verified. Inspect the error detail and retry later or choose a supported public storefront; previous history is preserved |
| `PARTIAL` | Pagination, coverage, output or another verification step did not complete. Inspect the error; reduce selection size or adjust supported limits. Free ceilings remain enforced |

Source status includes `complete`, `error`, observed/matched variant counts and `baselineUpdated`. `selectionType` identifies the selector used; `resolvedOptions` shows verified names/values for an `option_values` link. When a complete source has no match, `selectionHints` includes at most five examples, the candidate count and a ready-to-use discovery input. These are suggestions for manual review, not replacements. An ambiguous comparison target also gets hints to narrow it to one variant. Failed sources do not generate suggestions from unverified data.

A first complete `NO_MATCH` can save an empty baseline, but does not emit a new-product alert or prove a previously tracked product disappeared. Verified no-match checks are billed because the source was scanned. If a spending limit prevents a whole target from completing, it does not create a removal alert. Comparison statuses and source statuses have separate meanings; a comparison can decline to rank otherwise successful offers because their markets or selections are incompatible.

### Frequently asked questions

#### Can I track Shopify competitor prices without their API keys?

Yes, for supported public storefronts. The Actor uses public Shopify endpoints and does not need the merchant's Admin API key. A blocked, private or incompatible store can still fail explicitly.

#### Can I monitor one size, color or SKU instead of a product's starting price?

Yes. Select the exact variant with a variant URL/ID, SKU or named options. Prices and availability belong to the selected variants, including sold-out variants.

#### How do I find the Shopify variant ID if the URL does not change?

Run one plain product URL in **Export / discover variants** mode with **One row per variant**. Review `sku`, `selectedOptions` and `url` in the observations, then copy the exact variant URL into a monitor Task. A product's displayed starting price is not proof that you selected a particular size.

#### Does it compare competitor prices in different currencies?

It ranks linked offers only in the same verified country and currency. It does not convert FX rates or equate offers from different markets. Set `country` and `expectedCurrency` for the intended comparison.

#### Does it return exact Shopify inventory quantities?

No guaranteed quantity is provided. The Actor tracks purchasability, stock status and backorders when exposed. Public Shopify data does not guarantee a precise warehouse count.

#### Will a failed store check mark its products as removed?

No. Failed and incomplete checks preserve the last successful history. Disappearance alerts require consecutive successful absence checks; collection departures and market unavailability have separate events.

#### Are competitor variants matched automatically?

You explicitly link equivalent variants in comparison groups. Optional GTIN candidates help find possible matches, but require review before ranking. A matching title or barcode alone is not proof of equivalent purchasing terms.

#### Can I use it on a Free Apify plan?

Yes, with Free-plan check prices and enforced coverage/run limits. Checks use available account credits. The advertised $0.80 per 1,000 product units applies to paid Apify plans; Free-plan product units cost $1 per 1,000.

#### Is price history saved automatically for scheduled runs?

Yes, in monitor mode. Reuse the same Task or `monitorId`; the first successful check creates the baseline. Retained sample count determines how much observed history is available.

#### Does it automatically reprice my Shopify store or send alerts?

It returns price/stock changes and comparison signals for your workflow. It does not edit your store, calculate a profitable selling price or send notifications itself.

# Actor input Schema

## `urls` (type: `array`):

<p>Paste <b>one public Shopify link per entry</b>. Start with one product to check compatibility and cost.</p>
<p><b>Choose your scope:</b><br><code>/products/shoe?variant=123</code> — one exact variant.<br><code>/products/shoe</code> — all variants of that product.<br><code>/collections/shoes</code> — that category.<br><code>https://store.example</code> — the whole public catalog.</p>
<p>Replace the example domain, handle and IDs with real values. Complete <code>?option_values=10,20</code> links also work when Shopify confirms their mapping.</p>
<p>For SKU/size filters, use the optional section below. Filtered collection and subscription <code>selling_plan</code> links are rejected with an explanation. Maximum 100 links; whole catalogs can exceed Free limits.</p>
## `country` (type: `string`):

<p>Enter the country where you want to compare offers: <code>US</code>, <code>GB</code>, <code>DE</code> or <code>CA</code>. Use two uppercase letters.</p>
<p>Leave empty to use the market returned by the store. A requested country must be confirmed by Shopify; a different country fails the check.</p>
<p>Applies to every link. An Advanced target can override it. Use the <b>same country and currency</b> when comparing competitors.</p>
## `language` (type: `string`):

<p>Optional storefront language: <code>EN</code>, <code>FR</code>, or another Shopify language code in uppercase.</p>
<p>For localized links such as <code>/fr-fr/products/shoe</code>, explicitly set country or language. The path alone does not verify the market.</p>
<p>Use the language in which the store exposes the exact option names/values you select. Applies to all links unless an Advanced target overrides it.</p>
## `expectedCurrency` (type: `string`):

<p>Enter the currency you expect: <code>USD</code>, <code>GBP</code>, <code>EUR</code> or <code>CAD</code>. Use three uppercase letters.</p>
<p>If the returned currency differs, the check fails and previous history is preserved. This field does <b>not</b> convert prices or switch markets; set <b>Market country</b> for that.</p>
<p>Leave empty if you only need the returned currency. Applies to every link unless an Advanced target overrides it.</p>
## `mode` (type: `string`):

<p><b>Track price and stock changes:</b> save the first successful check as a baseline. Repeat runs compare with the last successful check. Save a Task and schedule it.</p>
<p><b>Export / discover variants:</b> return current variants without changing monitor history. To find IDs, use a plain product link, clear SKU/options selectors, and choose <b>One row per variant</b> under Results.</p>
<p>Discovery is billed as a normal check. Copy the desired variant URL into a monitor Task afterward.</p>
<p><b>Preview watchlist:</b> validate and show parsed URLs, exact selectors and requested markets without source requests, product/catalog charges or history writes. Variant existence and actual cost remain unverified; the Apify start fee still applies.</p>
## `outputMode` (type: `string`):

<p><b>Changes only:</b> later monitor runs return changes, source health and check receipts. The first run also shows current variants by default. No price change means no price-alert row.</p>
<p><b>All observations and changes:</b> include current selected variants on every successful monitor check.</p>
<p>Export/discovery mode always returns current variants. Quiet checks still scan the source and are billed. Notifications require a separate workflow.</p>
## `watchlistCsv` (type: `string`):

<p><b>Paste CSV exported from Excel or Google Sheets.</b> Clear Shopify URLs and Advanced targets first. Up to 100 rows / 128 KiB; comma, semicolon, or tab delimiter.</p>
<p><code>url,variantId,country,expectedCurrency,option:Size</code><br><code>https://example.com/products/shirt,123456,US,USD,M</code></p>
<p>Required column: <code>url</code>. Optional: <code>targetId</code>, <code>variantId</code>, <code>sku</code>, <code>country</code>, <code>language</code>, <code>expectedCurrency</code>, <code>productId</code>, <code>comparisonGroup</code>, <code>role</code>, and <code>option:NAME</code> or <code>options</code> JSON.</p>
<p>Format IDs and SKUs as text to preserve digits and leading zeros. Select <b>Preview watchlist</b> to check the parsed selections without requests to stores or history changes. Preview still has the Apify start fee.</p>
## `skus` (type: `array`):

<p>Paste the merchant’s <b>exact SKU</b>, one per entry. Leave empty if the URL already selects the variant you need.</p>
<p>Any listed SKU can match. Size/color options and a URL variant are additional restrictions; all must agree.</p>
<p>Applies to every link unless an Advanced target overrides it. Different stores may use different SKUs. A complete no-match result includes reviewable variant links; the Actor does not guess a replacement.</p>
## `options` (type: `object`):

<p>Enter the store’s exact option names and values, for example:<br><code>{"Size":"M","Color":"Black"}</code></p>
<p>For several sizes:<br><code>{"Size":["M","L"]}</code><br>This matches M or L. With a Color field too, the color must also match.</p>
<p>Applies to every link unless an Advanced target overrides it. Leave empty for no extra option restriction. Choose <b>Export / discover variants</b> to see actual option names, SKUs and links.</p>
## `observationLayout` (type: `string`):

<p><b>Compact products:</b> group exact variants under their product. Recommended for catalogs and lower row counts. A single selected product variant stays a flat row.</p>
<p><b>One row per variant:</b> easier to read/export as CSV and to discover exact SKU/options/URLs. Large flat exports can use more check units.</p>
<p>Both formats retain exact variant prices and stock. Change events always stay flat.</p>
## `emitInitialSnapshot` (type: `boolean`):

<p>Keep enabled to see current selected variants on the <b>first successful monitor check</b>. The Actor saves a baseline without calling existing products “new”.</p>
<p>Disable for a quiet first baseline. Later output follows your chosen results mode. The first check is billed either way.</p>
## `minimumPriceChangePercent` (type: `string`):

<p>Enter <code>0</code> for every price move, <code>1</code> for 1%, or <code>0.5</code> for 0.5%. Do not type the % sign.</p>
<p>Small moves accumulate from the last price alert. A threshold reduces noise; successful quiet checks still save observed history.</p>
<p>Sold-out and restock events do not depend on this price threshold.</p>
## `emitNewLowAlerts` (type: `boolean`):

<p>Enable a separate alert when the price falls below previously retained samples from the last 30 days.</p>
<p>This is an <b>observed low</b>, not proof of the lowest price at every moment. Sample count and retained history determine coverage.</p>
## `includeMetadataChanges` (type: `boolean`):

<p>Enable alerts for changes to labels, SKU, selected options or product URL. Off by default to reduce cosmetic noise.</p>
<p>Known product IDs continue to be tracked when the merchant renames a handle.</p>
## `removalConfirmations` (type: `integer`):

<p>Require <b>2–5 consecutive successful absence checks</b> before reporting disappearance. Default: 2.</p>
<p>Failed or incomplete checks do not count. Leaving a collection, losing a variant, and becoming unavailable in a verified market have separate events.</p>
## `includeBackordersInComparison` (type: `boolean`):

<p>Enable if backorder offers should participate in best-price ranking. Off by default because a purchasable backorder is not necessarily ready to ship.</p>
<p>Backorders remain visible in the offers either way. This setting affects reviewed competitor groups.</p>
## `emitMatchCandidates` (type: `boolean`):

<p>Find possible cross-store matches sharing a valid GTIN barcode in the same verified country/currency.</p>
<p>Suggestions include exact variants and copyable comparison targets. <b>Review model, pack quantity and purchase terms first.</b> Candidates are never automatically ranked.</p>
## `maxMatchCandidates` (type: `integer`):

<p>Maximum barcode-match suggestions to return. Default: 100.</p>
<p>Only verified sources and checksum-valid barcodes are considered. Suggestion rows are separate from the product/change-row limit.</p>
## `monitorId` (type: `string`):

<p><b>For saved Tasks, leave empty:</b> each Task keeps its own history automatically.</p>
<p>For API/ad hoc checks, reuse a stable name such as <code>competitor-watch</code>. Give independent clients/watchlists different names.</p>
<p>Changing this ID starts a separate history. Keep the same Task or ID for repeat checks.</p>
## `historyLimit` (type: `integer`):

<p>Number of successful observations retained <b>per variant</b>. Default: 90; allowed: 2–365.</p>
<p>This is samples, not days: 90 daily checks span about 90 days; 90 hourly checks span under 4 days.</p>
<p>30-day lows/highs use only retained samples. Inspect the sample count before interpreting them.</p>
## `targets` (type: `array`):

<p>Use this JSON list for <b>different selections per store</b> or competitor comparisons. <b>Clear Shopify URLs first.</b></p>
<p>Each entry needs <code>url</code>. Optional fields: <code>targetId</code>, <code>variantIds</code>, <code>skus</code>, <code>options</code>, <code>country</code>, <code>language</code> and <code>expectedCurrency</code>.</p>
<p>For comparison, give equivalent offers the same <code>comparisonGroup</code> and select exactly one variant per target. Mark at most one offer <code>role: "own"</code>. README has a full copyable example.</p>
## `maxOutputRows` (type: `integer`):

<p>Maximum product/variant observations and change rows for the run. Default: 50,000.</p>
<p>A target that would exceed the limit is reported partial; its previous history is preserved. Source statuses and comparison rows are additional.</p>
## `advanced` (type: `object`):

<p><b>Leave empty for normal use.</b> Adjust coverage/request limits only when needed. Example:<br><code>{"maxProducts":500,"maxRequests":200,"maxRunSeconds":300}</code></p>
<p>Paid defaults: 5,000 products and 50,000 variants per target; 1,000 requests and 900 seconds per run.</p>
<p><b>Free ceilings:</b> 100 check units/run; 100 products and 3,000 variants/target; 1,000 variants/product; 80 requests and 180 seconds/run. Input cannot raise these ceilings.</p>
<p>Incomplete checks preserve history. Set spending limits separately in Apify run options. A larger input limit does not provide more credits or memory.</p>

## Actor input object example

```json
{
  "urls": [
    "https://www.deathwishcoffee.com/products/valhalla-java-single-serve-pods?variant=392715452"
  ],
  "mode": "monitor",
  "outputMode": "changesOnly",
  "observationLayout": "product",
  "emitInitialSnapshot": true,
  "minimumPriceChangePercent": "0",
  "emitNewLowAlerts": false,
  "includeMetadataChanges": false,
  "removalConfirmations": 2,
  "includeBackordersInComparison": false,
  "emitMatchCandidates": false,
  "maxMatchCandidates": 100,
  "historyLimit": 90,
  "maxOutputRows": 50000
}
```

# Actor output Schema

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

No description

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

No description

## `billing` (type: `string`):

No description

## `report` (type: `string`):

No description

## `reportMarkdown` (type: `string`):

No description

## `watchlistPreview` (type: `string`):

No description

## `notificationWorkflow` (type: `string`):

No description

## `csvTemplate` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.deathwishcoffee.com/products/valhalla-java-single-serve-pods?variant=392715452"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("x_guru/shopify-price-stock-monitor").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 = { "urls": ["https://www.deathwishcoffee.com/products/valhalla-java-single-serve-pods?variant=392715452"] }

# Run the Actor and wait for it to finish
run = client.actor("x_guru/shopify-price-stock-monitor").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 '{
  "urls": [
    "https://www.deathwishcoffee.com/products/valhalla-java-single-serve-pods?variant=392715452"
  ]
}' |
apify call x_guru/shopify-price-stock-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,x_guru/shopify-price-stock-monitor"
        }
    }
}
```

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/bsyo7tfeD9bzJc38x/builds/UoQ7tqltdNYCR6UXG/openapi.json
