# Product Research for Etsy Listings via Etsy's Open API (`wheaten_steelpan/listing-market-research`) Actor

Product and market research for Etsy sellers: up to 12,000 active listings per keyword from Etsy's Open API, with views per day, favorite rate, real listing age, tags and shop sales, plus a free keyword summary. You pay only for listings delivered.

- **URL**: https://apify.com/wheaten_steelpan/listing-market-research.md
- **Developed by:** [Vanja V](https://apify.com/wheaten_steelpan) (community)
- **Categories:** E-commerce, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 listing delivereds

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

## Product Research for Etsy Listings via Etsy's Open API

Type a product idea such as `crochet pattern` and get up to 12,000 active listings for it. Each listing
comes with its views, views per day, favorite rate, real age, tags and the shop's total sales. Use it
to size a niche before you make the product: how much attention its listings get, what they charge,
how long the strong sellers have been around, and which shops you would be up against.

> The term 'Etsy' is a trademark of Etsy, Inc. This Application uses Etsy's API, but is not endorsed or certified by Etsy.

### What you can find out

- **Is there demand?** Views per day and favorite rate for every listing, and the median for the keyword.
- **Can a new listing break in?** `ageDays` shows whether the busiest listings are years old or a few weeks old.
- **What price works?** Every price in the niche, with the spread worked out for you.
- **Who is the competition?** The shop's total sales next to every listing, and the 10 biggest shops per keyword.
- **Which tags do sellers use?** The 20 most used tags per keyword.

### Read this before your first run

**Where the listings come from.** The data comes from Etsy's Open API, which keeps its own search
index. Its order differs from etsy.com search results, so a row's position says nothing about where
that listing appears on etsy.com. Use the depth and the numbers on each listing. In our tests, a full
"Recently renewed" pull contained a median 85% (72–86%) of the listings on etsy.com's first results
page for the same keyword; the first 1,000 "Best match" results contained 16%. To cover what shoppers
see, pull deep.

**Every run is live.** Each run fetches its listings fresh from Etsy's API, and nothing is cached
or reused between runs, so the numbers are as current as the run.

![Example output with invented data: title, price, currency, views, views per day, favorite rate, age in days, shop name and shop sales for 13 crochet pattern listings](https://api.apify.com/v2/key-value-stores/xFTZucwoyhFo58jNc/records/market-overview.png)

*Example only: these listings and shops are invented. Etsy's API terms don't allow showing listing
data older than six hours, so real results can't appear here. Your run returns these columns with live
Etsy data.*

**Up to 12,000 listings per keyword.** That is the most Etsy's API serves for one search. Full pulls
in our tests delivered 11,799–11,948 unique listings.

**Listing age is the real age.** Etsy resets a listing's creation date every time it renews, and
81–88% of "Recently renewed" results are renewals. `ageDays` and `viewsPerDay` count from the day
the listing was first created, so a three-year-old bestseller does not pass for a new hit. `listedAt`
keeps Etsy's renewal date if you want it.

**Prices are in the seller's own currency.** Nothing is converted; the `currency` column says which
one. In one 100-listing sample, 17 were priced in something other than USD.

**The price filter matches variations.** Etsy includes a listing when any of its variations is in
your range, so about 5% of rows show a price outside it. Each of those rows says so in `priceNote`.

**Repeats are dropped for free.** Etsy sometimes hands over the same listing twice deep in a
search. Repeats are removed, never charged, and counted in the `SUMMARY` record.

**Coverage.** Across 14,971 listings in our tests, views, favorites, original creation date, shop
sales and image were present on 100% of rows, and tags on 97.1–99.6%.

### Input

| Setting | What to enter | Default |
|---|---|---|
| `keywords` | One search per line: a keyword, or a search URL copied from etsy.com. Filters in a pasted URL are applied; any the API cannot apply are named in the run's status and `SUMMARY`. | required |
| `maxResults` | Listings per keyword, up to 12,000. You are charged per listing delivered. | 1,000 |
| `sort` | Recently renewed, Best match, Price low to high, or Price high to low. A sort in a pasted URL wins. | Recently renewed |
| `minPrice`, `maxPrice` | Price range in USD, matched by Etsy against every variation (see above). A pasted URL keeps its own price filter. | none |
| `dedupeAcrossKeywords` | On: a listing found by two keywords is delivered and charged once, under the first keyword, and later keywords fetch further to make up their count. | off |

Example:

```json
{
  "keywords": ["crochet pattern", "amigurumi pattern"],
  "maxResults": 1000,
  "sort": "created",
  "dedupeAcrossKeywords": true
}
```

### Output

#### Listings (one row per listing)

The results open on the **Market overview** tab: title, price, currency, views, views per day,
favorite rate, age, shop name and shop sales. The **Full details** tab has every column below. Export
as CSV, Excel or JSON from the run's Output tab.

The shop fields and `imageUrl` come from a second Etsy lookup. If that lookup fails for some
listings, those fields are empty on those rows, and the run's status and `SUMMARY` say how many.

| Field | What it means |
|---|---|
| `keyword` | The search term or pasted URL this row came from |
| `listingId` | Etsy's id for the listing |
| `title` | Listing title |
| `url` | Link to the listing on Etsy |
| `price` | Listing price as a number, in `currency` |
| `currency` | Currency code of `price`, the seller's own currency |
| `priceNote` | Set when the price may be the cheapest of several variations, and when Etsy matched your price filter through a variation |
| `hasVariations` | Whether the listing offers variations |
| `quantity` | Units the seller has available |
| `views` | Lifetime views of the listing |
| `numFavorers` | How many shoppers favourited the listing |
| `favoriteRate` | Favorites divided by views (0.05 = 5%); empty when the listing has no views |
| `viewsPerDay` | Views divided by the listing's real age in days; empty for listings under a day old |
| `ageDays` | Days since the listing was first created (not since renewal) |
| `tags` | The listing's tags |
| `materials` | Materials the seller listed |
| `listedAt` | The created date Etsy shows, which resets every time the listing renews |
| `originalCreatedAt` | When the listing was first created, and the basis for ageDays |
| `endingAt` | When the current listing period ends |
| `whoMade` | Who made the item, as the seller declared it: i_did, collective (a team member) or someone_else |
| `whenMade` | When the item was made, as the seller declared it, e.g. made_to_order or 2020_2026 |
| `productionPartners` | Production partners the seller declared, as "Name (location)", e.g. a print-on-demand shop |
| `isDigital` | Whether the listing is a digital download |
| `shopId` | Etsy's id for the shop |
| `shopName` | Shop name |
| `shopSales` | Total sales for the shop |
| `shopFollowers` | How many shoppers favourited the shop |
| `shopRating` | Average shop review score |
| `shopReviewCount` | Number of shop reviews |
| `imageUrl` | Main listing photo, 570 px wide |

#### Keyword summaries (free)

The `KEYWORD_SUMMARIES` record holds one summary per keyword: Etsy's total count of active listings,
the price spread (in the most common currency), the median views per day and favorites, the 20 most
used tags, and the 10 shops with the most sales among the listings delivered.

#### Completeness check (free)

The `SUMMARY` record shows, per keyword, how many listings you asked for, how many Etsy reports, how
many its API will serve, how many were delivered, how many repeats were dropped, and a verdict:

| Verdict | Meaning |
|---|---|
| `complete` | You have every listing Etsy's API serves for this keyword. |
| `capped-by-request` | The run stopped at your `maxResults`. Raise it for more. |
| `capped-by-etsy` | Etsy has more than 12,000 listings and serves no further. |
| `incomplete` | The run stopped early for another reason; the note says why. |

A run ends as failed only when it delivered nothing for a reason other than "Etsy has no matches",
and then nothing is charged.

### Pricing

**$0.005 per listing delivered.** No start fee. The keyword summaries and the completeness check are free.

| Listings | Cost |
|---|---|
| 100 | $0.50 |
| 1,000 (the default) | $5.00 |
| One full keyword (11,800–11,950) | about $59–60 |

If you set a maximum charge for the run, it stops at the last listing that limit covers and says so
in its status and `SUMMARY`. Nothing past that point is delivered or charged. Repeats Etsy hands
over are never charged.

### Speed

On Apify, 1,000 listings take about 17 seconds and a full 12,000 about 2½ minutes.

### Tips

- Sizing a niche: pull deep (5,000–12,000) with Recently renewed, then sort by `viewsPerDay`.
- Read `favoriteRate` together with `views`: a high rate on 30 views is noise.
- Comparing several keywords in one run: turn on `dedupeAcrossKeywords` so a listing that matches
  two of them is paid for once.

> The term 'Etsy' is a trademark of Etsy, Inc. This Application uses Etsy's API, but is not endorsed or certified by Etsy.

# Actor input Schema

## `keywords` (type: `array`):

One search per line: a keyword such as crochet pattern, or a search URL copied from etsy.com. Filters in a pasted URL are applied; any the API cannot apply are named in the run's status and SUMMARY, never dropped silently. Results come from Etsy's API index, in a different order from etsy.com search.

## `maxResults` (type: `integer`):

How many listings to return for each keyword. Etsy's API serves at most 12,000 per keyword. You are charged per listing delivered.

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

Order Etsy hands listings over in. Recently renewed reaches the most of etsy.com's first page at full depth. A sort in a pasted URL wins over this one.

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

Sent to Etsy, which matches a listing when any of its variations is in range. Such a listing can show a price outside the range; its priceNote says so. Applies to keywords; a pasted URL keeps its own price filter.

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

Sent to Etsy, which matches a listing when any of its variations is in range. Applies to keywords; a pasted URL keeps its own price filter.

## `dedupeAcrossKeywords` (type: `boolean`):

On: a listing found by two keywords is delivered and charged once, under the first keyword; later keywords fetch further to make up the count, and their summaries leave those listings out. Off: every keyword gets its full set.

## Actor input object example

```json
{
  "keywords": [
    "crochet pattern"
  ],
  "maxResults": 100,
  "sort": "created",
  "dedupeAcrossKeywords": false
}
```

# Actor output Schema

## `data` (type: `string`):

One row per active listing: price and currency, views, favorites, views per day, favorite rate, age, tags, and the shop's sales, followers and rating.

## `keywordSummaries` (type: `string`):

Per keyword: Etsy's count of active listings, price spread, median views per day and favorites, the 20 most used tags and the 10 biggest shops in the sample.

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

Per keyword: what you asked for, Etsy's count, what the API can serve (12,000 at most), what was delivered, duplicates dropped, and why any keyword stopped short. Only delivered listings are charged.

# 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 = {
    "keywords": [
        "crochet pattern"
    ],
    "maxResults": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("wheaten_steelpan/listing-market-research").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 = {
    "keywords": ["crochet pattern"],
    "maxResults": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("wheaten_steelpan/listing-market-research").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 '{
  "keywords": [
    "crochet pattern"
  ],
  "maxResults": 100
}' |
apify call wheaten_steelpan/listing-market-research --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wheaten_steelpan/listing-market-research"
        }
    }
}
```

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/2eScr4NATfLTcscRO/builds/cQfEB6NErbiIwX0dt/openapi.json
