# Etsy Listings Scraper (`nice_dev/etsy-listings-scraper`) Actor

Scrape Etsy products by keyword, category, shop or listing URL: price, rating, reviews, ads and organic rank, bestseller badges. Reviews, shop details and emails as options. Export to JSON, CSV or Excel.

- **URL**: https://apify.com/nice_dev/etsy-listings-scraper.md
- **Developed by:** [Nice Dev](https://apify.com/nice_dev) (community)
- **Categories:** E-commerce, Lead generation, MCP servers
- **Stats:** 6 total users, 5 monthly users, 96.4% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.36 / 1,000 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?

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

### 🛒 What is Etsy Listings Scraper?

**Etsy Listings Scraper** extracts **listings from [Etsy](https://www.etsy.com)**: **title, price, discount, shop rating, shop, badges, ad slot and organic rank**, for any keyword, category, shop or listing. Reviews, shop details, the shop's contact e-mail, demand signals and the full listing data are optional extras. It works as an **Etsy API alternative**: run it on a schedule, call it from your code, or plug it into Make, Zapier or n8n.

Type a **keyword** (`ceramic mug`), paste Etsy search, category, shop or listing URLs, or give shop names and listing IDs, click **Start**, and download the listings in JSON, CSV or Excel. No login, no API key, nothing to set up.

### 📋 What data can you extract from Etsy?

One item per listing, 76 fields:

| Category | What you get |
| --- | --- |
| 🏷️ **Listing** | title, link, the Etsy id — `Personalized Ceramic Mug: Handmade Pottery Cup` |
| 💰 **Price and discount** | price, price before discount, discount in percent, currency — `32.77 EUR`, `30% off` |
| ⭐ **Rating** | the shop's average rating and number of reviews shown on the listing's card — `4.9 (153 reviews)`; the rating and reviews of the listing itself are an option (Listing page) |
| 🏪 **Shop** | shop name and link, owner's first name, age of the shop, where it ships from — `10 years on Etsy` |
| 📈 **Ads and ranking** | ad or organic, place of the ad, Etsy's own organic rank over all pages, page, position, number of results of the search — `organic rank 2` |
| 🏅 **Badges** | Bestseller, Etsy's Pick, Star Seller, free shipping, on sale, and the badges Etsy rates the listing eligible for even when it does not show them |
| 📝 **Full listing details** (option) | description, tags, materials, favorites, stock, category, every photo, made to order / digital / customizable, creation date, price in US dollars, the country code it ships from |
| 🔥 **Demand signals, variations and shipping** (option) | "In 20+ carts", people who bought it in the last 24 hours, "only 2 left", colors and sizes, price range, shipping cost and delivery estimate, returns and their window in days, the listing's own rating and reviews, video |
| 💬 **Reviews** (option) | rating, text, date, reviewer, photos, seller reply |
| 🧾 **Shop details** (option) | total sales, shop rating, location, opening year, Star Seller, active listings, links, banner, tagline, team and shop policies |
| 📧 **Shop e-mail** (option) | the shop's public contact e-mail |
| 🔎 **Source** | the keyword and the search URL that found the listing, the time it was read |

Every field, with an example, is listed in the **Output** section below. The fields of an option are empty unless you tick that option.

### ✅ Why use Etsy Listings Scraper?

- 🚀 **Simple and fast to start**: a keyword is enough, and the run gives you the listings page after page without any set-up (in our tests, 100 listings usually take 1 to 3 minutes, 1,000 listings 2 to 15 minutes).
- 🎯 **Four ways in, in the same run**: keywords, search or category URLs, shops, single listings by ID or URL. They add up, and a listing found twice is returned once.
- 📈 **Ads told apart from organic results**: for each listing, whether it is an ad, its place among the ads and Etsy's own rank among the organic results.
- 🧩 **Extras à la carte**: listing details, demand signals, reviews, shop details and the shop's e-mail, each one unticked until you tick it and charged only for what it reads or brings you.
- 🗂️ **Several searches in one run**, with a cap per search so that one big keyword cannot eat the whole budget.
- 🔔 **Monitoring built in**: tick **Only new listings**, schedule the Actor, and each run returns (and charges) only what it has never delivered before.
- 📅 **Filters the Actor applies for you**: shop rating, number of shop reviews, favorites, Bestsellers, Etsy's Picks, no ads, words to exclude, listing date — filtered-out listings are not saved and never count in `maxItems`. Etsy's own filters (ready to ship in 1 or 3 days, Etsy's Pick, free shipping, price…) are sent with each search.
- 🔌 API, scheduling, monitoring, integrations (Make, Zapier, n8n, Google Sheets…), proxy rotation and JSON/CSV/Excel export via the Apify platform.

### 🚀 How to scrape Etsy

1. Create a free Apify account.
2. Open **Etsy Listings Scraper** and type a **Search keyword** (e.g. `ceramic mug`).
3. Or paste your own Etsy URLs into **Start URLs** (search results with all their filters, category pages, shop pages, single listings), give **Shop names**, or give **Listing URLs or IDs**: everything you give is read in the same run.
4. Set **Max listings** (100 by default, 0 = no limit) — and **Max listings per search** when you run several searches or shops — then click **Start**.
5. Download the dataset in JSON, CSV, Excel or via API.

### 💰 How much does it cost to scrape Etsy?

This Actor uses **pay per event** pricing: you pay for each listing saved, plus a small fee for each run start. The extras are charged on top, only when you tick them: **Full listing details** (a higher price per listing, instead of the standard one), **Listing page** (per listing page read, even when the page shows no demand signal), **Reviews** (per review returned), **Shop details** (per listing that carries them) and **Shop e-mail** (per listing whose shop e-mail is found). The filters the Actor applies itself (shop rating, shop reviews, favorites, listing date, Bestsellers, Etsy's Picks, ads, excluded words) work on every listing it reads: a listing you keep costs its normal price, a listing a filter drops costs a small filter fee, lower than a listing (a little more when the filter needs the listing's own data) — filtering never costs more than taking everything. Platform usage (compute, proxy) is included in the price.

### ⚙️ Input

```json
{
    "query": "ceramic mug",
    "maxItems": 200
}
```

Several searches in one currency, a cap per search, only the listings not delivered before:

```json
{
    "searchQueries": ["ceramic mug", "linen apron"],
    "sortBy": "price_asc",
    "minPrice": 10,
    "maxPrice": 60,
    "freeShippingOnly": true,
    "currency": "GBP",
    "country": "GB",
    "maxItemsPerQuery": 50,
    "onlyNew": true,
    "stateKey": "mugs-uk"
}
```

Or with your own URLs, a shop and a listing:

```json
{
    "startUrls": [
        { "url": "https://www.etsy.com/search?q=ceramic+mug&order=price_asc" },
        { "url": "https://www.etsy.com/c/home-and-living/kitchen-and-dining/drink-and-barware/drinkware/mugs" }
    ],
    "shopNames": ["KJPottery"],
    "listingUrls": ["1179605879"],
    "maxItems": 500
}
```

Only the listings that ship within 3 days, sorted by Etsy's reviews order, Etsy's Picks only:

```json
{
    "query": "ceramic mug",
    "sortBy": "highest_reviews",
    "readyToShipWithin": "3",
    "etsysPickOnly": true,
    "maxItems": 100
}
```

With the extras you tick (each one is charged only when you tick it, see Pricing):

```json
{
    "query": "ceramic mug",
    "maxItems": 50,
    "includeDetails": true,
    "includeReviews": true,
    "maxReviewsPerListing": 20,
    "includeShopDetails": true
}
```

| Field | Notes |
| --- | --- |
| `query`, `searchQueries` | Free-text search, exactly as typed in Etsy's search box: `ceramic mug`. `searchQueries` adds more keywords, one search each (up to 100 more). |
| `startUrls` | Search results (every filter set on the site is kept), category pages, shop pages and single listing pages, e.g. `https://www.etsy.com/shop/KJPottery`. Paste them one by one: importing a file of links is not supported. Pagination is automatic. They add up with the keywords, shops and listings. |
| `shopNames` | Shop names or shop URLs: all the active listings of each shop, e.g. `KJPottery`. |
| `listingUrls` | Single listings by ID or URL, e.g. `1179605879`. |
| `maxItems` | Stop after this many listings for the whole run (`0` = unlimited). |
| `maxItemsPerQuery` | Cap for EACH search (keyword, search URL or shop). `0` = no per-search cap. |
| `sortBy` | Etsy's order: `most_relevant` (default), `price_asc`, `price_desc`, `date_desc` (most recent) or `highest_reviews` (Top customer reviews). Keyword searches only: shops, listings and pasted URLs are read without it. |
| `minPrice`, `maxPrice` | Price bounds, in whole units of the currency Etsy shows. Empty = no bound. Keyword searches only: shops, listings and pasted URLs are read without them. |
| `freeShippingOnly`, `onSaleOnly`, `customizableOnly`, `giftWrapOnly` | Etsy's own yes/no filters, sent with each keyword search. Keyword searches only: shops, listings and pasted URLs are read without them. |
| `listingType` | `physical` (default), `digital` (instant downloads only) or `all`. Keyword searches only: shops, listings and pasted URLs are read without it. |
| `readyToShipWithin` | Etsy's Ready to ship filter: `any` (default, no filter), `1` or `3` days. Sent with each keyword search and added to a pasted search or category URL that has no such filter of its own; shops and listings given by ID or URL are not filtered on it. |
| `shipTo`, `location`, `country` | 2-letter country codes such as `US`, `GB` or `DE`: only listings that ship there, only shops located there, and the country Etsy shops for. `shipTo` and `location` apply to keyword searches only: shops, listings and pasted URLs are read without them. |
| `currency` | Currency of the prices: `USD` (default), `EUR`, `GBP`… |
| `includeDetails`, `includeDemandSignals`, `includeReviews`, `includeShopDetails`, `includeShopEmail` | The extras, all unticked by default and each charged only when ticked (see Pricing). The e-mail needs the shop details. |
| `maxReviewsPerListing` | Cap on the reviews returned for each listing (`20` by default, `0` = all the reviews found). |
| `postedAfter`, `postedBefore` | Listing date range: `2026-09-01`, or a period before now (`7 days`, `2 weeks`, `1 month`, `24 hours`). |
| `excludeKeywords` | Drop the listings whose title contains one of these words (case and accents ignored): `personalized`. |
| `minRating`, `minReviews`, `minFavorites` | Drop the listings below these numbers. Rating and reviews are the shop's, as the card shows them (searches and categories), not the listing's own. |
| `bestsellersOnly`, `starSellerOnly`, `etsysPickOnly`, `excludeAds` | Keep only Bestsellers, Star Sellers or Etsy's Picks (Etsy filters its searches and categories itself); drop the ads. |
| `onlyNew`, `stateKey`, `resetState` | Monitoring: only the listings never delivered under this memory key, e.g. `mugs-uk`; `resetState` forgets the memory. |
| Advanced | `proxyConfiguration` (Apify proxy by default, included in the price; the residential proxy is not available), `maxConcurrency`, `maxRequestsPerMinute`, `minRequestIntervalMs`, `maxRequestRetries`, `debugLog`. |

### 📦 Output

One listing of a keyword search, shortened to the main fields:

```json
{
    "id": "4557204445",
    "url": "https://www.etsy.com/listing/4557204445",
    "title": "Minimally striped mug, Custom ceramic cup with pet portrait, Handmade personalized cat and dog statue cup, 400ml cup",
    "price": 10.71,
    "originalPrice": 18.15,
    "discountPercent": 41,
    "currency": "EUR",
    "imageUrl": "https://i.etsystatic.com/13079015/r/il/71c32c/8440244305/il_fullxfull.8440244305_pm5t.jpg",
    "shopRating": 4.9,
    "shopReviewCount": 153,
    "shopName": "VALLDEAROMAS",
    "shopUrl": "https://www.etsy.com/shop/VALLDEAROMAS",
    "shopOwnerName": "Rosa",
    "shopAge": "10 years on Etsy",
    "isAd": false,
    "adSlot": null,
    "organicRank": 2,
    "position": 5,
    "page": 1,
    "searchTotalResults": 691995,
    "badges": ["Star Seller"],
    "eligibleBadges": [],
    "isBestseller": false,
    "isEtsysPick": false,
    "isStarSeller": true,
    "hasFreeShipping": false,
    "isOnSale": true,
    "images": ["https://i.etsystatic.com/13079015/r/il/71c32c/8440244305/il_fullxfull.8440244305_pm5t.jpg"],
    "searchQuery": "ceramic mug",
    "searchUrl": "https://www.etsy.com/search?q=ceramic+mug&locale_override=EUR%7Cen-US%7CIT&page=1",
    "scrapedAt": "2026-10-02T09:00:00.000Z"
}
```

You can download the dataset in various formats such as JSON, HTML, CSV or Excel.

#### All 76 fields

| Fields | What you get |
| --- | --- |
| `id`, `url`, `title` | **Listing** — `4557204445`, `https://www.etsy.com/listing/4557204445`, the title |
| `price`, `originalPrice`, `discountPercent`, `currency` | **Price** — `10.71`, `18.15` (before the discount), `41`, `EUR`. A listing given by ID or URL has no card: its price is the one before the sale, unless the Demand signals option reads its page, which gives the sale price, the price before it and the discount |
| `priceUsd` | the price converted to US dollars by Etsy (Full listing details) |
| `imageUrl`, `images`, `imageCount` | **Photos** — the first photo; every photo and their number (Full listing details). Every photo is the full size (`il_fullxfull`), whichever way the listing was found |
| `shopRating`, `shopReviewCount` | **Shop rating** — `4.9`, `153`: the SHOP's rating and number of reviews as the card shows them, the same on all the listings of a shop; the count is rounded by Etsy above 999 on a search page (`3.5k` gives `3500`, `12k` gives `12000`) and exact on a category page |
| `shopId`, `shopName`, `shopUrl` | **Shop** — `13079015`, `VALLDEAROMAS`, the shop page |
| `shopOwnerName`, `shopAge`, `shopLocation` | the owner's first name, `10 years on Etsy`, the place of the shop, as the card writes it under "Ships from": a city and a country (`London, United Kingdom`), a city and a US state, or a country alone. An object printed on demand can ship from another country: see `shipsFromCountryCode` |
| `isAd`, `adSlot`, `organicRank`, `position`, `page`, `searchTotalResults` | **Ranking** — ad or not, place among the ads, Etsy's own rank among the organic results (all pages counted), place on its page, page, results announced for the search |
| `badges`, `eligibleBadges` | **Badges** — `Star Seller`; the ones Etsy rates the listing eligible for, shown or not |
| `isBestseller`, `isEtsysPick`, `isStarSeller`, `hasFreeShipping`, `isOnSale` | the same badges as yes / no, and `null` when the page that found the listing does not show that badge (a shop page prints no Bestseller or Etsy's Pick, a listing given by ID or URL has no card): `null` is not "no". For a listing given by ID or URL, the On sale flag is filled only when the Demand signals option reads its page. `isBestseller` is also yes when Etsy's own flag of the listing says so (Full listing details completes the card). `isEtsysPick` is also yes for any organic listing of a search Etsy filtered on Etsy's Picks, even when its card prints another badge, such as Bestseller |
| `description`, `tags`, `materials`, `etsySummary` | **Full listing details** — the seller's text, the tags, the materials, the short summary Etsy writes when it has one. A list of an option you did not tick (`tags`, `materials`, `categoryPath`, `demandSignals`) is `null`, an empty list means the page was read and had none |
| `favorites`, `quantity` | people who favorited it, stock |
| `categoryPath`, `categoryId`, `categoryUrl`, `sectionName` | Etsy category from top to leaf, its id and page, the section of the shop |
| `whenMade`, `isMadeToOrder`, `isDigital`, `isCustomizable`, `hasVariations`, `isSoldOut`, `isTopRated`, `isAvailable`, `isOnVacation` | what the seller says and the state of the listing and of its shop |
| `publishedAt`, `updatedAt` | `2026-09-29T13:39:47.000Z` — creation (or last renewal) and last update of the listing |
| `shipsFromCountryCode` | the 2-letter code of the country the listing ships from, e.g. `TR` (Full listing details, or the Listing page); `shopLocation` above is the place of the shop |
| `inCartCount`, `boughtLast24h`, `stockLeft`, `demandSignals` | **Demand signals** (Listing page) — `20` for "In 20+ carts", `2`, `3` for "only 3 left", and the texts as Etsy shows them |
| `rating`, `reviewCount` | **Listing rating** (Listing page) — the rating and number of reviews of the listing itself, e.g. `4.9` and `81`, as its page gives them; the card's are the shop's |
| `variations`, `priceMin`, `priceMax` | the choices (color, size…) with the price of each one when shown, the lowest and highest price |
| `shipsFromRegion`, `shippingCost`, `deliveryEstimate`, `returnsAccepted`, `returnWindowDays` | where it ships from, the shipping cost (`0` = free), the delivery estimate, whether returns are accepted and, when they are, the days of the returns window (`21` for "within 21 days") |
| `videoUrl`, `madeBy` | the listing's video, the first name the shop shows on the listing |
| `reviews` | **Reviews** — rating, text, date, reviewer, photos and the seller's reply, as a list |
| `shop` | **Shop details** — total sales, rating, location, opening year, Star Seller, active listings, links, banner, tagline, team and policies, as one object |
| `shopEmail` | **Shop e-mail** — the shop's public contact e-mail |
| `searchQuery`, `searchUrl`, `scrapedAt` | **Source** — the keyword, the search or shop page that found the listing, the ISO time it was read |

### 💡 Tips

#### How to get more results

Set `maxItems` to `0` and use a broad keyword. Etsy shows 20 pages, about 1,200 listings, for one search: when you ask for more, the Actor cuts the search into price ranges by itself and reads each one, and a listing found twice is saved once. For more than a keyword gives, add more keywords in `searchQueries`, or read whole shops with `shopNames` (all the active listings of a shop, up to about 10,000).

#### How to reduce costs

The price is per listing, so the levers are `maxItems`, `maxItemsPerQuery`, the filters (a filtered-out listing costs only the filter fee, lower than a listing) and `onlyNew` for recurring runs (you never pay twice for the same listing). Tick only the extras you need: each one is charged only when you tick it (the listing page for each page read, even when it shows no demand signal), and the listing's own price stays the same without them.

#### Four sources in one run

Keywords, search or category URLs, shop names and listing IDs add up: a run can read a keyword, two shops and ten listings together. A listing found by several of them is saved — and charged — once. A shop gives all its active listings with the data a shop page shows: price, discount, badges, photo (no rating, no review count, no owner name, no ad or rank — those exist only in a search). A listing given by ID or URL gives its title, price, shop, photo and Bestseller flag, plus the extras you tick. Rating and review filters keep these listings (Etsy shows neither there), the Bestseller and Etsy's Pick filters keep a shop's listings (a shop page prints neither badge; when an option reads the listing's own data (Full listing details, reviews, shop details, dates, minimum favorites…), Etsy's own Bestseller flag is known and the Bestseller filter then applies it), and Etsy's Pick and Star Seller filters keep the listings given by ID or URL. Prices of a listing given by ID or URL are in the seller's own currency.

#### Monitoring: only the new listings

Tick **Only new listings** (`onlyNew`) and schedule the Actor. The first run returns everything; each later run skips the listings already delivered: they are not saved, not charged, and their listing data is not even read, except a listing given by ID or URL: its data is read at each run, then the listing is skipped, free of charge. The memory lives in a named key-value store of your account (`etsy-listings-scraper-seen`, up to 150,000 listings per key) and is only updated with listings that really reached the dataset, so a failed run never hides anything. Give each schedule its own `stateKey` (two schedules sharing a key would hide each other's listings), and tick `resetState` once to start over. With `onlyNew`, a listing that your filters drop is checked (and charged) once: the next runs with the same filters skip it for free. Change a filter, or tick `resetState`, to check those listings again.

#### Filter by listing date

`postedAfter` and `postedBefore` take a date (`2026-09-01`, the whole day is included, UTC) or a period before now (`7 days`, `2 weeks`, `1 month`; via the API also `24 hours` or a full ISO date-time). Etsy's result pages show no date, so the Actor reads the data of each listing it finds to get it, which makes such a run slower. The filter reads `publishedAt`, the creation or last renewal date of the listing; a listing without a date is dropped as soon as a date bound is set. Filtered-out listings are not saved and do not count in `maxItems` (each listing dropped costs the filter fee, see Pricing); the run summary tells how many were filtered. Sorted by date (**Most recent**) with `postedAfter`, the run stops at the first listing older than your date instead of reading (and charging) the older ones that follow.

### 🔌 Integrations and API

Call the Actor via the Apify API, the JavaScript or Python clients, or connect it with integrations and webhooks (Make, Zapier, n8n, Google Sheets, Slack, Airtable…). The dataset can be fetched as JSON or CSV from any tool.

### 🤖 Use with AI agents (MCP)

AI agents (Claude, ChatGPT, Cursor…) can find and run this Actor through the [Apify MCP server](https://mcp.apify.com), billed to their Apify account like any run. It returns one item per Etsy listing. Actor id: `nice_dev/etsy-listings-scraper`; MCP server with this Actor only: `https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/etsy-listings-scraper`.

Smallest input, for a cheap first call:

```json
{
    "query": "ceramic mug",
    "maxItems": 10
}
```

Key output fields: `url`, `title`, `price`, `currency`, `shopRating`, `shopReviewCount`, `shopName`, `isAd`, `organicRank` and `badges`.

Cost: pay per listing plus a small fee for each run start, see the pricing section above. Cap each call with `maxItems` and, through the API, with the run option `maxTotalChargeUsd`.

### ❓ FAQ

#### Is it legal to scrape Etsy?

The Actor only reads what Etsy shows publicly to any anonymous visitor. It logs in to nothing. Etsy's Terms of Use restrict automated access to its site: you are responsible for using the data in compliance with them and with the applicable law. Results can contain personal data, which is protected by GDPR: the first names of shop owners and the names of the people who wrote a review, as Etsy shows them publicly. Do not store or use them without a legitimate reason. This Actor is not affiliated with Etsy.

#### Does it need a login or a proxy?

No login. The proxy is included in the price: leave the default setting (the residential proxy is not available). A request the site turns away is retried at once on a new proxy session, up to 10 times on top of the retries (without a proxy, after a pause of 5 seconds, doubled at each retry up to 150 seconds).

#### Is the data safe to open in Excel or to show on a web page?

Titles, descriptions and reviews are the sellers' and buyers' own words, copied as they are. A text can begin with `-`, `+`, `=` or `@` (a title such as `-20% today`): Excel and Google Sheets may read such a cell of a CSV file as a formula or as a number. The Actor leaves the text as it is, so that the JSON and the API give the real value: when you open a CSV, import these columns as text. On a web page, escape every field like any text written by a stranger.

#### Known limitations

- Etsy filters that are not in the input can still be used by pasting a filtered search URL into `startUrls`.
- When you give more listings by ID or URL than **Max listings**, the Actor returns the first ones of your list.
- One search gives at most about 1,200 listings (20 pages of about 60 listings each, ads included). When **Max listings** asks for more, the search is cut into price ranges by the Actor: the order you chose and `organicRank` hold inside each range, not between them, so the first rows are not the first of the sort (with the lowest price first, a range of dearer listings is read beside the cheapest one). With `onlyNew` and the sort **Most recent**, only the first 20 pages are read: the new listings are on top. A shop gives up to about 10,000 listings. The sort by reviews (`highest_reviews`) is no deeper: Etsy shows its 20 first pages only.
- Ads and Etsy's rank exist only in searches and categories: `adSlot`, `organicRank`, `position`, `page` and `searchTotalResults` are `null` for shops and for listings given by ID or URL, and `isAd` is `false` there. A listing met first as an ad and also found among the organic results keeps `isAd` true and no `organicRank`, in that run.
- A shop page shows no rating, no review count, no owner name, no shop age and no origin; a listing given by ID or URL shows none of them either, and shows only the Bestseller badge. It has no discount either, unless the Demand signals option reads its page, which gives the sale price, the price before it and the discount. `shopRating`, `shopReviewCount` and these fields are then empty, and the yes / no badge fields the page does not show are `null`, not `false`.
- The stars of a card are the shop's (`shopRating`, `shopReviewCount`): the same couple on every listing of a shop. The listing's own `rating` and `reviewCount` come from its page, only when you tick **Listing page**. The ready-to-ship filter (`readyToShipWithin`) works on searches and categories: shops and listings given by ID are not filtered on it, and the log says so.
- Etsy gives a shop's reviews, not a listing's: with `includeReviews` the Actor keeps the reviews of the shop that point to the listing, among the latest 500 reviews of the shop at most, so older reviews of a listing can be missing.
- `inCartCount` is a lower bound: "In 20+ carts" gives `20`. Etsy shows these signals on some listings only, and they change from one moment to the next.
- `publishedAt` is the creation date of the listing, or the date of its last renewal: Etsy renews a listing's date when the seller renews it.
- `currency` is the currency the price of the row is shown in; `priceUsd` is Etsy's own conversion.
- `onlyNew` remembers listing ids, not their content: a listing whose price changed is not returned again.
- With `onlyNew`, a listing that does not pass a time-dependent filter yet (a relative "posted before" date, or a minimum count that keeps rising: favorites, shop reviews) is checked again on the next run.
- With `onlyNew`, a shop of more than 20 pages (about 720 listings) is read until 20 pages in a row hold only listings already delivered, then the run moves on: a new listing that Etsy shows deeper than that in the shop is not returned.
- If your filters drop almost everything that one search or shop returns (300 listings in a row), that search stops and the run goes on with the next ones; the run stops only when every search and shop is in that case.
- Two runs sharing the same `stateKey` at the same time may both return the same new listing (the ones they deliver within the same minute).

**A run that reaches its timeout** stops itself about 45 seconds before it: no new page is asked, what it read is saved and, with `onlyNew`, remembered, and the run ends *Succeeded* with "Stopped before the run's timeout". Resurrect it to go on from there, or give the next run a longer timeout (Run options).

**A run the platform stops without warning** (out of memory)

- Resurrect it: it goes on from where it stood at most a minute before the stop. What it had read since is read again, and the listings already saved are skipped: none is delivered or charged twice, and `maxItems` still counts them.
- With `onlyNew`, the memory is saved after each write (at most once every 2 seconds): resurrect the stopped run and the listings it had saved meanwhile join the memory; leave it stopped for good, and the next run may return the listings of its last 2 seconds once more.

#### Something doesn't work?

The last line of the log counts the listings saved, filtered out and no longer on Etsy (removed while the run was reading them), and the requests that failed after every retry. Those requests and the removed listings are listed, with the reason, in the `FAILED_REQUESTS` record of the run's key-value store (the first 200 of them). A shop that Etsy does not know is said in the log and gives no listing. A run that saved nothing and had failed requests fails, and its last message gives the cause (a search URL that does not exist says so, instead of "run it again").

If Etsy changes its pages, you are told instead of paying for blank rows. A results page that counts listings but gives none the Actor can read is an error (listed in `FAILED_REQUESTS`), never a quiet "No listings found". If the first 20 listings read all lack their title, price or shop name — or, when the listing data is read, their description — the run saves nothing more, stops and fails, and its last message names the missing field: at most those first listings are charged. A listing that a filter drops counts among those 20. A run of fewer listings (a scheduled `onlyNew` run that finds a few new ones, a small **Max listings**) is checked at its end, from 5 listings: its listings are already saved, and the run fails with the missing field instead of ending green.

### 🛟 Support

Open an issue in the **Issues** tab with a link to your run: the run log and the `FAILED_REQUESTS` record of the key-value store show exactly which URLs failed and why.

# Actor input Schema

## `startUrls` (type: `array`):

Etsy URLs to read: search results (`https://www.etsy.com/search?q=ceramic+mug`, all filters set on the site are kept), category pages (`/c/...`), shop pages (`/shop/<name>`: all the active listings of the shop) or single listings (`/listing/<id>`). Paste the URLs one by one: importing a file of links is not supported. Pagination is automatic. They add up with the keywords, shop names and listing IDs below: everything is read in one run, each listing is saved once, and the caps, the filters of the Filters section and monitoring apply to all of it. Max 1 000 URLs.

## `query` (type: `string`):

Free-text search, exactly as typed in Etsy's search box (e.g. `ceramic mug`). Leave empty to give only URLs, shops or listings.

## `searchQueries` (type: `array`):

Several keywords in one run: one search per keyword. Added to **Search keyword**; listings found by several searches are saved once.

## `shopNames` (type: `array`):

Etsy shop names (e.g. `KJPottery`) or shop URLs (`https://www.etsy.com/shop/KJPottery`): all the active listings of each shop, up to about 10 000. Use **Max listings per search** to cap each shop.

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

Single listings by ID (`1234567890`) or URL (`https://www.etsy.com/listing/1234567890/...`). No search page is read: each listing gives its title, price, shop and photo, plus the extras you tick below.

## `maxItems` (type: `integer`):

Maximum number of listings to save for the whole run (after deduplication and filters). 0 = no limit.

## `maxItemsPerQuery` (type: `integer`):

Cap for EACH search (keyword, search URL or shop), so that the first search cannot use up the whole **Max listings** budget. 0 = no per-search cap.

## `sortBy` (type: `string`):

Order of Etsy's results. Top customer reviews is Etsy's own sort by reviews. Etsy shows at most 20 pages of results, whatever the order. When Max listings asks for more than about 1,200, the search is read in price ranges: the order holds inside each range, not between them. With Most recent and Only new listings, only the first 20 pages are read. Keyword searches only: shops, listings and pasted URLs are read without it.

## `minPrice` (type: `integer`):

Lowest price, in the currency Etsy shows for the search. Empty = no minimum. Keyword searches only: shops, listings and pasted URLs are read without it.

## `maxPrice` (type: `integer`):

Highest price, in the currency Etsy shows for the search. Empty = no maximum. Keyword searches only: shops, listings and pasted URLs are read without it.

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

Only listings with free shipping. Keyword searches only: shops, listings and pasted URLs are read without it.

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

Only discounted listings. Keyword searches only: shops, listings and pasted URLs are read without it.

## `customizableOnly` (type: `boolean`):

Only listings the buyer can personalize. Keyword searches only: shops, listings and pasted URLs are read without it.

## `listingType` (type: `string`):

Physical items only (Etsy's own default for most searches), instant digital downloads only, or both. Keyword searches only: shops, listings and pasted URLs are read without it.

## `giftWrapOnly` (type: `boolean`):

Only listings the seller can gift-wrap. Keyword searches only: shops, listings and pasted URLs are read without it.

## `readyToShipWithin` (type: `string`):

Etsy's Ready to ship filter: only listings whose shop ships within 1 or 3 days of the order. Sent with each keyword search, and added to a pasted search or category URL that has no such filter of its own. Not applied to shops or to listings given by ID or URL, which Etsy cannot filter that way.

## `shipTo` (type: `string`):

Only listings that ship to this country, as a 2-letter code (`US`, `GB`, `DE`…). Empty = Etsy's default. Keyword searches only: shops, listings and pasted URLs are read without it.

## `location` (type: `string`):

Only shops located in this country, as a 2-letter code (`US`, `GB`, `FR`…). Empty = anywhere. Keyword searches only: shops, listings and pasted URLs are read without it.

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

Currency of the prices (and of **Min price** / **Max price**). Default: US dollars. A listing given by ID or URL shows its price in the seller's own currency.

## `country` (type: `string`):

Country Etsy shops for, as a 2-letter code (`US`, `GB`, `DE`…): it changes the results, availability and shipping offers, like a visitor of that country. Empty = the proxy's country.

## `includeDetails` (type: `boolean`):

Description, the tags (up to 13), materials, favorites, stock quantity, category path, creation date, all photos, made-to-order / digital / customizable flags, Etsy's item summary, the code of the country it ships from.

## `includeDemandSignals` (type: `boolean`):

Opens one more page per listing (the listing page itself, slower). Gives the demand signals ("In 20+ carts", "N people bought this in the last 24 hours", "Only N left"), the variations (colors, sizes, with the price of each option when shown), the lowest and highest price, the shipping cost, region and delivery estimate, whether returns are accepted and for how many days, the rating and number of reviews of the listing itself, the video and the first name the shop shows. Charged for each listing page read, even when the page shows no demand signal.

## `includeReviews` (type: `boolean`):

Buyer reviews of each listing: rating, text, date, reviewer, photos, seller reply.

## `maxReviewsPerListing` (type: `integer`):

Cap on the reviews returned for each listing when **Reviews** is ticked (500 at most: Etsy gives a shop's reviews, not a listing's, and the Actor reads the latest 500 reviews of the shop). 0 = as many as it finds, up to 500.

## `includeShopDetails` (type: `boolean`):

For each listing's shop: total sales, shop rating and review count, location, opening year, Star Seller, number of active listings, social links. One shop page per shop, reused for all its listings.

## `includeShopEmail` (type: `boolean`):

Public contact e-mail of the shop, looked up on the shop's own website and social profiles linked from Etsy. Needs **Shop details** ticked too. Charged only when an e-mail is found.

## `postedAfter` (type: `string`):

Only listings created (or last renewed) on or after this date — Etsy's listing date: `2026-09-01`, or a period before now such as `7 days`, `2 weeks`, `1 month` (API: `24 hours` and full ISO date-times work too). Etsy cards carry no date: the Actor reads each listing's data to apply it.

## `postedBefore` (type: `string`):

Only listings created on or before this date (the whole day is included), or older than a period such as `30 days`.

## `excludeKeywords` (type: `array`):

Drop the listings whose title contains one of these words (case and accents ignored).

## `minRating` (type: `number`):

Drop listings whose shop is rated below this (1-5; 0 = no filter): the shop rating the card shows, the same on all the listings of a shop. Listings without it are dropped too. Shop pages and listings given by ID or URL show no rating: their listings are kept. Empty or 0 = keep all.

## `minReviews` (type: `integer`):

Drop listings whose shop has fewer reviews than this: the shop review count the card shows, rounded by Etsy above 999 on a search page (1.7k = 1,700). Shop pages and listings given by ID or URL show no review count: their listings are kept. Empty = keep all.

## `bestsellersOnly` (type: `boolean`):

Keep only listings with Etsy's Bestseller badge. Shop pages show no such badge: their listings are kept, unless an option reads the listing's own data (Full listing details, reviews, shop details, dates, minimum favorites…): Etsy's flag is then known and applied. A listing given by ID or URL that Etsy gives no flag for is kept too.

## `starSellerOnly` (type: `boolean`):

Only listings of Star Seller shops (Etsy's own filter for keywords, applied by the Actor to the other sources). Not applied to listings given by ID or URL (their data does not say).

## `etsysPickOnly` (type: `boolean`):

Keep only Etsy's Picks: asked of Etsy for keyword searches and pasted search or category URLs (its ads are still checked on their badge), read on the badge for shop pages. Not applied to listings given by ID or URL (their data does not say).

## `minFavorites` (type: `integer`):

Drop listings favorited by fewer people than this (reads the listing data: a listing it drops is charged the filter fee, a listing it keeps costs nothing more). Empty = keep all.

## `excludeAds` (type: `boolean`):

Drop the sponsored (Ad) listings of the search pages and keep the organic ones only.

## `onlyNew` (type: `boolean`):

Skip the listings that a previous run (same **Memory key**) already delivered: they are not saved and not charged, and their page is not even opened, except a listing given by ID or URL: its data is read at each run, then the listing is skipped, free of charge. First run = everything is new.

## `stateKey` (type: `string`):

Name of the memory used by **Only new listings**. Give each schedule / task its own key (e.g. `mugs-us`) so that they do not share their memory. Letters, digits, `-` and `_`.

## `resetState` (type: `boolean`):

Forget everything remembered under this **Memory key** before the run: this run returns (and charges) every listing again. Untick it afterwards.

## `proxyConfiguration` (type: `object`):

Apify Proxy or your own proxies. Keep the default: it is included in the price. The residential Apify proxy is not available in this Actor.

## `maxConcurrency` (type: `integer`):

Maximum number of requests processed in parallel.

## `maxRequestsPerMinute` (type: `integer`):

Most requests to Etsy in any 60 seconds: search pages, listing pages, reviews and shop pages. A budget, not an even pace: up to this many can leave at once when the minute starts (spread them with the minimum delay below). Lower it if Etsy answers HTTP 429 / 403 in the log.

## `minRequestIntervalMs` (type: `integer`):

Smallest gap between two requests, in milliseconds. Unlike the per-minute rate, this spreads the requests evenly instead of letting them go out in a burst. 0 = no gap.

## `maxRequestRetries` (type: `integer`):

Retries per request before it is marked as failed. Behind a proxy, a request the site turns away is also retried on a new proxy session up to 10 times without using up these retries.

## `debugLog` (type: `boolean`):

Include debug messages in the run log.

## Actor input object example

```json
{
  "startUrls": [],
  "query": "ceramic mug",
  "searchQueries": [],
  "shopNames": [],
  "listingUrls": [],
  "maxItems": 100,
  "maxItemsPerQuery": 0,
  "sortBy": "most_relevant",
  "freeShippingOnly": false,
  "onSaleOnly": false,
  "customizableOnly": false,
  "listingType": "physical",
  "giftWrapOnly": false,
  "readyToShipWithin": "any",
  "currency": "USD",
  "includeDetails": false,
  "includeDemandSignals": false,
  "includeReviews": false,
  "maxReviewsPerListing": 20,
  "includeShopDetails": false,
  "includeShopEmail": false,
  "excludeKeywords": [],
  "bestsellersOnly": false,
  "starSellerOnly": false,
  "etsysPickOnly": false,
  "excludeAds": false,
  "onlyNew": false,
  "stateKey": "default",
  "resetState": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 8,
  "maxRequestsPerMinute": 120,
  "minRequestIntervalMs": 0,
  "maxRequestRetries": 5,
  "debugLog": false
}
```

# Actor output Schema

## `results` (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 = {
    "query": "ceramic mug",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("nice_dev/etsy-listings-scraper").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 = {
    "query": "ceramic mug",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("nice_dev/etsy-listings-scraper").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 '{
  "query": "ceramic mug",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call nice_dev/etsy-listings-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nice_dev/etsy-listings-scraper"
        }
    }
}
```

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/bQH2ahs8x6x2hq1js/builds/QF9E99EzgfIe8pI92/openapi.json
