# Shopify Price & Restock Monitor (`atlasi/shopify-price-restock-monitor`) Actor

Monitor a Shopify product shortlist. Export variant-level prices, availability and before/after changes. Public available is not warehouse inventory.

- **URL**: https://apify.com/atlasi/shopify-price-restock-monitor.md
- **Developed by:** [Atlas](https://apify.com/atlasi) (community)
- **Stats:** 2 total users, 1 monthly users, 33.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 product checkeds

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 & Restock Monitor

Check a shortlist of public Shopify product URLs and get variant-level prices and availability, plus before/after changes on repeated runs. No Shopify installation, admin credentials or browser is required.

**Restock means a public `available: false → true` observation.** It does not reveal inventory quantities, warehouse stock or sales. This is a polling tool; changes between checks can be missed.

### Start with one competitor product

#### 1. Create your first price check

1. Open this Actor in [Apify Store](https://apify.com/atlasi/shopify-price-restock-monitor), click **Try for free**, and sign in. Account credits may cover your trial; successful checks use the price shown in the Pricing tab.
2. In **Input**, paste a public Shopify **product page** into **Product URLs**. Start with one URL, not a store homepage or collection. You can try `https://www.allbirds.com/products/mens-tree-runners` and then replace it with your competitor's product.
3. Enter **Watch ID** `my-competitors`. Keep this exact ID on future checks of this list. Leave **Mode** at **Compare with last successful check** and leave the other fields at their defaults.
4. Under **Run options**, select **Build: latest** and set **Maximum cost per run** to **$0.01** for this one-product test. Click **Start** or **Save & start**.
5. Once finished, open **Output → Monitoring report**. Read the OK / incomplete / skipped counts first. The report lists every requested product and links to changes CSV/JSON. Choose **Current product options** for option-level prices and availability. Several size/color rows still count as one product check.

You can also paste this into the JSON input editor:

```json
{
  "productUrls": ["https://www.allbirds.com/products/mens-tree-runners"],
  "watchId": "my-competitors",
  "mode": "monitor"
}
```

**Expected first result:** `status: ok`, `isBaseline: true`, and no changes. The first run saves today's prices and availability for the next comparison. The example returned seven USD options in testing on September 30, 2026; its price, options and accessibility can change.

#### 2. Check the same product every day

1. Return to the Actor's **Input** and click **Save as a new task**. Name it `Competitor daily check` and keep the option to copy your input selected.
2. Open the Task's **Input**. Confirm the same product URL, **Watch ID** `my-competitors`, and monitor mode. Check its **Run options**, including **Build: latest** and the maximum cost. Click **Save** if you changed anything.
3. Click **Start** on the Task once. A successful run with `isBaseline: false` confirms it is comparing against your first check. No change is normal when the store has not changed.
4. In the left menu, open **Schedules** → **Create new**. Under **Schedule setup**, select daily at **09:00** in your own time zone (for example, `Asia/Seoul`). If using cron, `0 9 * * *` means daily at 09:00 in the selected zone.
5. Use **Add** → **Task** and select `Competitor daily check`. Leave input overrides empty so it uses the Task's saved input. Enable exclusive execution if offered, check **Next runs**, then click **Enable** when ready.

Keep only one schedule for this watch and avoid starting another run while it is running. To stop future checks, open that schedule and click **Disable**. Disabling does not abort an already running check.

#### 3. Read changes and spot failed checks

Open your Task's **Runs**, choose the latest finished run, and open **Output → Monitoring report** first. It lists every requested product, including unprocessed URLs and the reason (for example `budget_limit`). The platform label `Succeeded` means the process finished; the report tells you whether every product was checked.

| What you need | Where to look | What it means |
| --- | --- | --- |
| Current prices and availability | **Output** table | `available: true` means publicly available to buy; it is not a stock quantity. |
| Price drops and restocks | **Monitoring report** → **Download changes CSV** or **Changes JSON** | JSON list or spreadsheet-ready rows with before/after values. Output links may also appear as **Changes JSON** and **Changes CSV**. |
| Whether all products were checked | **Monitoring report** → **Summary JSON** | `status: complete`, `ok: 1`, `processedProducts: 1` confirms this one-product example completed. |

`CHANGES: []` is expected on the first run or when nothing changed. **Check SUMMARY before treating an empty list as “no changes”:** a partial or failed check can also produce an empty list. Each run has its own files; open the latest run rather than yesterday's download link.

Change labels are `price_drop`, `price_increase`, `discount_changed`, `became_available` (restock), and `became_unavailable` (sold out). The Actor provides files; email, Slack and Sheets delivery require a separate integration.

#### What will it cost?

At the current launch price of **$0.003 per successful product check**, daily checks for 30 days cost:

| Products | Product-check fees for 30 days |
| --- | ---: |
| 1 | $0.09 |
| 10 | $0.90 |
| 100 | $9.00 |

These examples assume one successful check per product per day. The first setup run and extra manual checks are additional. Options and changes do not add event fees. Failed, partial and duplicate products have no product-check fee. Platform usage is included in the current Actor price; check [Pricing](https://apify.com/atlasi/shopify-price-restock-monitor/pricing) for current terms. Your account plan and retained storage can have separate charges.

**Maximum cost per run is not a monthly budget.** A $0.01 event budget covers up to three successful checks at this rate. Raise it when expanding your list (100 products need at least $0.30); if too low, SUMMARY reports skipped products or a budget stop. Disable the schedule to stop future runs.

#### If your first check does not work

| Result | Next step |
| --- | --- |
| `not_found` | Open the product in your browser and copy its current product URL. |
| `blocked` or `unsupported` | The store may block automated access, require a queue/CAPTCHA, or redirect to another domain. Use its final product hostname if it is a normal Shopify product page; access barriers are not bypassed. |
| `partial` | Inspect `warningCode` and SUMMARY. The price/currency or options were incomplete; no change comparison was made and the previous good baseline was preserved. |
| Every run says `isBaseline: true` | Keep the same Watch ID, account and monitor mode. A different currency, product identity or locale can also reset the baseline. |
| Run is `Succeeded` but SUMMARY is `partial` | Some products could not be checked. Inspect their status rows before relying on the result. |

Report a failing public URL through the Store **Issues** tab, with the run ID and warning code. Exclude credentials and private URLs.

### Input options

Use `mode: "snapshot"` for current state only; no baseline is read or written. Up to 500 URL strings are accepted. Locale paths and `variant` selections are retained; tracking parameters are discarded. Set **Options to monitor → Selected options only** (`variantScope: "selected"`) to return and compare only the option IDs in your URLs. Products without an option ID still include all options. Missing selected options produce a free partial check, with the previous baseline preserved. The same canonical product is fetched and billed once, even when multiple variants are selected. The default `variantScope: "all"` retains the previous behavior: all options are returned and compared. The baseline always stores the full product, and billing remains per successful product.

Optional `minPriceChangePct` filters price drop/increase events only; discount events are unaffected. `changeTypes` selects `price_drop`, `price_increase`, `discount_changed`, `became_available`, and `became_unavailable`. Filtering never changes billing or baseline updates. Price thresholds compare the immediately previous successful observation, not an accumulated change.

### Results

- Default Dataset: one row per valid returned variant; failed products have one status row. First runs and unchanged runs also return current data.
- `CHANGES`: JSON change list. Empty array means no reported changes.
- `CHANGES.csv`: one row per change, or headers only. Text is escaped to limit spreadsheet formula injection.
- `REPORT.html`: readable product coverage, baseline explanation, changes and downloads; selected by default in Output.
- `SUMMARY`: processed/skipped counts, status, baseline updates, change count, billing counts and a `products` list including skipped URLs/reasons. Platform cost is null until measured on Apify.

The Output tab links to these records. Multiple fields changing produces multiple events. `eventId` identifies a specific before/after observation; deduplicate on it downstream. Times are observation times, not actual change times. An error never becomes a sold-out event.

Synthetic change example:

```json
{
  "changeType": "price_drop",
  "currency": "USD",
  "previousPrice": "30.00",
  "currentPrice": "24.00",
  "changePct": -20,
  "previousObservedAt": "2026-09-29T00:00:00Z",
  "observedAt": "2026-09-30T00:00:00Z"
}
```

Full events also contain product/variant IDs, selected watch/run, compare-at values, availability and discount percentages. Prices are decimal strings; IDs are strings. `rawPrice`, `rawCompareAtPrice` and `priceConversion` preserve the Ajax source and `/100` conversion rule. SKU does not replace variant ID.

### Support and history

Only public Shopify Ajax product endpoints are supported. Collections, store-wide discovery, headless stores without Ajax endpoints, CAPTCHA bypass, proxies, discount codes, selling-plan prices and private stores are outside this version.

Currency is checked through `cart.js` before and after the product request in the same cookie session. Missing/unstable currency or malformed options produce a free partial result, suppress all change events and preserve the last successful baseline. This version accepts recognized currencies with 0 or 2 ISO fractional digits; 3-digit currencies are conservatively partial. The Ajax `/100` representation also applies to supported zero-decimal currencies.

Observable context is the final product URL/locale and currency. Changes of this context or product ID create a new baseline. Geo/market rules sharing the same currency cannot always be detected; use consistent execution location for a watch. Redirects to another origin are reported as unsupported, so use the store's final canonical hostname. Responses with 250 options carry a coverage warning; Shopify can truncate larger products. Missing/new option IDs do not create deletion/restock events.

Baselines live in your named Key-value store `shopify-watch-<hash(actor + watch)>`, one last successful record per canonical product. Past run outputs follow your storage retention policy; named storage can incur storage charges. To reset a watch, use a new watch ID or delete only its named store after ensuring no run is active. Removing a URL does not delete its existing baseline.

Use exclusive schedules. Local watch locking uses exclusive file creation; a crashed process can leave a lock under `storage/watch-locks/`, removable only after confirming no process is running. Cloud locking uses one pending Request queue item per watch with a renewable lease. Cloud lock contention has been tested in the developer account; expired-lease and migration recovery remain experimental. Avoid overlapping runs for the same watch.

Results are saved before charging. The default KVS contains a run manifest and per-product `ENTRY-*` recovery records; Dataset `rowId` prevents duplicate rows on resume. If a charge may have happened but its response was not durably recorded, the run reports `billing_unknown`, does not retry that charge and does not advance that product's baseline. Review the platform ledger rather than blindly retrying billing. Storage and billing are not one transaction.

### Pricing

The launch price is **$0.003 per successful product check**, including first checks and unchanged checks, with platform usage included. Multiple options, changes and output rows incur no extra event fee. Duplicate URLs, blocked/not-found/unsupported/error and partial products are free. Check the Store Pricing tab for the active configuration before running.

100 products daily for 30 days produce $9 in product-check fees at this rate. Maximum run cost is not a monthly cap. Set a small maximum run cost for your first test. The Actor stops before processing the next product when its event budget is exhausted.

This is an early release. On September 30, 2026, two runs of a 100-product sample across eight stores each checked 99 products; one Gymshark URL redirected to a queue and was unsupported. Independently fetched data matched 407 options. This sample does not guarantee support for every Shopify store.

### Send alerts with n8n

1. Finish one Actor run and open **Monitoring report → Download n8n alert workflow**. Import the JSON into n8n (workflow menu → Import from File). It is inactive and starts manually.
2. In **Configuration**, replace `REPLACE_WITH_FINISHED_RUN_ID` with your finished run ID and `notificationUrl` with your own HTTP webhook. Keep `apiBase` at `https://api.apify.com/v2`.
3. Create an n8n **Header Auth** credential: name `Authorization`, value `Bearer YOUR_APIFY_TOKEN`. Select it on **Get run**, **Get summary**, and **Get changes**. Never paste a token into Configuration or exported workflow JSON. Add your receiver's authentication to **Send notification** if required.
4. Click **Execute workflow**. The workflow checks that the run finished successfully, reads that run's SUMMARY and CHANGES, and sends a JSON notification for changes or incomplete checks. A complete run with zero changes sends nothing. A failed/unfinished run stops with an error; use n8n's error workflow for operational alerts.
5. The receiver gets `text`, `kind`, `runId`, `watchId`, `eventIds`, `changes` and `incompleteProducts`. A webhook accepting a JSON `text` field can display the summary; use a field-mapping node for other receiver formats. Deduplicate `eventIds` at your receiver if retrying or replaying runs; this example does not provide exactly-once delivery across workflow executions.

After testing manually, replace **Manual run → Configuration** with the Apify **Actor Run Finished** trigger and map its run ID into Configuration. For Saved Tasks use **Actor Task Run Finished**. Filter to this Actor/Task, keep API and receiver credentials in n8n, then activate. Match the actual trigger payload in your n8n version; do not leave the example run ID fixed in an automatic workflow. Use exclusive Actor schedules.

The example was tested in the real n8n engine with a controlled cloud change event and a local HTTP receiver, including incomplete-check warnings and no-change suppression. This is not proof of a naturally occurring restock or delivery to Slack/email. The Actor itself sends no messages.

For independent lists, leave **Watch ID** blank in a Saved Task to use its unique Task ID. Manual/API monitor runs still need an explicit ID. When copying a Task that has an explicit Watch ID, clear it or assign a new one; preserving it shares the same baseline. Filtering compares the immediately previous successful observation. For example, `minPriceChangePct: 5` with `changeTypes: ["price_drop", "discount_changed"]` can still report a discount change when the price drops less than 5%.

### References

- [Apify saved Tasks](https://docs.apify.com/actors/running/tasks) and [daily schedules](https://docs.apify.com/actors/running/schedules).
- [Shopify product Ajax API](https://shopify.dev/docs/api/ajax/reference/product): locale-aware endpoint, cart presentment currency and 250-option limit.
- [Shopify product money representation](https://shopify.dev/docs/api/liquid/objects/product): zero-decimal currency representation.
- [Apify SDK PPE](https://docs.apify.com/sdk/js/docs/concepts/pay-per-event): charge results and remaining budget.
- [Apify queue locking](https://docs.apify.com/api/client/js/reference/class/RequestQueueClient): authoritative request leases.

# Actor input Schema

## `productUrls` (type: `array`):

1–500 HTTPS Shopify product pages, not homepages or collection pages. Duplicate products are checked once. A ?variant=ID selects an option; choose Selected options only below to limit output and changes.

## `watchId` (type: `string`):

Leave blank in a Saved Task to use its unique Task ID. Manual/API monitor runs require your own ID. Keep it for repeated checks; use a different ID per customer/list. When copying a Task, clear the old explicit ID.

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

Snapshot reads/writes no baseline. Monitor requires watchId or a Saved Task ID.

## `minPriceChangePct` (type: `number`):

Only price drop/increase events use this threshold. Discount and availability events are unaffected. The baseline still advances after a filtered change.

## `changeTypes` (type: `array`):

Choose report events. Filtering does not change product-check billing.

## `variantScope` (type: `string`):

Selected mode limits output and change events to variant IDs in your URLs. Products without variant IDs still include all options. The full product baseline and per-product billing remain unchanged. Missing selected IDs are a free partial check.

## Actor input object example

```json
{
  "productUrls": [
    "https://www.allbirds.com/products/mens-tree-runners"
  ],
  "mode": "monitor",
  "minPriceChangePct": 0,
  "changeTypes": [
    "price_drop",
    "price_increase",
    "discount_changed",
    "became_available",
    "became_unavailable"
  ],
  "variantScope": "all"
}
```

# Actor output Schema

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

No description

## `snapshots` (type: `string`):

No description

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

No description

## `changesCsv` (type: `string`):

No description

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

No description

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "productUrls": [
        "https://www.allbirds.com/products/mens-tree-runners"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("atlasi/shopify-price-restock-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 = { "productUrls": ["https://www.allbirds.com/products/mens-tree-runners"] }

# Run the Actor and wait for it to finish
run = client.actor("atlasi/shopify-price-restock-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 '{
  "productUrls": [
    "https://www.allbirds.com/products/mens-tree-runners"
  ]
}' |
apify call atlasi/shopify-price-restock-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,atlasi/shopify-price-restock-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/2UvY92LbYMpcs7ndN/builds/HaUFYZY97nw9dq2Xm/openapi.json
