# Etsy Scraper + Listing Change Monitor (`luminar/etsy-product-shop-listing-change-monitor`) Actor

Search public US Etsy listings, fetch exact public listing details, collect shop catalogs, or track verified listing changes with stable IDs and explicit coverage.

- **URL**: https://apify.com/luminar/etsy-product-shop-listing-change-monitor.md
- **Developed by:** [Luka](https://apify.com/luminar) (community)
- **Categories:** Automation, Developer tools, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 1,000 etsy listings

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

Use this Etsy scraper to search public US listings, inspect exact public listing details, collect a bounded shop catalog, or track verified listing changes. Every delivered row keeps a stable identity, and every run explains whether the requested scope was complete, capped, filtered, partial, blocked, failed, or positively empty.

**Start with one keyword and 25 core listings.** You receive analysis-ready Dataset rows plus a free coverage row that explains what Etsy exposed for that target.

### 🚀 Start in 60 seconds

1. Choose **Search Etsy products**.
2. Enter one buyer-style keyword.
3. Keep **Core listing cards** and the 25-listing default.
4. Run the Actor, open the Dataset, and check the free coverage row before increasing the limit.

```json
{
  "workflow": "search_products",
  "queries": ["handmade silver ring"],
  "maxListings": 25,
  "detailLevel": "core",
  "marketCountry": "US",
  "currency": "USD",
  "maxBuyerChargeUsd": 5
}
```

![Etsy listing rows with prices, placements and explicit coverage](https://api.apify.com/v2/key-value-stores/DJgPR6wwS2tIaLDQW/records/etsy_listing_shop_change_monitor--f798bc7f7de5cc22-listings-dataset.png)

### 🎯 Choose the right workflow

| Workflow | Use it when | Required input | Current evidence |
| --- | --- | --- | --- |
| Search Etsy products | Keyword research, price comparison and placement analysis | `queries` | US keyword search returned listing IDs, titles, prices, currencies and images with `COMPLETE` coverage |
| Analyze Etsy shops | A public shop profile and its current catalog window | `shops` | Public shop catalog returned 25 listings plus one shop row and declared `CAPPED` coverage |
| Scrape exact listings | Known public listing IDs or URLs that need additional fields | `listingTargets` | Exact detail returned title, price, currency, shop, rating, review count, description, video and shipping |
| Track listing changes | Sequential comparison of the same search, shop or exact-listing scope | Matching target fields plus `stateNamespace` | Baseline, unchanged repeat, simultaneous changes and recovery behavior are mechanically verified |

US keyword search, exact public listing detail and a bounded public shop-catalog window are the release-supported source groups. Other coverage stays inside the limitations listed below.

### 📦 What you get

#### Core listing rows

Core rows can include:

- stable listing ID and canonical public URL;
- title, current price, original price and currency;
- main image and observed query/page/rank placements;
- public shop name when Etsy includes it on the observed page;
- rating, review count, product type, badges and shipping signals when published;
- explicit sponsored, in-cart or recent-purchase values only when Etsy displays them.

Missing conditional fields stay `null`. The Actor does not estimate sales, demand, popularity, quality, sentiment or future performance.

#### Detailed listing and shop rows

Successful detailed rows can additionally include description, images, video, categories, tags, materials, personalization, variations, shipping, returns and processing information when Etsy publishes them. A successful detailed row uses `detailed_listing_result` **instead of** `listing_result`; the two listing events are never stacked for the same row.

Shop rows can include the public shop identity, URL, location, rating, review count, active-listing count, announcement, sections and logo when available. The same shop is charged at most once per run.

#### Coverage and monitoring rows

Coverage rows are free. They report targets, pages visited, raw and unique listing counts, duplicates merged, filters applied, detail success, field population and the exact stop reason.

Monitoring can return paid `NEW` or `UPDATED` rows and free `NOT_OBSERVED` notices. `NOT_OBSERVED` means only that an item was absent from the same bounded search or shop window; it never means deleted, sold, inactive or unavailable.

Representative synthetic listing row:

```json
{
  "recordType": "listing",
  "stableId": "etsy:listing:synthetic-001",
  "listingId": "synthetic-001",
  "title": "Synthetic handmade silver ring",
  "priceAmount": 42,
  "currency": "USD",
  "imageCount": 1,
  "detailStatus": "CORE",
  "paid": true,
  "paidEvent": "listing_result",
  "sourceClass": "PUBLIC_FIRST_PARTY",
  "sourceName": "Etsy",
  "sourceUrl": null,
  "sourceContractVersion": "etsy-public-first-party-v2"
}
```

This is a synthetic schema example, not a live Etsy listing.

### 🎛️ Input guide

#### Targets and limits

The input schema has hard safety ceilings of 50 queries, 100 shops, 10,000 exact listing targets, 50,000 current-data rows and 250 pages per target. Those limits have passed the real local runtime and resource guards, but they are capacity controls—not live-source coverage promises.

The release canaries verified one US keyword target with `maxListings: 25`, one exact public listing with one detail and one shop row, and one public shop target with `maxListings: 25` and one shop row. Larger live workloads are not yet supported as a Store promise. Reaching any page, listing, resource or charge ceiling returns `CAPPED`, never false completion.

`categoryUrls` accepts public Etsy category or market URLs, but pagination beyond the first verified page is not yet supported as a release promise.

#### Filters

Price range, product type, handmade, sale, free shipping, rating, review count, shop location and sort settings become part of the exact monitoring scope. Currency values are preserved as Etsy published them; no exchange-rate conversion is performed.

Cart, recent-purchase and sponsored fields are conditional. Sorting by those values uses only explicit numeric signals; missing values sort last and are never estimated.

#### Detail work

- `core`: use search or shop cards only.
- `detailed`: open up to `maxDetailListings` public listing pages.
- `complete`: perform the same listing detail work and add up to `maxShopProfiles` public shop profiles.

If detail work is unavailable, the valid core listing remains in the Dataset, its `detailStatus` becomes `PARTIAL`, coverage becomes `PARTIAL`, and no detailed-listing event is charged for that row.

#### Monitoring

The default first monitoring run creates a baseline without paid `NEW` rows. Choose `emit_current_as_new` only when the first current snapshot should be returned as paid `NEW` events.

Repeat runs must use the same `stateNamespace` and exactly the same targets, filters and limits. Runs for the same state scope must execute sequentially.

### 💰 Pricing

The complete charge is:

`run charge = successful run or monitored targets + mutually exclusive core/detailed listing rows + shop rows + verified change rows`

| Event | Free | Bronze | Silver | Gold | Platinum | Diamond |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| Successful trustworthy run | $0.208 | $0.208 | $0.208 | $0.208 | $0.208 | $0.208 |
| Monitored target | $0.001 | $0.0009 | $0.0008 | $0.0007 | $0.0006 | $0.0005 |
| Core listing | $0.0016 | $0.001336 | $0.001064 | $0.0008 | $0.0008 | $0.0008 |
| Detailed listing | $0.011 | $0.011 | $0.011 | $0.011 | $0.011 | $0.011 |
| Shop profile | $0.003 | $0.0028 | $0.0026 | $0.0024 | $0.0022 | $0.002 |
| Public review | Reserved; not yet supported | Reserved; not yet supported | Reserved; not yet supported | Reserved; not yet supported | Reserved; not yet supported | Reserved; not yet supported |
| Verified change | $0.0016 | $0.001336 | $0.001064 | $0.0008 | $0.0008 | $0.0008 |

Free-plan examples:

- 25 delivered core listings: `$0.208 + 25 × $0.0016 = $0.248`.
- One successful detailed listing plus one shop profile: `$0.208 + $0.011 + $0.003 = $0.222`.

Coverage rows, warnings, duplicate placements, `UNCHANGED` observations and `NOT_OBSERVED` notices are free. Blocked, failed and challenge-stopped runs do not accept the successful-run event. A successful detailed listing replaces the core listing event rather than adding another charge.

### ✅ Coverage you can trust

- `COMPLETE`: every requested page in the comparable exposed scope was exhausted.
- `CAPPED`: a declared limit stopped otherwise useful work while more may exist.
- `PARTIAL`: useful rows were delivered, but requested discovery or detail work did not finish.
- `FILTERED`: the public source was reached and buyer filters removed some rows.
- `BLOCKED`: a verified access condition prevented trustworthy completion.
- `FAILED`: the expected public data shape or reliable transport was not available.
- `EMPTY_CONFIRMED`: the expected current public shape positively showed no matching rows.

Blocked, failed or incomplete results never become empty success and never advance monitoring state. The Actor does not emit `ENDED` because search or shop disappearance is not proof of removal.

### 🔌 API and automation

Use the same input JSON with the Apify API, JavaScript client, Python client or CLI. Export the default Dataset as JSON, CSV or Excel, and open the authoritative run summary for coverage, delivered counts, charge information and state status.

After the Actor is public, you can create your own Apify Task or schedule from any successful input. No public Task or schedule is bundled with the Actor.

### ⚠️ Not yet supported

- Category and market pagination beyond the first verified page.
- Public review-row delivery.
- Non-US market coverage.
- Live workloads beyond the release-canary bounds: one US keyword target with `maxListings: 25`, one exact listing detail, or one public shop target with `maxListings: 25` and one shop profile.
- Guaranteed population of cart, recent-purchase or sponsored fields.
- A naturally occurring live `NEW` or `UPDATED` observation; the same production parser, normalizer, diff, delivery and state path is verified with controlled source-shaped changes.
- Universal coverage across every keyword, shop, listing, locale or conditional Etsy field.
- Login, account data, imported cookies, private seller analytics, buyer identity, checkout actions or messages.
- CAPTCHA interaction or solving.
- Official API surfaces that require a buyer-supplied key or token.
- AI-generated demand, sales, popularity, quality, sentiment, value or recommendation scores.
- Deletion, sold, inactive or unavailable claims inferred from disappearance.

### ❓ FAQ and support

#### Why is a field null?

Etsy does not publish every field on every listing, shop or page. `null` means not observed; it is not an estimate or zero.

#### Does Detailed charge both listing events?

No. A successfully enriched row uses `detailed_listing_result` instead of `listing_result`.

#### Can a missing search result trigger deletion?

No. It becomes a free `NOT_OBSERVED` notice only when the comparison is otherwise compatible. The prior state is retained.

#### What should I include in a support request?

Send the Actor run ID, selected workflow, sanitized input and the coverage warning or stop reason. Remove API tokens and private data before sharing.

# Actor input Schema

## `workflow` (type: `string`):

Choose the buyer job first. Search uses queries/categories, Shop uses public shop names/URLs, Exact uses listing IDs/URLs, and Track compares one of those same scopes over sequential runs.

## `queries` (type: `array`):

Used by Search and search monitoring. Each buyer-style keyword is visited independently; one listing can retain several query/page/rank placements without becoming duplicate paid rows.

## `maxListings` (type: `integer`):

Run-wide delivery cap. Search and Shop may return CAPPED with a useful prefix. Monitoring is additionally limited to 10,000 current listings so state and retry safety remain bounded.

## `maxBuyerChargeUsd` (type: `number`):

Stops before source work when the maximum event vector could exceed this buyer ceiling. Lower the value for a tighter per-run spending limit.

## `categoryUrls` (type: `array`):

Used by Search and search monitoring. Only public first-party etsy.com category or market URLs are accepted.

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

Required by Shop and shop monitoring. Enter a public shop name or etsy.com/shop URL.

## `listingTargets` (type: `array`):

Required by Exact and exact-listing monitoring. Enter numeric listing IDs or public etsy.com/listing URLs.

## `marketCountry` (type: `string`):

Two-letter country code used for public Etsy market context and shipping filters.

## `currency` (type: `string`):

Three-letter currency requested from Etsy. No conversion or exchange-rate inference is performed.

## `minPrice` (type: `number`):

Keep listings whose source-published current price reaches this amount.

## `maxPrice` (type: `number`):

Keep listings whose source-published current price does not exceed this amount.

## `itemType` (type: `string`):

Keep all products or require Etsy's explicit physical/digital signal. Missing signals do not pass a narrowed filter.

## `handmadeOnly` (type: `boolean`):

Keep only rows Etsy explicitly marks handmade; no handmade status is inferred from text.

## `onSaleOnly` (type: `boolean`):

Keep listings with a source-published original price above the current price.

## `freeShippingOnly` (type: `boolean`):

Keep listings Etsy explicitly marks as free shipping for the selected market.

## `includeAds` (type: `boolean`):

Keep explicitly labeled ad placements. Turn off to exclude them; unlabeled rows remain sponsored=null rather than guessed organic.

## `minimumRating` (type: `number`):

Keep rows whose source-published rating reaches this value.

## `minimumReviewCount` (type: `integer`):

Keep rows whose source-published review count reaches this value.

## `shopLocation` (type: `string`):

Case-insensitive deterministic filter on a source-published public shop location.

## `sort` (type: `string`):

Source-supported sorts are requested from Etsy. Cart and recent-purchase sorts deterministically reorder only explicit source signals; null values sort last.

## `detailLevel` (type: `string`):

Core uses search or shop cards only. Detailed opens extra public listing pages, adds source requests and runtime, and replaces the core listing charge with a paid detailed-listing event only when enrichment succeeds. Complete performs the same detail work and can add separately priced shop-profile rows. Public review-row delivery is not yet supported.

## `maxDetailListings` (type: `integer`):

Used only by Detailed/Complete. Rows after this limit remain core and are not billed as detailed results.

## `maxShopProfiles` (type: `integer`):

Used only by Complete when a workflow did not already open that shop page.

## `monitorTargetType` (type: `string`):

Chooses which target family Track changes compares. Search/shop absence is never deletion; exact listing 404 is still reported cautiously.

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

Used only by Track changes. Both choices fetch the same public source scope. Seed-only stores the first baseline without paid NEW rows; Emit current returns those rows as paid NEW events and can cost more. Each repeat run compares with the saved same-scope baseline.

## `stateNamespace` (type: `string`):

Separates independent baselines. Runs for the same exact namespace and scope must be sequential.

## `maxPagesPerTarget` (type: `integer`):

Stops pagination for each query, category or shop while preserving useful rows and explicit coverage.

## `maxRequests` (type: `integer`):

Internal run-wide navigation ceiling.

## `maxSourceBytes` (type: `integer`):

Internal run-wide source transfer ceiling.

## `maxRuntimeSecs` (type: `integer`):

Internal source-work runtime ceiling.

## `detailConcurrency` (type: `integer`):

Internal maximum concurrent detail/profile operations.

## Actor input object example

```json
{
  "workflow": "search_products",
  "queries": [
    "handmade silver ring"
  ],
  "maxListings": 25,
  "maxBuyerChargeUsd": 5,
  "marketCountry": "US",
  "currency": "USD",
  "itemType": "all",
  "handmadeOnly": false,
  "onSaleOnly": false,
  "freeShippingOnly": false,
  "includeAds": true,
  "sort": "relevance",
  "detailLevel": "core",
  "maxDetailListings": 25,
  "maxShopProfiles": 5,
  "monitorTargetType": "shop",
  "firstRunBehavior": "seed_only",
  "stateNamespace": "default",
  "maxPagesPerTarget": 5,
  "maxRequests": 105000,
  "maxSourceBytes": 512000000,
  "maxRuntimeSecs": 1800,
  "detailConcurrency": 2
}
```

# Actor output Schema

## `dataset` (type: `string`):

Open all delivered listing, shop, change and free coverage rows from this run.

## `listings` (type: `string`):

Open the mixed Dataset with useful listing and market-signal columns selected; recordType remains visible.

## `shops` (type: `string`):

Open the mixed Dataset with public shop-profile columns selected; recordType remains visible.

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

Open NEW, UPDATED and free NOT\_OBSERVED columns. Absence is never presented as deletion.

## `coverage` (type: `string`):

Open the mixed Dataset with target completion, cap, block and field coverage columns selected.

## `runSummary` (type: `string`):

Open the buyer-safe run summary with coverage, delivered counts, charge information and monitoring-state status.

# 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 = {
    "workflow": "search_products",
    "queries": [
        "handmade silver ring"
    ],
    "maxListings": 25,
    "maxBuyerChargeUsd": 5,
    "marketCountry": "US",
    "currency": "USD",
    "itemType": "all",
    "handmadeOnly": false,
    "onSaleOnly": false,
    "freeShippingOnly": false,
    "includeAds": true,
    "sort": "relevance",
    "detailLevel": "core",
    "maxDetailListings": 25,
    "maxShopProfiles": 5,
    "monitorTargetType": "shop",
    "firstRunBehavior": "seed_only",
    "stateNamespace": "default",
    "maxPagesPerTarget": 5,
    "maxRequests": 105000,
    "maxSourceBytes": 512000000,
    "maxRuntimeSecs": 1800,
    "detailConcurrency": 2
};

// Run the Actor and wait for it to finish
const run = await client.actor("luminar/etsy-product-shop-listing-change-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 = {
    "workflow": "search_products",
    "queries": ["handmade silver ring"],
    "maxListings": 25,
    "maxBuyerChargeUsd": 5,
    "marketCountry": "US",
    "currency": "USD",
    "itemType": "all",
    "handmadeOnly": False,
    "onSaleOnly": False,
    "freeShippingOnly": False,
    "includeAds": True,
    "sort": "relevance",
    "detailLevel": "core",
    "maxDetailListings": 25,
    "maxShopProfiles": 5,
    "monitorTargetType": "shop",
    "firstRunBehavior": "seed_only",
    "stateNamespace": "default",
    "maxPagesPerTarget": 5,
    "maxRequests": 105000,
    "maxSourceBytes": 512000000,
    "maxRuntimeSecs": 1800,
    "detailConcurrency": 2,
}

# Run the Actor and wait for it to finish
run = client.actor("luminar/etsy-product-shop-listing-change-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 '{
  "workflow": "search_products",
  "queries": [
    "handmade silver ring"
  ],
  "maxListings": 25,
  "maxBuyerChargeUsd": 5,
  "marketCountry": "US",
  "currency": "USD",
  "itemType": "all",
  "handmadeOnly": false,
  "onSaleOnly": false,
  "freeShippingOnly": false,
  "includeAds": true,
  "sort": "relevance",
  "detailLevel": "core",
  "maxDetailListings": 25,
  "maxShopProfiles": 5,
  "monitorTargetType": "shop",
  "firstRunBehavior": "seed_only",
  "stateNamespace": "default",
  "maxPagesPerTarget": 5,
  "maxRequests": 105000,
  "maxSourceBytes": 512000000,
  "maxRuntimeSecs": 1800,
  "detailConcurrency": 2
}' |
apify call luminar/etsy-product-shop-listing-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,luminar/etsy-product-shop-listing-change-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/Utz31shGl7avI6uIA/builds/wEBgV6XKQwnVX47TZ/openapi.json
