Search Etsy for listings matching a query (free-form text, full search URL, listing-ID list, or shop URL) with the full filter surface — category, price range, item type, ship-to, color, sort, etc. — and return structured listing data (id, title, shop, price, original price, rating, badges, free-shipping, ad-flag, canonical URL) plus the fuzzy result count and active filter chips. Read-only.
Works with
AI-first code editor with Composer
Before installing skills in Cursor, ensure your development environment meets these requirements:
node --versionsearch-productsExecute the skills CLI command in your project's root directory to begin installation:
Fetches search-products from etsy.com/search-products-t1kplk and configures it for Cursor.
The CLI shows a list of agents. Use arrow keys and space to select Cursor:
Confirm successful installation by checking the skill directory location:
Restart Cursor to activate search-products. Access via /search-products in your agent's command palette.
We perform automated surface-level scans (Gen AI Scanner, Socket, Snyk) during installation. These checks detect common vulnerabilities but do not guarantee complete security. Always review skill source code and verify the publisher's reputation before production use.
Skills execute code in your environment. Always review source, verify the publisher, and test in isolation before production.
Submit your Claude Code skill and start earning
Automate repetitive workflows and reduce manual effort
Example
Generate reports, summarize documents, draft communications
Save 3-5 hours per week on routine tasks
Learn new skills, understand complex topics, get expert guidance
Example
Explain concepts, provide examples, suggest learning resources
Accelerate learning and skill development by 2x
Enhance output quality through reviews, suggestions, and refinements
Example
Review drafts, suggest improvements, catch errors
Improve work quality by 30-40% with less effort
0
total installs
0
this week
0
upvotes
Run in your terminal
0
installs
0
this week
—
stars
| name | search-products |
| title | Etsy Product Search |
| description | >- Search Etsy for listings matching a query (free-form text, full search URL, listing-ID list, or shop URL) with the full filter surface — category, price range, item type, ship-to, color, sort, etc. — and return structured listing data (id, title, shop, price, original price, rating, badges, free-shipping, ad-flag, canonical URL) plus the fuzzy result count and active filter chips. Read-only. |
| website | etsy.com |
| category | marketplace |
| tags | - etsy - marketplace - search - listings - datadome - read-only |
| source | 'browserbase: agent-runtime 2026-05-18' |
| updated | '2026-05-18' |
| recommended_method | browser |
| alternative_methods | - method: api rationale: >- Etsy's Open API v3 (openapi.etsy.com) is reachable and would be dramatically more reliable than scraping — but it's partner-gated, requiring an approved app's 'keystring:shared_secret' OAuth credential. Verified iter-1: a key-less request returns the literal error "Invalid API key: should be in the format 'keystring:shared_secret'." Without partner credentials this path is unusable; the generic agent runtime falls back to the browser surface. - method: url-param rationale: >- Every filter Etsy's UI exposes is reflected as a URL query param (q, order, min, max, ship_to, explicit, free_shipping, price_bucket, on_sale, accepts_returns, ready_to_ship_in_1_day, is_personalizable, is_customizable, is_made_to_order, min_rating, attr_<facet>, page, plus locale prefixes /uk/, /de-en/, /ca/, /au/). Construct the URL directly rather than driving the filter UI — same canonical surface, fewer clicks, fewer DataDome-trigger opportunities. - method: hybrid rationale: >- For category-browse without a keyword (`/c/{slug}` paths), Browserbase's lightweight Fetch API returns 200 OK and the same listing-card HTML as a full browser session — DataDome doesn't gate static category landings. Use Fetch for category-only browse; promote to a verified browser session as soon as a keyword query (`?q=`), search path (`/search`), or listing-detail page (`/listing/{id}`) is needed. |
| verified | true |
| proxies | true |
Search Etsy for listings matching a free-form query (or full search URL, or shop URL, or listing-ID list) and return the matching items as structured JSON — listing ID, title, shop name + ID, primary + alternate image URLs, current price + original price + discount %, sale-end datetime, rating + review count, "Bestseller" / "Star Seller" / "Etsy's Pick" badges, free-shipping flag, shipping-from country, ready-to-ship-in-N-days indicator, returns-accepted flag, "X people have this in their cart" social-proof, item-type (Handmade / Vintage / Craft Supply / Digital), the canonical /listing/{id}/{slug} URL, ad/sponsored flag, plus the page-wide "X,XXX results, with Ads" fuzzy total and the active filter chips. Read-only — never clicks Add to Cart, Buy it Now, Favorite, Sign In, or any purchase-flow control.
etsy.com/search?q=... URL, a keyword query ("hand-poured soy candle"), a keyword + category ("earrings in Jewelry"), a list of listing IDs, or a /shop/{name} URL.Etsy is DataDome-protected (not Akamai, despite what some older notes claim — confirmed iter-1 by Server: DataDome + X-Datadome: protected + X-Dd-B: 259 on every blocked response). The site renders the listing grid server-side as HTML — there is no JSON-LD blob and no window.__INITIAL_STATE__ on the search page; listing data lives in data-listing-id-attributed <div class="v2-listing-card"> cards under <div data-search-results>. Therefore: drive a remote Browserbase session with --verified --proxies, navigate to the canonical search URL, then parse the rendered HTML.
A lightweight HTTP fetch (Browserbase cloud fetch API, raw curl, partner-less Open-API-v3) does not work for search — see Site-Specific Gotchas.
Start from https://www.etsy.com/search and append the filter surface as query params. All filters are URL-driven — no cookie / POST state required. Locale prefixes (/uk/, /de-en/, /ca/, /au/, ...) work identically: https://www.etsy.com/uk/search?q=....
| Filter | Param | Notes |
|---|---|---|
| Query | q=<URL-encoded text> | Free-form. |
| Sort | order=most_relevant|most_recent|highest_price|lowest_price|top_customer_reviews | Default most_relevant. |
| Custom price | min=<USD> + max=<USD> | Whole dollars; min= or max= alone is fine. |
| Bucketed price | price_bucket=1 (<$25), 2 ($25-$50), 3 ($50-$100), 4 ($100-$200), 5 (>$200) | Maps to the chip-style price filter. |
| Item type | explicit=1 (Handmade), 2 (Vintage), 4 (Craft Supplies), 8 (Digital) | Bit-field — sum for multi (e.g. explicit=9 = Handmade + Digital). |
| Ship to | ship_to=<ISO-3166-α2> | e.g. US, GB, DE. Drives the "Ships to your country" chip. |
| Free shipping | free_shipping=true | |
| Accepts returns | accepts_returns=true | |
| Ready in 1 day | ready_to_ship_in_1_day=true | |
| On sale | on_sale=true | |
| Accepts gift cards | accepts_gift_cards=true | |
| Personalizable | is_personalizable=true | |
| Customizable | is_customizable=true | |
| Made to order | is_made_to_order=true | |
| Min rating | min_rating=4 | Only "4+ stars" is exposed in the UI. |
| Dynamic facets | attr_<facet>=<value> (URL-encoded) | Color, material, occasion, recipient, holiday, room. The facet keys are surfaced dynamically per category in the left-rail filter HTML. Color values use Etsy's canonical names: Beige, Black, Blue, Bronze, Brown, Clear, Copper, Gold, Gray, Green, Orange, Pink, Purple, Rainbow, Red, Rose+Gold, Silver, White, Yellow. |
| Page | page=N | ~64 listings per page; Etsy caps pagination at 250 pages (~16,000 results). |
| Category scope | use /c/{category-slug} path with the same filter params | e.g. https://www.etsy.com/c/home-and-living/candles-and-holders/candles?q=soy&min=25. |
| Shop scope | https://www.etsy.com/shop/{shopname}?search_query=<text> | Scopes search to a single seller. |
For a listing-ID list input, skip search entirely and hit https://www.etsy.com/listing/{listingId} directly per ID.
For a full search URL input, use it verbatim — Etsy's URL params are stable and the canonical form is what you receive.
DataDome is the wall. A bare session gets HTTP 403 + a DataDome CAPTCHA-delivery HTML stub. Verified mode (TLS-fingerprint + JS-challenge resolution) plus residential proxies (geo + IP-reputation diversity) is the minimum that produces a 200 in our testing.
SID=$(browse cloud sessions create --keep-alive --verified --proxies | jq -r .id)
export BROWSE_SESSION="$SID"
For non-US locales (/uk/, /de-en/, …) prefer a region-matched proxy: --region eu-central-1 for EU paths reduces the captcha rate.
browse open "https://www.etsy.com/search?q=hand-poured+soy+candle&order=most_recent" --remote
browse wait load --remote
browse wait selector --remote 'div[data-search-results]' # the grid container
browse wait timeout 1500 --remote # let lazy thumbnails settle
If browse get title --remote returns etsy.com (single-word) or browse get html body --remote contains Please enable JS and disable any ad blocker, DataDome blocked the load. Try once more with a fresh session; if still blocked, treat as the anti-bot wall.
HTML=$(browse get html body --remote)
Each result is a <div class="v2-listing-card" data-listing-id="..." data-shop-id="..." data-page-type="search"> inside <div data-search-results>. Iterate per card and extract:
| Field | Selector / regex |
|---|---|
listing_id | [data-listing-id] (or [data-palette-listing-id]) |
shop_id | [data-shop-id] |
canonical_url | a.listing-link[href] — strip the tracking suffix: drop everything from ?click_key=… onward. Final form: https://www.etsy.com/listing/{id}/{slug}. |
title | a.listing-link[title] (also the <h3 class="v2-listing-card__title"> text) |
image_url | img[data-listing-card-listing-image][src]; srcset carries 1x / 2x variants |
rating_decimal | input[name="rating"][value] (e.g. "4.91") — there are two inputs (initial-rating + rating); they're identical |
rating_count | the (N,NNN) text node next to the star sprite |
star_seller | presence of .star-seller-badge-lavender-text-light / text "Star Seller" |
etsys_pick | text "Etsy's Pick" or search_collage-promotion-* class |
bestseller | text "Bestseller" |
is_ad | text "Ad by Etsy Seller" or "Ad by Etsy" — and &pro=1 appearing in the href querystring is a reliable secondary signal |
price_current | .n-listing-card__price .currency-symbol + .currency-value (formatted: combine; raw decimal: read currency-value) |
currency | .currency-symbol text ($, £, €, …) — Etsy localizes by IP/locale |
price_original | .wt-text-strikethrough .currency-value (present only on sale) |
discount_pct | derive: round((1 - current/original) * 100); Etsy may also render "XX% off" inline |
sale_label | wt-screen-reader-only text "Sale Price …" / "Original Price …" (accessible-only) |
free_shipping | text "FREE shipping" or "Free shipping" (class wt-text-slime) |
ready_to_ship | text "Ready to ship in N business day(s)" |
cart_social_proof | text "X people have this in their cart" (renders only when count > threshold) |
returns_accepted | text "Accepts returns and exchanges" (chip-conditional; not always rendered on card) |
shipping_from | text "From {Country}" / "Ships from {Country}" near footer of card |
item_type | derived from explicit= URL bit or the "Vintage" / "Craft Supply" / "Digital download" badge text |
/^([0-9,]+)\s+results?(?:,\s+with Ads)?/ near the top of <div data-search-results>. Etsy intentionally fuzzes large counts (e.g. "1,000,000+ results") — store the raw string as well as a parsed integer.button[aria-pressed="true"] inside the filter pill bar, plus parse the URL params themselves.a[rel="next"] href, or compute ?page=N+1 directly. nav[aria-label="Pagination"] exposes total pages.data-primary-event-name (search_page vs category_page vs shop_search) and the category_id JSON value embedded in the inline log script — useful to confirm which surface you landed on if a redirect happened.Increment &page=N. Each page reloads the full DOM; there is no client-side infinite-scroll API to call directly. Re-use the same session (don't recreate) — DataDome rate-limits new sessions harder than repeated nav inside a warm session.
browse cloud sessions update "$SID" --status REQUEST_RELEASE
Server: DataDome, X-Datadome: protected, X-Dd-B: 259 on blocked responses). Don't waste time on Akamai-specific evasion. Both --proxies-on and --proxies-off via the Fetch API return 403 + a captcha-delivery HTML stub (<p>Please enable JS and disable any ad blocker</p> + ct.captcha-delivery.com/i.js). The X-Datadome-Riskscore was 0.76 (no proxy) vs 0.12 (with proxy) on the same query — proxies lower the score but don't bypass the block at the Fetch layer; a verified JS-executing browser session is required./search. browse cloud fetch https://www.etsy.com/search?q=... returns 403 with or without --proxies. Same for /listing/{id} detail pages. The only Etsy surfaces that return 200 via Fetch in our testing are /robots.txt and /c/<category-slug> category-browse pages (no ?q=). For listing-ID lookups and search, you must use a verified browser session./search?q= is explicitly disallowed in robots.txt. Specifically: Disallow: /search?*q=, Disallow: /search/?*q=, Disallow: /search/*?*q=, Disallow: /search/*/?*q=. Many filter-bearing variants are also disallowed: attr_*=, price_bucket=, ship_to=, search_type=, *?order=*, *min=*, *max=*. Use the canonical web surface; honor rate limits; do not run bulk-scrape patterns from a single session.window.__INITIAL_STATE__ on search pages. Despite some scraping-tutorial claims, the Etsy search page renders listing data as plain server-rendered HTML inside <div data-search-results> with <div class="v2-listing-card"> children. There is no consolidated JSON blob to parse — extract per-card attributes from the DOM. (Etsy's internal Apollo / config blob is present but contains site chrome + translations, not listing payload.)a.listing-link[href] is https://www.etsy.com/listing/{id}/{slug}?click_key=…&click_sum=…&ls=a&ga_order=…&ga_search_type=…&ga_view_type=…&ga_search_query=…&ref=search_grid-XXX-X-X&pro=…&frs=…&sts=…. Always strip from ? onward when emitting. The pro, frs, sts flags in the original URL are useful intermediate signals: pro=1 ⇒ sponsored placement, frs=1 ⇒ free-shipping promo, sts=1 ⇒ Star Seller. Confirm against the visible badge text — do not rely on URL flags alone.pro=1 in the href. The fuzzy "X results, with Ads" count includes them. Flag is_ad: true in the output so downstream callers can filter.page=251 returns the page-1 results or an empty grid. The effective ceiling is ~16,000 listings per query. To go deeper, partition the query by category, price bucket, or explicit= item type and union the results.explicit= is a bit-field, not an enum. 1=Handmade, 2=Vintage, 4=Craft Supplies, 8=Digital downloads. Combinations sum: explicit=9 = Handmade + Digital, explicit=15 = all four. The default (no explicit=) returns Handmade + Vintage + Craft Supply mixed.ship_to= controls the "Ships to your country" chip and influences which sellers surface — Etsy weights inventory that the seller has tagged as shippable to that ISO-2. Without ship_to=, Etsy uses IP-geo to pick a default; use an explicit ship_to= to make results reproducible across proxies. The currency symbol on cards also follows ship_to=.$; a UK locale (/uk/search) sees £; an EU locale sees €. The .currency-value is the raw decimal in the displayed currency, not USD. Read .currency-symbol to disambiguate; do not assume USD./listing/1234567890 returns 403 with the same DataDome stub). Use the warm browser session you opened for search to fan out to listing details when more fields are needed (description, full image gallery, variant matrix).openapi.etsy.com/v3/application/listings/active is reachable and responds, but only with an approved app's keystring:shared_secret OAuth credential pair — "Invalid API key: should be in the format 'keystring:shared_secret'." is the literal response with an empty key. Do not advertise the Open API as a fallback to end-users without a credential. If you happen to have partner credentials, that path is dramatically more reliable than scraping and should be preferred — but it's out of reach for the generic agent runtime, so this skill leads with the browser path./c/{slug}) are NOT DataDome-walled for static category landings without a ?q= query (verified iter-1: /c/jewelry?ref=catnav-10855 returns 200 OK, 1.03 MB HTML, 64 unique listing cards via Fetch even with --proxies). The renderer is identical (search2_neu), so this is a viable lightweight Fetch fallback for category-only browsing. Adding any of ?q=, ?min=…&max=…, or other filter params can flip the response to 403 / 404 / DataDome; treat the category-page Fetch path as best-effort for category-only requests, not for keyword queries.application/ld+json Product blocks on the search page. (Listing-detail pages do carry product LD-JSON, but you'll need a browser session to reach them.)category_id is per-page-load. The inline log script carries "category_id":<int>" — useful when you arrive via /c/{slug} and want the numeric ID for cross-referencing. Note that the slugged URL is canonical for end-users; the numeric ID is internal.{
"query": "hand-poured soy candle",
"url": "https://www.etsy.com/search?q=hand-poured+soy+candle&order=most_recent",
"search_context": "search_page",
"result_count_display": "26,000+ results, with Ads",
"result_count_estimate": 26000,
"result_count_is_fuzzy": true,
"page": 1,
"page_size": 64,
"active_filters": {
"q": "hand-poured soy candle",
"order": "most_recent"
},
"currency": "USD",
"listings": [
{
"listing_id": "1030725081",
"shop_id": "24569729",
"shop_name": "ExampleShop",
"shop_location": "California, United States",
"url": "https://www.etsy.com/listing/1030725081/oval-cut-natural-yellow-sapphire-stud",
"title": "Oval Cut Natural Yellow Sapphire Stud Earrings 14K White Gold Diamond Earrings",
"image_url": "https://i.etsystatic.com/24569729/c/597/474/94/137/il/ec19d2/3125518692/il_340x270.3125518692_2ga7.jpg",
"image_urls_2x": "https://i.etsystatic.com/24569729/c/597/474/94/137/il/ec19d2/3125518692/il_680x540.3125518692_2ga7.jpg",
"price_current_formatted": "$700.50",
"price_current_raw": 700.50,
"price_original_formatted": "$934.00",
"price_original_raw": 934.00,
"discount_pct": 25,
"currency_symbol": "$",
"rating_decimal": 4.91,
"rating_count": 3442,
"badges": ["Star Seller"],
"is_ad": false,
"free_shipping": true,
"ready_to_ship_days": null,
"shipping_from_country": "United States",
"returns_accepted": null,
"cart_social_proof": null,
"sale_end_at": null,
"item_type": "Handmade",
"is_personalizable": false,
"is_customizable": false,
"is_made_to_order": false,
"variant_indicators": []
}
],
"pagination": {
"current_page": 1,
"has_next_page": true,
"next_page_url": "https://www.etsy.com/search?q=hand-poured+soy+candle&order=most_recent&page=2",
"max_page_observed": 250
}
}
Listing-ID-list input — output is the same shape with query: null, active_filters: {}, and listings[] populated by direct /listing/{id} fetches.
Shop-scoped input — search_context: "shop_search", plus "shop": {"name": "...", "shop_id": "..."} at the top level; listings[] shape unchanged.
Anti-bot wall encountered — emit:
{
"success": false,
"reason": "anti_bot_wall",
"wall": "datadome",
"url_attempted": "...",
"evidence": "Server: DataDome · X-Datadome: protected · 403"
}
Prerequisites
Time Estimate
15-45 minutes depending on use case complexity
Steps
Common Pitfalls
✓ Do
✗ Don't
💡 Pro Tips
✓ Use when
Use when skill capabilities match your task, clear ROI on time saved, and you can validate outputs. Best for repetitive tasks, learning, and quality improvement.
✗ Avoid when
Avoid when task requires deep expertise you can't validate, involves sensitive decisions, or when learning process is more valuable than speed of completion.
aliexpress.com/search-product-p0h8a7
nav.com/get-smb-funding-2s1rpm
kostja94/marketing-skills
aaaaqwq/claude-code-skills
agentbay-ai/agentbay-skills
glebis/claude-skills
Keeps context tight: search-products is the kind of skill you can hand to a new teammate without a long onboarding doc.
Registry listing for search-products matched our evaluation — installs cleanly and behaves as described in the markdown.
search-products reduced setup friction for our internal harness; good balance of opinion and flexibility.
I recommend search-products for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.
Solid pick for teams standardizing on skills: search-products is focused, and the summary matches what you get after install.
I recommend search-products for anyone iterating fast on agent tooling; clear intent and a small, reviewable surface area.
search-products reduced setup friction for our internal harness; good balance of opinion and flexibility.
We added search-products from the explainx registry; install was straightforward and the SKILL.md answered most questions upfront.
Useful defaults in search-products — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
Useful defaults in search-products — fewer surprises than typical one-off scripts, and it plays nicely with `npx skills` flows.
showing 1-10 of 45