# Shopify Price and Restock Change Watch (`practical_ai_lab_jp/pal-shopify-storefront-price-restock-watch`) Actor

Track Shopify prices and restocks from one store URL. Export JSON/CSV changes.

- **URL**: https://apify.com/practical_ai_lab_jp/pal-shopify-storefront-price-restock-watch.md
- **Developed by:** [Practical AI Lab](https://apify.com/practical_ai_lab_jp) (community)
- **Categories:** E-commerce, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 66.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 price restock changes

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 and Restock Change Watch

**Know what changed in a Shopify store: price drops, price increases, restocks, sell-outs and newly observed product variants.** Provide **one public storefront URL**; no store login or CSV upload is required.

| Your action | What you receive |
| --- | --- |
| First run with a public store URL | A saved comparison baseline, sampled variants and a `BASELINE_CREATED` record |
| Repeat run using the same store and state | JSON/CSV evidence of observed changes; `NO_CHANGES` if none are found |
| Optional recurring schedule in Apify | Periodic comparisons; you configure the schedule, this Actor does not send automatic alerts |

**Working first-run example:** On November 4, 2026, a bounded Apify test using `https://www.allbirds.com` succeeded with `BASELINE_CREATED` and 26 variants sampled. This documents a past test, **not** a guarantee that the public feed will remain accessible or that every run will discover a change.

**Billing:** only `CHANGE` results trigger the `price-restock-change` event ($0.002 per change at the observed Store rate). Baseline, samples, no-change reports and errors do not trigger that event. **Apify Actor-start and platform usage charges can still apply**, even without a change. Check the live Pricing tab before running.

If you need a one-time catalog export rather than repeated comparisons, use [Shopify Products Scraper & Variant CSV Export](https://apify.com/practical_ai_lab_jp/pal-shopify-product-json-csv-export), a separate paid Actor.

### Quick start (one URL)

1. Open the Actor and set **Storefront URL**, for example `https://www.allbirds.com`. Nothing else is required. (Replace it with the store you want to watch; the example is only an input format illustration and its feed availability is not guaranteed.)
2. Run it. The first run saves a baseline and returns a record with `recordType: "BASELINE_CREATED"` plus a few `SAMPLE` rows so you can verify what was read. **No changes are reported on a first run** because there is nothing to compare with.
3. Run it again later. You now get a `RUN_SUMMARY` record and one `CHANGE` record per changed variant. If nothing changed, the status is `NO_CHANGES`.

```json
{
    "storeUrl": "https://www.allbirds.com",
    "maxPages": 4,
    "minPriceChangePercent": 2,
    "sampleSize": 10
}
```

### Scheduling later runs

The Actor does not run itself and does not send alerts. To watch a store continuously, create an **Apify Schedule** (Console → Schedules) that starts this Actor with the same input, for example daily. Use the same `storeUrl` and `stateStoreName` each time so runs share one baseline. To be notified, connect Apify integrations or webhooks to the run or dataset yourself; this Actor sends no emails or messages.

### How state is kept

Snapshots are written to a **named key-value store** in your own Apify account (default name `pal-storefront-watch-state`), one compact record per store domain. A named store is used because the default store of a run is discarded with the run and could not hold a baseline. Change `stateStoreName` to keep separate watch groups. Use **Reset baseline** to start over.

### Output

| recordType | When | Change-event charge (Actor-start/usage charges separate) |
|---|---|---|
| `BASELINE_CREATED` | first run for a store (or reset) | no |
| `RUN_SUMMARY` | repeat runs, with counts, sampling details, warnings | no |
| `CHANGE` | one per changed variant | yes, one `price-restock-change` event each (when pay-per-event is enabled on the listing) |
| `SAMPLE` | first N current variants, for inspection | no |
| `ERROR` | the run failed | no |

`CHANGE` fields: `changeType` (`RESTOCK`, `SOLD_OUT`, `PRICE_DROP`, `PRICE_INCREASE`, `NEW_VARIANT`), `allChanges`, `productTitle`, `variantTitle`, `productUrl`, `variantId`, `oldPrice`, `newPrice`, `priceChangePercent`, `oldAvailable`, `newAvailable`, `previousSnapshotAt`, `currentSnapshotAt`.

Key-value store outputs of each run: `OUTPUT` (summary JSON) and `CHANGES_CSV` (the change rows, or the sample rows when there are no changes).

Failed runs (blocked by robots.txt, unreachable feed, invalid URL and so on) write an `ERROR` record and do not generate billable `price-restock-change` events. Standard Apify Actor-start and platform usage charges may still apply.

### Input

| Field | Default | Meaning |
|---|---|---|
| `storeUrl` | required | Public HTTPS storefront URL. Only the host is used. |
| `maxPages` | 4 (max 10) | Feed pages of up to 250 products per run |
| `maxProducts` | 1000 (max 2500) | Product sample ceiling |
| `minPriceChangePercent` | 0 | Ignore smaller price moves |
| `maxChangeRecords` | 200 | Cap on returned and billed change records |
| `sampleSize` | 10 | Number of `SAMPLE` rows |
| `resetBaseline` | false | Rebuild the baseline now |
| `stateStoreName` | `pal-storefront-watch-state` | Named store holding snapshots |
| `requestTimeoutSecs` | 20 | Per-request timeout |

### Safety and access rules

- Only HTTPS storefront host names are accepted. IP addresses, credentials in URLs, non-default ports, `localhost` and internal suffixes are rejected.
- The host name is resolved before every request and refused if any address is non-public (private, loopback, link-local, multicast and similar).
- `robots.txt` is fetched first and checked for **every** later request using the user agent token `PALStorefrontWatch`. If robots.txt cannot be read reliably (anything except 200, 404 or 410), or disallows the feed, nothing else is requested. Crawl-delay is honored up to 10 seconds, and a longer delay causes a refusal.
- Redirects are followed at most twice and only within the same host; every hop is re-checked. A redirect to another host (for example from the bare domain to `www.`) is refused with a message telling you which host to resubmit.
- At most `maxPages + 4` requests per run, with a pause between requests, per-request timeouts and a response size cap.
- Only the public `/products.json` feed is read. No logins, no cart or checkout pages, no customer data.

### Limitations you should know

- **Sampling.** Only the first `maxProducts` products (within `maxPages` pages) in the feed's own order are compared. Large catalogs are partly covered. The `sampling` block of every summary states pages read, products and variants sampled and whether the whole feed was read. When the sample is partial, new-variant detection is turned off and previously stored variants outside the sample are kept, not treated as removed.
- **Feed order can change.** If the store reorders products, a partial sample may include different products from run to run; those variants are then simply not compared.
- **Availability is a public flag, not stock.** `available` says whether a variant can currently be bought on the storefront. The Actor never infers or reports inventory counts or private stock, and a "restock" means that flag turned from false to true between two runs. Brief sell-outs between runs are invisible.
- **Prices and currency.** Prices are the feed values for the store's default market. The feed does not name the currency, and stores that localize prices may show different values to shoppers elsewhere. Discounts applied only at checkout are not visible.
- **Not every store works.** Some stores disable or restrict `/products.json`, forbid it in robots.txt, or rate-limit. Those runs fail with a clear error code and are not charged.
- **Catalog size caps.** At most 5,000 variants per run and 10,000 stored per snapshot.
- Intermediate changes between two runs are collapsed into one before/after comparison.
- DNS is checked before each request, but the HTTP connection resolves the host again; a hostile DNS server could in theory answer differently the second time.

### Billing

When pay-per-event is enabled on the Store listing, the code emits one `price-restock-change` event per `CHANGE` record. Baseline, summary, sample, no-change and failed runs emit no events. Platform usage costs apply as usual.

### Local tests

The unit tests run offline with fakes and need no network or Apify account:

```
python -m unittest discover -s tests -t .
```

# Actor input Schema

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

Public HTTPS URL of a Shopify storefront (only the host is used). This is the only required field. The first run saves a baseline; later runs report changes.

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

Maximum number of public product feed pages (up to 250 products each) to read per run.

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

Upper bound on products compared per run. Large catalogs are only partly covered when this is lower than the catalog size.

## `minPriceChangePercent` (type: `number`):

Ignore price moves smaller than this percentage. 0 reports every change.

## `maxChangeRecords` (type: `integer`):

Cap on change records returned (and billed) per run. The most important changes (restocks, sell-outs, price moves) come first.

## `sampleSize` (type: `integer`):

Number of current variants echoed as SAMPLE rows so you can inspect what was read. Sample rows are never billed.

## `resetBaseline` (type: `boolean`):

Discard the stored snapshot for this store and create a new baseline now.

## `stateStoreName` (type: `string`):

Name of the private Apify key-value store that keeps snapshots between runs. Change it to keep separate watch groups.

## `requestTimeoutSecs` (type: `integer`):

Timeout for each HTTP request.

## Actor input object example

```json
{
  "storeUrl": "https://www.allbirds.com",
  "maxPages": 4,
  "maxProducts": 1000,
  "minPriceChangePercent": 0,
  "maxChangeRecords": 200,
  "sampleSize": 10,
  "resetBaseline": false,
  "stateStoreName": "pal-storefront-watch-state",
  "requestTimeoutSecs": 20
}
```

# 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 = {
    "storeUrl": "https://www.allbirds.com"
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ai_lab_jp/pal-shopify-storefront-price-restock-watch").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 = { "storeUrl": "https://www.allbirds.com" }

# Run the Actor and wait for it to finish
run = client.actor("practical_ai_lab_jp/pal-shopify-storefront-price-restock-watch").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 '{
  "storeUrl": "https://www.allbirds.com"
}' |
apify call practical_ai_lab_jp/pal-shopify-storefront-price-restock-watch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,practical_ai_lab_jp/pal-shopify-storefront-price-restock-watch"
        }
    }
}
```

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/ctw9fCZTfpUJIoHxC/builds/SVvH0NU9xXSvwnNtK/openapi.json
