# Etsy Shop Monitor (Beta) - Listings, Price, Sales & Reviews (`ivora/etsy-shop-monitor`) Actor

Beta. Track competitor Etsy shops: new listings, price changes, sales started or ended, removed listings, total shop sales and new reviews since your last run. Changes only, no login. Etsy's anti-bot protection blocks some pages; blocked pages are not charged.

- **URL**: https://apify.com/ivora/etsy-shop-monitor.md
- **Developed by:** [Ivora Tools](https://apify.com/ivora) (community)
- **Categories:** E-commerce, Marketing, Automation
- **Stats:** 2 total users, 1 monthly users, 92.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $60.00 / 1,000 shop page reads

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

## Etsy Shop Monitor (Beta): New Listings, Price, Sales & Review Tracker

> **Beta.** It works, but Etsy's anti-bot protection makes it less reliable than my other Actors. Please read the known limitations below before scheduling it.

Keep an eye on **competitor Etsy shops** and see **only what changed** since your last run: new listings, price drops and increases, sales that started or ended, listings that disappeared, how many sales the shop made, and new reviews.

I built this because checking rival shops on Etsy by hand is slow and you always miss something. Run it on a daily schedule and each run's output is a short list of changes.

- **No login, no Etsy API key.** It reads the public shop pages the way a visitor would.
- **Changes only.** The first run saves a baseline, and later runs output only the differences.
- **A free summary row per shop**: total sales, sales since the last run, review count, rating, followers, price range and how many listings are on sale.
- You can also watch **single listings** (any shop) for price, sale, stock and review-count changes.

### Known limitations (please read)

- **Etsy blocks some pages.** Even through residential proxies and a real browser, some pages get blocked. When that happens, a shop is read only partly or not at all. **Blocked pages aren't charged.**
- **Runs are slow.** Expect 30–90 seconds per shop, and a few minutes when Etsy blocks and the IP has to change.
- **Big shops are read only partly.** By default the first 3 pages (108 listings) are read, in the shop's own order. New listings beyond those pages and removed listings in big shops aren't detected.
- **Sales are shop-level only.** Etsy doesn't show sales per listing.
- **Only the last ~10 reviews** shown on the shop page are compared.
- **It may break** if Etsy tightens its protection. I'll try to fix it, but I can't promise how fast.

### Sample output

Change rows from a real cloud test run (Oct 2026, shop `SilverRainSilver`), shortened. To produce rows in that test I edited the saved baseline. On a real schedule they appear when the shop really changes.

```json
[
  {"type": "change", "changeType": "newListing", "shop": "SilverRainSilver", "listingId": "4591615189",
   "title": "Sterling Silver Pearl Mistletoe Earrings and Necklace Set", "price": 29.07, "currency": "USD",
   "url": "https://www.etsy.com/listing/4591615189/sterling-silver-pearl-mistletoe-earrings"},
  {"type": "change", "changeType": "priceChange", "shop": "SilverRainSilver", "title": "Sterling Silver Moonstone Droplet Ear Threader Earrings",
   "direction": "down", "oldPrice": 21.52, "newPrice": 17.93, "changePct": -16.7, "currency": "USD"},
  {"type": "change", "changeType": "shopSales", "shop": "SilverRainSilver", "oldSales": 2194645, "newSales": 2194683, "salesDelta": 38},
  {"type": "change", "changeType": "newReview", "shop": "SilverRainSilver", "reviewer": "Steven", "rating": 5, "date": "Oct 8, 2026",
   "text": "cute ring. i hope my wife likes it as a gift.", "listingTitle": "Sterling Silver Bluebell Enamel Open Ring"}
]
```

### How it works

Etsy protects its site with strong bot protection, so plain HTTP requests are always blocked. This Actor opens the pages in a real (stealth) Firefox browser through **Apify residential proxy**. The proxy is included in the price, so you don't need your own.

1. For every shop it reads `etsy.com/shop/<name>` page by page (36 listings per page, up to `maxPagesPerShop`). Each listing gives its ID, title, price, original price (when on sale), currency, URL and image.
2. From the first page it also takes the shop's total sales, review count, rating, followers and the latest reviews.
3. It saves all of this in a **named key-value store** (`stateStoreName`). The next run compares against it:
   - **New listing** is a listing ID not seen before.
   - **Price change** includes the old and new price and the change %.
   - **Sale started / ended** means a crossed-out original price appeared or disappeared.
   - **Removed listing** is a listing that's gone from a complete read of the shop (sold out, expired, deactivated or deleted, because Etsy doesn't say which). It's only reported when every page was read in both runs.
   - **Shop sales** is the change in the shop's total sales counter, which tells you roughly how many orders a competitor got.
   - **New review** is a review in the shop's latest reviews that wasn't there before.
4. For **single listing URLs**, the listing page is read and compared the same way, plus sold out / back in stock and the listing's own review count.

### Input example

```json
{
  "shops": ["SilverRainSilver", "https://www.etsy.com/shop/MignonandMignon"],
  "listingUrls": ["https://www.etsy.com/listing/870847843/letter-necklace"],
  "maxPagesPerShop": 3,
  "firstRunBehavior": "baseline",
  "changeTypes": ["newListing", "priceChange", "saleChange", "removedListing", "shopSales", "newReview"],
  "minPriceChangePct": 2,
  "stateStoreName": "my-etsy-competitors"
}
```

| Option | What it does |
|---|---|
| `shops` | Shop names or shop URLs |
| `listingUrls` | Optional single listings to watch |
| `maxPagesPerShop` | Pages per shop (36 listings each). The default of 3 covers 108 listings |
| `firstRunBehavior` | `baseline` (default) only saves state. `outputListings` also outputs the listings it read (charged per row) |
| `listingRowsLimit` | Caps those first-run listing rows per shop |
| `changeTypes` | Which changes to output. Unselected types are not charged |
| `minPriceChangePct` | Ignore small price moves |
| `stateStoreName` | One name per independent monitor or task |
| `proxyCountry` | Country of the residential IP (default `US`, so prices are in USD) |

### Output

- **`change` rows** (charged): `changeType` is one of `newListing`, `priceChange`, `saleChange`, `stockChange`, `removedListing`, `shopSales`, `newReview` or `listingReviews`. Each row also has `direction`, old and new values, and the listing's title, price, currency, URL and image.
- **`listing` rows** (charged, only when asked for on the first run).
- **`summary` rows** (free, one per shop): `totalSales`, `salesSinceLastRun`, `reviewCount`, `rating`, `followers`, `listingsRead`, `allListingsRead`, `onSaleListings`, price range, change counts and notes.
- **`error` rows** (free): `not_found` (wrong shop name or inactive listing) or `blocked`.

### Honest limits

- **Etsy fights scrapers hard.** Some pages get blocked even through residential IPs. On the first page of a shop the Actor tries up to 3 fresh IPs. On later pages it tries once and then stops that shop, to keep runs short, but a run can still end with a page or a shop marked `blocked`. That happened in my tests, mostly from the 3rd page of a shop onward. **Blocked pages and shops are not charged.** The summary says when only part of a shop was read.
- **It's slow.** It uses a real browser, so expect about 30–90 seconds per shop, and longer when Etsy blocks and the IP has to change. Daily monitoring of a handful of shops works fine. Exporting thousands of listings is not what this is for.
- **Listings come in the shop's own order.** Etsy blocks the "newest first" sort for robots. A new listing placed on a page beyond `maxPagesPerShop` won't be noticed, and removed listings are detected only when the whole shop fits in `maxPagesPerShop`. For big shops, watch the important listings with `listingUrls`.
- **Sales are shop-wide.** Etsy doesn't publicly show sales per listing, so `shopSales` is the shop's total counter.
- **Reviews:** only the latest reviews shown on the shop page (about 10) are compared. If a huge shop gets more than that between two runs, some reviews will be missed. The review count in the summary is always exact.
- **Sold-out listings** usually just vanish from the shop page, so in shop mode they show up as `removedListing`. Single listing URLs show sold out / back in stock explicitly.
- Prices follow the proxy country's currency (USD by default). Titles may sometimes come in another language if Etsy guesses the language from the IP. That doesn't affect change detection, which works on listing IDs and prices.

### Pricing (pay per event)

| Event | Price |
|---|---|
| Actor start | $0.01 per GB of memory, so $0.02 per run at the default 2 GB |
| Shop page read (up to 36 listings) | $0.06 |
| Single listing checked | $0.06 |
| Change row (or first-run listing row when requested) | $0.002 |

Summary and error rows are free. Blocked pages aren't charged. The price is higher than for my non-browser Actors because each page needs a real browser and residential proxy.

Examples:

- 5 competitor shops of up to 108 listings, checked daily, with about 5 changes each: $0.02 + 15 × $0.06 + 25 × $0.002 = **$0.97 per day**.
- 5 small shops (one page each), daily: $0.02 + 5 × $0.06 + 25 × $0.002 = **$0.37 per day** (about $11 per month).

### Daily alerts to Slack or email

1. Fill in the input and click **Save as a new task**.
2. In **Schedules**, add the task to a daily schedule.
3. In the task's **Integrations** tab, add **Slack**, **Gmail** or a **webhook** (Zapier, Make, n8n). Read the changes from `https://api.apify.com/v2/datasets/{defaultDatasetId}/items?view=changes`.

### Related actors

- [Shopify Store Monitor](https://apify.com/ivora/shopify-store-monitor): the same "changes only" idea for Shopify stores, including new products, prices, sales and stock.
- [Google Trends Scraper & API](https://apify.com/ivora/google-trends-api): check whether a product idea is growing before you list it.
- [Google Ads Transparency Scraper & New Ads Monitor](https://apify.com/ivora/google-ads-transparency-monitor): what competitors advertise on Google.
- [App Store & Google Play Scraper](https://apify.com/ivora/app-store-monitor): ratings, versions and ranks of iOS and Android apps.
- [Google Hotels Scraper & Rate Monitor](https://apify.com/ivora/google-hotels-rate-monitor): hotel prices by booking site, with price-change alerts.
- [ATS Jobs Scraper & Hiring Monitor](https://apify.com/ivora/company-hiring-monitor): new and closed jobs from Greenhouse, Lever, Ashby, Workday and more.
- [LinkedIn Ad Library Scraper](https://apify.com/ivora/linkedin-ad-library-monitor) and [Bing Ads Library Scraper](https://apify.com/ivora/microsoft-ads-library-monitor): competitors' ads.

Questions or a shop that doesn't work? Open an issue in the Issues tab, and I'll take a look.

# Actor input Schema

## `shops` (type: `array`):

Shop names or shop URLs, e.g. CaitlynMinimalist or https://www.etsy.com/shop/CaitlynMinimalist.

## `listingUrls` (type: `array`):

Individual listing URLs (https://www.etsy.com/listing/<id>/...) to watch price, sale, stock and the listing's review count. Charged per listing checked.

## `maxPagesPerShop` (type: `integer`):

Etsy shows 36 listings per page, in the order the shop chose. 3 pages = 108 listings. Pages after the first are more often blocked by Etsy. Removed-listing detection needs the whole shop, so set this high enough to cover all listings (e.g. 10 = 360 listings). More pages = longer run, same price per shop.

## `firstRunBehavior` (type: `string`):

On the first run there is nothing to compare with. baseline = only remember the shop. outputListings = also output the listings that were read, charged per row.

## `listingRowsLimit` (type: `integer`):

Caps listing rows per shop when first run outputs listings. 0 = no limit.

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

Which changes to output. Unselected changes are not output and not charged.

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

Ignore price changes smaller than this (e.g. 2 = only changes of 2% or more).

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

Named key-value store that remembers the previous run. Use a different name for each independent monitor/task. Only a-z, 0-9 and -.

## `proxyCountry` (type: `string`):

Two-letter country of the residential IP. Prices and currency follow it (US = USD).

## Actor input object example

```json
{
  "shops": [
    "CaitlynMinimalist"
  ],
  "maxPagesPerShop": 3,
  "firstRunBehavior": "baseline",
  "listingRowsLimit": 0,
  "changeTypes": [
    "newListing",
    "priceChange",
    "saleChange",
    "stockChange",
    "removedListing",
    "shopSales",
    "newReview",
    "listingReviews"
  ],
  "minPriceChangePct": 0,
  "stateStoreName": "etsy-shop-monitor-state",
  "proxyCountry": "US"
}
```

# Actor output Schema

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

No description

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

No description

## `runSummary` (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 = {
    "shops": [
        "CaitlynMinimalist"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("ivora/etsy-shop-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 = { "shops": ["CaitlynMinimalist"] }

# Run the Actor and wait for it to finish
run = client.actor("ivora/etsy-shop-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 '{
  "shops": [
    "CaitlynMinimalist"
  ]
}' |
apify call ivora/etsy-shop-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ivora/etsy-shop-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/b85owqrtNjp8pNQEb/builds/2y35mN5GLFnvicJCh/openapi.json
