# ThredUp Scraper — Resale Comps & Thrift Data (`b2b_leads/thredup-real-time-data-scraper`) Actor

Live ThredUp resale intelligence: search listings by keyword for price, brand, size, condition, discount and demand signals, plus full details, seller shop inventory and sold comps. Luxury finds are tagged, never filtered, so 1,000 records stay 1,000. Slack webhooks. Free trial: 2 results.

- **URL**: https://apify.com/b2b\_leads/thredup-real-time-data-scraper.md
- **Developed by:** [Emmanuel](https://apify.com/b2b_leads) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 results

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

## ThredUp Real-Time Data

**Live ThredUp resale intelligence** — search listings by keyword for price, brand, size, condition, discount and demand signals, plus full details, seller shop inventory and completed-listing comps. **Luxury finds are tagged, never filtered, so 1,000 records stay 1,000.** Every record streams to your dataset the moment it is collected, in clean, structured JSON.

> **Free trial:** on a free Apify plan, runs are capped at **2 results** so you can verify the data quality. Upgrade to a paid plan for unlimited exports.

***

### What you can do with it

ThredUp is one of the deepest secondhand catalogs online — millions of pre-owned and new-with-tags items across brands from Carhartt and Patagonia to Farm Rio and Free People. This actor turns that catalog into structured resale intelligence you can act on.

| You want to… | Use this |
| --- | --- |
| Find undervalued inventory (vintage single-stitch, designer denim, gorpcore, Y2K) | **Listing Search** with a minimum discount filter |
| Catch fresh drops before other resellers | **Listing Search** with *Listed within (days)* + webhooks |
| Price your own inventory against the live market | **Listing Search** + **Sold Comps** |
| Track a specific listing's full spec sheet | **Listing Details** |
| Monitor a partner shop's entire inventory | **Seller Shop Listings** |
| Vet a shop before you buy or partner | **Seller Profile** |
| Collect any ThredUp page you already have a link for | **Scrape By URL** |
| Get Slack/Discord deal alerts | **Webhook URL** |
| Ask an AI agent for comps | **Apify MCP** |

**Who it's for:** vintage & thrift resellers (cross-listing on Poshmark, eBay, Depop, Mercari, Grailed), consignment shops, resale arbitrageurs, pricing analysts, fashion researchers, and AI agents that need live resale data.

***

### Quick start (10 results in seconds)

1. Keep **Listing Search** enabled (it is on by default).
2. Keywords are already prefilled: `carhartt detroit jacket`, `vintage 90s levis 501`, `patagonia fleece`.
3. Click **Start**.

You get one row per listing — title, brand, size, condition, prices, discount, save count, images, and more — as it lands.

***

### Features

| Feature | Checkbox | What it returns |
| --- | --- | --- |
| **Listing Search** | `enableListingSearch` *(default on)* | One row per matching listing, streamed live, with keyword, rank, and total market depth. |
| **Listing Details** | `enableListingDetails` | Full records for specific item numbers or product URLs: description, fabric, care, features, pattern, measurements, MPN, and full image gallery. |
| **Seller Shop Listings** | `enableClosetListings` | One row per live item in a seller shop. *(ThredUp is shop-based rather than closet-based, so this uses shop IDs — `featureType` stays `closet_listings` for cross-platform compatibility.)* |
| **Seller Profile** | `enableSellerProfile` | One row per shop: shop type, live inventory depth, and completed-listing depth. |
| **Sold Comps** | `enableSoldHistory` | One row per completed listing where the marketplace publishes it — sold price, retail baseline, and discount from retail. |
| **Scrape By URL** | `enableScrapeByUrl` | Paste any ThredUp search, department, brand, shop, or product URL; the page type is detected automatically. |

#### Enrichment happens in place — no duplicate rows

**Enrich with full listing details** (`searchFetchFullDetails`) merges the full record **into the same `listing_search` row** and sets `detailsFetched: true`. You never get two rows for one listing, and partial items are never dropped — every discovered item reaches your dataset.

#### Grading labels tag rows — they never remove them

**Luxury brand tier labels** (`labelLuxuryBrands`) and the **discount target** (`labelMinDiscountPercent`) grade each listing instead of filtering it: a 1,000-row request stays 1,000 records, with `brand_tier`, `is_luxury_brand`, and `meets_discount_target` filled in for the segment you care about. Nothing is filtered for missing a tier, so output volume — and therefore per-1,000-record pricing — stays predictable. Details in [Grading labels](#grading-labels--tag-listings-never-filter-them).

***

### Input reference

#### Run settings

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `country` | `US` | `UK` | `US` | Market region; also drives the recommended proxy country. |

#### Listing Search

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `enableListingSearch` | boolean | `true` | Live keyword search. |
| `searchKeywords` | string list | 3 prefilled thrift queries | One keyword per entry, processed in parallel. |
| `searchMaxResults` | integer 1–1000 | `10` | Per keyword. Total rows = keywords × this number, bounded by the run limit. |
| `searchSort` | enum | `relevance` | `relevance`, `newest_first`, `price_low_high`, `price_high_low`, `marked_down_at_desc`, `recommended` — these are the marketplace's own sort options. |
| `searchDepartment` | string | – | e.g. `women`, `men`, `kids`, `home`. |
| `searchBrand` | string | – | e.g. `Carhartt`. |
| `searchCondition` | string | – | As the marketplace labels it: `excellent`, `good`, `fair`. |
| `searchMinPrice` / `searchMaxPrice` | number | – | Price band in USD. |
| `searchListedWithinDays` | integer 1–365 | – | Only recently listed inventory — ideal for fresh-deal alerts. |
| `searchFetchFullDetails` | boolean | `false` | Merge full details into the same row. |

#### Grading labels — tag listings, never filter them

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `labelLuxuryBrands` | boolean | `false` | Tag each listing with the marketplace's luxury brand grouping. Nothing is removed. |
| `labelMinDiscountPercent` | integer 1–95 | – | Savings-off-retail target to grade against; sets `meets_discount_target` on every row. |

Labels **grade** what you collected instead of deciding what you collect. Every discovered listing is written to the dataset in full, matching or not matching the label, so a labeled run returns exactly as many rows as an unlabeled one — the record count stays priceable, and there are no duplicate rows. Filter on the label fields afterwards, in your own tooling, to isolate the segment you care about.

Cost: a little extra time per keyword to resolve the tags — bounded by how many rows the run can export, so a small run stays quick and a capped run never pays for tags it will not write. The row count never changes. If tags cannot be resolved for a keyword, the run continues and the label fields are simply left blank — listings are still saved.

#### Run limits

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `maxItems` | integer 0–1,000,000 | `0` | Total rows for the whole run, across every enabled feature. `0` = no run limit. |

#### Narrowing filters — they define the search, and the run tells you the pool size first

Brand, department, condition, price band, and freshness narrow *which* inventory you are shopping. Every listing that matches is still written in full — nothing is dropped for being incomplete — but a narrower pool caps how many rows can exist. So the run measures that pool before it collects anything:

```
Projected output: 1000 record(s) — 1 keyword(s) x up to 1000, bounded by the run limit and by how many listings actually exist
"jacket": 10,001 listing(s) available, collecting 1000
Narrowing active (Brand / designer) — these reduce how many listings exist, so your output is capped by the availability above
```

If the pool is smaller than your target, you are told in plain numbers (`"jacket": only 40 listing(s) match the current filters (you asked for 1000)`) and the run collects everything that matches rather than silently under-delivering. The same numbers land in the run `OUTPUT` (`projectedRecords`, `keywordPools`), so a caller can price the run before paying for it.

Measured on the single keyword `jacket` against the live marketplace, for orientation: no filters **10,001** · listed within 1 day ≈**1,700** · brand or department scoped — varies by brand. Anything that cannot be planned is reported up front, never discovered at the end.

#### Bulk price research: how to reliably land ~1000 rows

1. Leave **all narrowing filters empty** — they are for deal hunting, not for bulk comps.
2. Give one or two **broad keywords** (`jacket`, `jeans`, `sweater`) and set **Max results per keyword** to the number you want.
3. Optionally set **Run limit** so a multi-keyword run cannot overshoot.

Measured locally with filters off: **300 rows in 7.1s**, **1000 rows in 20.3s**, zero duplicates. Paging is offset-stable, and repeated listings across page boundaries are skipped, so a request for 1000 unique rows delivers 1000 unique rows rather than “1000 attempted”. If the pool genuinely runs out first, the log says so instead of silently returning less.

#### Targeting a luxury segment without breaking that math

Luxury inventory is roughly one listing in ten for a broad keyword, and the share changes per keyword. So the actor reports it instead of guessing:

```
"jacket": 968 of 10,001 matching listing(s) are in the luxury group — they are tagged, not filtered out
"jacket": 412 of 1000 saved listing(s) carry the luxury tag
```

With **Luxury brand tier labels** on, a 1000-row run still returns 1000 rows; the share line tells you exactly how many carry the tag, and `OUTPUT.luxuryTagged` / `OUTPUT.luxuryShare` report the same thing to a calling system. To land ~1000 *luxury* rows, divide by the reported share — e.g. a 9.7% share means asking for ≈10,300 rows — which is a number you can read before the run finishes instead of a number you have to guess.

#### Filters this marketplace does not expose

ThredUp does not offer a per-size filter on its search surface, and this actor does not fake one — size is returned on every row, so filter the dataset instead. Condition is a free-text filter rather than a fixed dropdown, matching how the marketplace itself accepts it. Everything else above is a real, working filter.

#### Listing Details

| Field | Type | Notes |
| --- | --- | --- |
| `listingIds` | string list | ThredUp item numbers, e.g. `1500810136`. |
| `listingUrls` | string list | Full product URLs. |

#### Seller shops (shared by three features)

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `sellerIds` | string list | – | Seller shop IDs. Shared by Seller Shop Listings, Seller Profile, and Sold Comps. |
| `sellerMaxListings` | integer 1–200 | `30` | Live listings per shop. |
| `soldMaxItems` | integer 1–200 | `30` | Completed listings per shop. |

#### Scrape By URL

| Field | Type | Notes |
| --- | --- | --- |
| `scrapeUrls` | string list | Any ThredUp URL. URLs from other sites are rejected with a clear error. |

**Supported URL shapes**

```
https://www.thredup.com/women?search_text=carhartt%20detroit%20jacket
https://www.thredup.com/women/carhartt
https://www.thredup.com/product/women-carhartt-jacket/1500810136
https://www.thredup.com/shop/<shop-id>
```

Search URLs are read exactly as the marketplace writes them — including `department_tags`, `brand_name_tags`, `price[min]`, `price[max]`, `condition`, `clearance`, `luxe_brand`, `user_promotion_discount_percent`, and `listed_days` — so any link you copy from a ThredUp browsing session works as-is.

#### Alerts

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `webhookUrl` | string | – | Every record is POSTed here right after it is saved. Delivery never slows collection. |
| `webhookFormat` | `json` | `slack` | `json` | `json` = full record; `slack` = ready-to-read message. |
| `proxyConfiguration` | object | Apify Residential | Residential proxy is recommended for stable runs and correct regional pricing. |

***

### Output reference

One dataset row per item, streamed as it is collected. `featureType` tells you which feature produced the row: `listing_search`, `listing_details`, `closet_listings`, `seller_profile`, `sold_history`, or `scrape_by_url`.

#### Shared core (every listing-like row)

| Field | Description |
| --- | --- |
| `featureType` | Which feature produced this row. |
| `scrapedAt` | ISO-8601 write time. |
| `url` | Source URL. |
| `item_id` | ThredUp item number — reuse it with Listing Details. |
| `item_url` | Direct product link. |
| `title`, `description` | Listing copy. |
| `main_image_url`, `additional_image_urls` | Full gallery. |
| `status` | `available`, `sold`, … |
| `current_price` | Live selling price (USD). |
| `original_price` | The listing's stated original price. |
| `retail_price` | List price / MSRP. |
| `savings_amount` | Retail minus current price. |
| `discount_percentage` | Percent off retail as published. **Negative means priced above retail.** |
| `currency` | `USD`. |
| `brand`, `brand_id` | Brand identity. |
| `size`, `size_scale` | Size plus the size system (`ALPHA`, `NUMERIC`). |
| `condition`, `quality_code`, `quality_type`, `condition_description` | Condition as published. |
| `category`, `category_tags`, `department`, `department_tags` | Taxonomy. |
| `color`, `colors`, `style_tags`, `material` | Attributes. |
| `likes_count` | Shopper saves — a live demand signal. |
| `is_sold` | Completion flag. |
| `seller_id`, `seller_type`, `seller_on_vacation`, `seller_covered_shipping`, `seller_shipping_cost` | Seller identity when the listing belongs to a marketplace shop. |
| `shipping_cost`, `free_shipping` | Shipping economics. |
| `detailsFetched` | `true` once full details were merged in. |

#### Feature-specific fields

| Field group | Fields | Where |
| --- | --- | --- |
| Search context | `search_keyword`, `position`, `total_results_available` | `listing_search` |
| Detail enrichment | `fabric`, `care_instructions`, `features`, `pattern`, `measurements`, `measurements_display`, `mpn`, `mpn_title`, `size_detailed`, `photo_count` | `listing_details`, and enriched search rows |
| Shop inventory | `seller_shop_id`, `shop_total_listings` | `closet_listings` |
| Shop profile | `seller_type`, `shop_total_listings`, `shop_total_sold`, `average_rating`, `ratings_count`, `is_marketplace_shop` | `seller_profile` |
| Comps | `sold_price`, `discount_from_retail_percentage`, `shop_total_sold` | `sold_history` |
| URL runs | `pageType`, `item_id` | `scrape_by_url` |
| Grading labels | `brand_tier`, `is_luxury_brand`, `meets_discount_target` | `listing_search` (present only when the matching label is switched on) |

Runs also write an `OUTPUT` summary: `totalPushed`, `spendingLimitReached`, a `paywall` object describing the tier and whether a cap was applied, the volume projection (`projectedRecords`, `keywordPools` with each keyword's `available` and `luxuryAvailable`), the narrowing and label controls that were active (`narrowingActive`, `labelsActive`), and the label results (`luxuryTagged`, `luxuryShare`, `discountTargets`).

**Price field semantics:** `current_price` is the live price a shopper pays, `original_price` is the item's stated original price, and `retail_price` is the list price. `discount_percentage` is computed against `retail_price`, so it is directly comparable with comps from other marketplaces.

***

### Deal alerts with webhooks

Add a Slack (or Discord) incoming-webhook URL as `webhookUrl`, set `webhookFormat` to `slack`, and every fresh listing shows up as a message the moment it is collected — with title, brand, price, discount, and a direct link. Webhook delivery is fire-and-forget, so alerting never slows the run, and a failed delivery never costs you a dataset row.

Example Slack card:

```
🛍️ Carhartt Detroit Jacket — $41.99 (48% off $80 retail)
Brand: Carhartt · Size: M · Condition: Good
https://www.thredup.com/product/...
```

For JSON consumers you get the full record — ideal for pricing bots, cross-listing tools, and your own dashboards.

***

### AI agents & MCP

Connect the Apify MCP server and ask questions in plain language:

- "What is the average price of a 90s Carhartt J97 jacket right now on ThredUp?"
- "Find Patagonia fleece listings under $40 that are at least 50% off retail."
- "How many Carhartt jackets are listed and how deep is that market?"

Because every row carries `brand`, `size`, `condition`, `current_price`, `retail_price`, `discount_percentage`, and `likes_count`, an agent can compute price bands, discount distributions, and demand signals without extra work. Prefer the Dataset views (`overview`, `search`, `details`, `closet_listings`, `seller_profile`, `sold_history`, `scrape_by_url`) for clean, focused tables.

***

### Pricing, free tier, and limits

- **Billing:** pay-per-event, charged per result written (`result`). You only pay for rows you actually receive.
- **Spending limits:** the actor respects your Apify spending limit. When it is reached the run stops cleanly, writes its summary, and exits gracefully — no error, no partial garbage.
- **Free plan:** capped at **2 results** per run, then the run stops with a clear upgrade message. A hard `block` mode is available to account owners for validation-only runs.
- **Memory & runtime:** 512 MB default with a 10,000-second timeout; memory stays flat during 10,000+ item runs because rows stream to the dataset instead of buffering.

***

### FAQ

**How fresh is the data?**
Every run reads live marketplace inventory at run time — there is no stale cache.

**Do I need a proxy?**
On Apify, residential proxy is configured by default and recommended. Locally, set your proxy in `.env` (see `.env.example`).

**Why did a keyword return no rows?**
The narrowing filters were too tight (a rare brand combined with a narrow price band and a short freshness window). Loosen one filter at a time. Note that grading labels never cause this — turning on luxury tagging cannot reduce your row count.

**Why is some optional field empty?**
Some listings genuinely publish no fabric, measurements, or condition notes. The actor never invents values — empty means the marketplace did not publish it.

**Can I get one row per listing with everything in it?**
Yes: enable **Enrich with full listing details** and you get the search row *plus* the full record in a single row (`detailsFetched: true`).

**Where do seller shop IDs come from?**
From ThredUp partner shop links you already have. The actor never guesses shop IDs: if a value does not resolve to a shop, that feature logs it clearly and the rest of the run continues normally.

**How do I keep dataset size and cost predictable?**
Use `searchMaxResults`, `sellerMaxListings`, `soldMaxItems`, and your Apify spending limit. Labels never change the row count, narrowing filters report their pool size up front in the log and in `OUTPUT.projectedRecords`, and rows stream as they are found — so a spending cutoff never loses what you already paid for.

***

### Local development

```bash
npm install
cp .env.example .env          # add your proxy settings
cp local.input.example.json local.input.json
npm run start:local           # streams to output/local_results.jsonl
```

```bash
npm run typecheck
npm run build
```

***

### License

ISC.

# Actor input Schema

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

Marketplace region. Affects pricing region and the recommended connection region.

## `enableListingSearch` (type: `boolean`):

Search ThredUp by keyword. On by default — prefilled so you can click Start and get results in seconds. Output size is set by the keyword count and the per-keyword cap below, and is reported at the start of every run.

## `searchKeywords` (type: `array`):

One or more search terms (e.g. carhartt detroit jacket, vintage 90s levis 501, patagonia fleece). Each keyword contributes its own rows, so output grows with the number of keywords.

## `searchMaxResults` (type: `integer`):

How many listings to collect for each keyword. Total output = keywords x this number, bounded by the run limit. Leave the narrowing filters below empty and this number is what you actually get.

## `searchFetchFullDetails` (type: `boolean`):

When on, each search result is the SAME row enriched with full product fields (description, fabric/material tags, style tags, full image gallery, measurements, seller shipping terms). Still featureType listing\_search with detailsFetched=true — never a second row, and nothing is filtered out for being incomplete. Adds a little extra time per listing. Enrichment never changes how many records you get.

## `searchSort` (type: `string`):

How to order search results. Does not change how many rows you get.

## `searchBrand` (type: `string`):

Optional brand filter, e.g. Carhartt. Narrows which listings exist, so your output can be smaller than the record count you asked for.

## `searchDepartment` (type: `string`):

Optional department scope, e.g. women, men, kids, home. Narrows the pool.

## `searchCondition` (type: `string`):

Optional condition filter as this marketplace labels it, e.g. excellent, good, fair. Narrows the pool.

## `searchMinPrice` (type: `number`):

Lowest listing price to include. Narrows the pool.

## `searchMaxPrice` (type: `number`):

Highest listing price to include. Narrows the pool.

## `searchListedWithinDays` (type: `integer`):

Only keep recently listed inventory — ideal for deal alerts where you want fresh finds only. Narrows the pool, so leave it empty for bulk price research.

## `labelLuxuryBrands` (type: `boolean`):

Tag every listing with the marketplace's own luxury brand grouping — without removing anything. Listings outside the tier are still written in full, and there are no duplicate rows. Adds a little extra time per keyword, but the record count stays exactly what you asked for, so runs stay priceable. Each listing gets brand\_tier, is\_luxury\_brand, and a per-keyword luxury share in the run log.

## `labelMinDiscountPercent` (type: `integer`):

Savings off retail to grade against, e.g. 30. Every listing is still exported; each row gains meets\_discount\_target so you can isolate deep discounts without shrinking the run. Leave empty to skip this label.

## `enableListingDetails` (type: `boolean`):

Fetch the full record for specific ThredUp listings by item number or URL.

## `listingIds` (type: `array`):

ThredUp item numbers, e.g. 1500810136. Required when Listing Details is on and no URLs are given.

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

Full ThredUp product URLs (https://www.thredup.com/product/...).

## `enableClosetListings` (type: `boolean`):

Collect a marketplace seller shop's full active inventory to track competitor stock. ThredUp is shop-based rather than closet-based.

## `sellerIds` (type: `array`):

ThredUp seller shop IDs. Shared by Seller Shop Listings, Seller Profile, and Sold Comps.

## `sellerMaxListings` (type: `integer`):

How many live listings to collect from each seller shop.

## `enableSellerProfile` (type: `boolean`):

Shop-level profile: shop type, live inventory depth, and published completed-listing depth.

## `enableSoldHistory` (type: `boolean`):

Collect completed listings for comps and sell-through. ThredUp publishes no site-wide sold feed, so comps come from seller shop archives where the marketplace keeps them.

## `soldMaxItems` (type: `integer`):

How many completed listings to collect from each seller shop.

## `enableScrapeByUrl` (type: `boolean`):

Paste any ThredUp URL — search results, department or brand page, seller shop, or a single listing. The page type is detected automatically.

## `scrapeUrls` (type: `array`):

Any ThredUp search, department, brand, shop, or listing URL. URLs from other sites are rejected.

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

Maximum rows to export for the whole run, across every enabled feature. 0 = no run limit (collect everything the enabled features match). Use it to bound cost on large bulk runs, e.g. 1000 for a price-research sweep.

## `webhookUrl` (type: `string`):

Optional. Every record is always saved to the run dataset — this webhook is an ADDITIONAL real-time push. Each new row is also POSTed to this URL (Slack, Discord, Zapier, Make, n8n, custom pricing bot).

## `webhookFormat` (type: `string`):

JSON sends the full record. Slack sends a ready-to-read message.

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

Residential proxy recommended. Match the region to the Region setting above.

## Actor input object example

```json
{
  "country": "US",
  "enableListingSearch": true,
  "searchKeywords": [
    "carhartt detroit jacket",
    "vintage 90s levis 501",
    "patagonia fleece"
  ],
  "searchMaxResults": 10,
  "searchFetchFullDetails": false,
  "searchSort": "relevance",
  "searchBrand": "",
  "searchDepartment": "",
  "searchCondition": "",
  "labelLuxuryBrands": false,
  "enableListingDetails": false,
  "listingIds": [],
  "listingUrls": [],
  "enableClosetListings": false,
  "sellerIds": [],
  "sellerMaxListings": 30,
  "enableSellerProfile": false,
  "enableSoldHistory": false,
  "soldMaxItems": 30,
  "enableScrapeByUrl": false,
  "scrapeUrls": [],
  "maxItems": 0,
  "webhookUrl": "",
  "webhookFormat": "json",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `allResults` (type: `string`):

Full dataset for this run (every featureType).

## `overview` (type: `string`):

Core resale fields across features, including detailsFetched for search rows.

## `luxury_finds` (type: `string`):

listing\_search rows carrying the luxury label (is\_luxury\_brand = true). Labels never remove rows — non-luxury listings stay in the dataset and this view only filters the display.

## `search` (type: `string`):

featureType=listing\_search only. One listing = one row; full details are merged when "Enrich with full listing details" is enabled.

## `details` (type: `string`):

featureType=listing\_details — only from the Listing Details feature (specific item numbers/URLs), not from search enrichment.

## `closet_listings` (type: `string`):

featureType=closet\_listings — live seller shop inventory.

## `seller_profile` (type: `string`):

featureType=seller\_profile — shop type and inventory depth.

## `sold_history` (type: `string`):

featureType=sold\_history — completed listings for comps, margins, and sell-through.

## `scrape_by_url` (type: `string`):

featureType=scrape\_by\_url — rows produced from direct URLs.

# 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 = {
    "country": "US",
    "enableListingSearch": true,
    "searchKeywords": [
        "carhartt detroit jacket",
        "vintage 90s levis 501",
        "patagonia fleece"
    ],
    "searchMaxResults": 10,
    "searchFetchFullDetails": false,
    "searchSort": "relevance",
    "labelLuxuryBrands": false,
    "enableListingDetails": false,
    "listingIds": [],
    "listingUrls": [],
    "enableClosetListings": false,
    "sellerIds": [],
    "sellerMaxListings": 30,
    "enableSellerProfile": false,
    "enableSoldHistory": false,
    "soldMaxItems": 30,
    "enableScrapeByUrl": false,
    "scrapeUrls": [],
    "maxItems": 0,
    "webhookFormat": "json",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("b2b_leads/thredup-real-time-data-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 = {
    "country": "US",
    "enableListingSearch": True,
    "searchKeywords": [
        "carhartt detroit jacket",
        "vintage 90s levis 501",
        "patagonia fleece",
    ],
    "searchMaxResults": 10,
    "searchFetchFullDetails": False,
    "searchSort": "relevance",
    "labelLuxuryBrands": False,
    "enableListingDetails": False,
    "listingIds": [],
    "listingUrls": [],
    "enableClosetListings": False,
    "sellerIds": [],
    "sellerMaxListings": 30,
    "enableSellerProfile": False,
    "enableSoldHistory": False,
    "soldMaxItems": 30,
    "enableScrapeByUrl": False,
    "scrapeUrls": [],
    "maxItems": 0,
    "webhookFormat": "json",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("b2b_leads/thredup-real-time-data-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 '{
  "country": "US",
  "enableListingSearch": true,
  "searchKeywords": [
    "carhartt detroit jacket",
    "vintage 90s levis 501",
    "patagonia fleece"
  ],
  "searchMaxResults": 10,
  "searchFetchFullDetails": false,
  "searchSort": "relevance",
  "labelLuxuryBrands": false,
  "enableListingDetails": false,
  "listingIds": [],
  "listingUrls": [],
  "enableClosetListings": false,
  "sellerIds": [],
  "sellerMaxListings": 30,
  "enableSellerProfile": false,
  "enableSoldHistory": false,
  "soldMaxItems": 30,
  "enableScrapeByUrl": false,
  "scrapeUrls": [],
  "maxItems": 0,
  "webhookFormat": "json",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call b2b_leads/thredup-real-time-data-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,b2b_leads/thredup-real-time-data-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/et72O2cOhx5c6ZIYB/builds/OWBBRQ2CxpfDNUi53/openapi.json
