# ThredUp $1.5💰 Secondhand Fashion Listings & Prices (`abotapi/thredup-scraper`) Actor

Scrape ThredUp.com resale listings by keyword, department, brand, size, condition and price. Returns 35+ fields per item: brand, price, original price, MSRP, size, colors, materials, condition, category and all photos. Search and URL modes, sorts and filters.

- **URL**: https://apify.com/abotapi/thredup-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** E-commerce, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 89.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 listing results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

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

## How to integrate an Actor?

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

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

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

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

# README

## ThredUp Scraper

Scrape public listings from ThredUp, one of the largest online resale and thrift marketplaces for secondhand clothing, shoes and accessories. Give it keywords plus a department and filters, or paste ThredUp category and item URLs, and get clean structured JSON: brand, price, original price, MSRP, size, colors, materials, condition, category and every photo. Search and URL modes, four sort orders, and brand, category, material, condition and price filters are all built in.

### Why this scraper

- Returns 35+ fields per item with no extra request: brand, price vs original price vs MSRP, size, condition, colors, materials, category, department and all photo URLs.
- Two modes: keyword search with filters, or paste ThredUp category (PLP) and item URLs directly.
- Filter by department, category, brand, material, style, condition and price range.
- Three sort orders: relevance, price low to high, price high to low.
- Optional full details: item description and garment measurements (chest, waist, inseam, length).
- Fast, resilient extraction with automatic session refresh and retry.
- **Resume & recurring updates** — turn on Incremental mode to get only NEW, UPDATED and REAPPEARED items on every scheduled run, or resume one specific interrupted crawl with `resumeFromRunId`.

### Data you get

> Sample shape: values are illustrative placeholders, not from a live listing.

| Field | Example |
|---|---|
| id | `200000000` |
| itemNumber | `1500000000` |
| url | `https://www.thredup.com/product/1500000000` |
| title | `Sample Item Title` |
| description | `Short item description appears here.` |
| brand | `Sample Brand` |
| price | `24.99` |
| originalPrice | `39.99` |
| msrp | `89` |
| condition | `excellent` |
| sizeDisplay | `Size M` |
| department | `women` |
| category | `dresses` |
| colorNames | `["Black"]` |
| materials | `["Cotton"]` |
| newWithTags | `false` |
| imageUrl | `https://tup-images.thredup.com/assets/000000000/xlarge.jpg` |
| imageUrls | `["https://tup-images.thredup.com/assets/000000000/xlarge.jpg"]` |
| fullDescription | `Full description appears when Fetch full details is on.` |
| measurements | `{ "chest": "00", "length": "00" }` |

### How to use

Search a department with filters:

```json
{
  "mode": "search",
  "searchQueries": ["nike dress"],
  "department": "women",
  "brands": ["Nike"],
  "priceMin": 15,
  "priceMax": 60,
  "sortBy": "price_low_high",
  "maxItems": 50,
  "proxy": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US" }
}
```

Browse a whole category with full details:

```json
{
  "mode": "search",
  "department": "juniors",
  "categories": ["jeans"],
  "fetchDetails": true,
  "maxItems": 40
}
```

Paste ThredUp URLs (category pages and item pages):

```json
{
  "mode": "url",
  "urls": [
    "https://www.thredup.com/women?department_tags=women",
    "https://www.thredup.com/product/1500000000"
  ]
}
```

### Input parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| mode | string | `search` | `search` (keywords + filters) or `url` (paste ThredUp URLs). |
| searchQueries | array | none | Keywords (search mode). Leave empty to browse by filters. |
| department | string | `women` | women, juniors, girls, boys, maternity, plus, petite, designer, handbags, shoes, accessories. |
| categories | array | none | Category tags (e.g. dresses, jeans, sneakers). |
| brands | array | none | Brand names (e.g. Nike, Madewell). |
| materials / styles | array | none | Material and style tags. |
| condition | array | none | Condition tags (excellent, very\_good, good, like\_new, new\_with\_tags). |
| clearanceOnly | boolean | `false` | Only return items ThredUp flags as clearance (search mode). |
| priceMin / priceMax | integer | none | Price range in USD. |
| sortBy | string | `relevance` | relevance, price\_low\_high, price\_high\_low. |
| urls | array | none | ThredUp category or item URLs (url mode). |
| fetchDetails | boolean | `false` | Visit each item for full description and measurements. |
| maxItems | integer | `20` | The single run cap. Set 0 for unlimited. |
| maxPages | integer | `200` | Safety bound on pages per query; the run stops at maxItems. |
| proxy | object | Residential US | Proxy configuration. |
| resumeFromRunId | string | none | Continue one specific previous run/dataset: items already collected there are skipped. For recurring monitoring of the same search, use Incremental mode instead — see "Resume & recurring updates" below. |
| incrementalMode | boolean | `false` | Daily/recurring monitoring of this same search. First run returns everything as NEW; later runs return only NEW/UPDATED/REAPPEARED by default. See "Resume & recurring updates" below. |
| stateKey | string | none | Optional name for a monitoring campaign, so its incremental state stays stable or is deliberately shared. Auto-derived from your search/URL/filter settings when left empty. |
| emitUnchanged | boolean | `false` | Incremental mode only. Also return items unchanged since the last run, marked UNCHANGED. Adds and bills extra rows you already have. |
| emitExpired | boolean | `false` | Incremental mode only. Also return items from a previous run no longer found, marked EXPIRED, once a run has fully scanned the search (not capped, not a resume). Adds and bills extra synthetic rows. |

### Output example

> Sample shape: values are illustrative placeholders, not from a live listing.

```json
{
  "id": 200000000,
  "itemNumber": "1500000000",
  "url": "https://www.thredup.com/product/1500000000",
  "title": "Sample Item Title",
  "description": "Short item description appears here.",
  "brand": "Sample Brand",
  "price": 24.99,
  "originalPrice": 39.99,
  "msrp": 89,
  "condition": "excellent",
  "newWithTags": false,
  "sizeDisplay": "Size M",
  "department": "women",
  "category": "dresses",
  "colorNames": ["Black"],
  "materials": ["Cotton"],
  "photoIds": ["000000000"],
  "imageUrls": ["https://tup-images.thredup.com/assets/000000000/xlarge.jpg"],
  "favoriteCount": 0,
  "warehouseId": 0
}
```

**Incremental mode only.** When `incrementalMode` is on, every returned record also carries:

| Field | Description |
|---|---|
| `changeType` | `NEW` | `UPDATED` | `UNCHANGED` | `REAPPEARED` | `EXPIRED` |
| `changedFields` | Top-level fields that changed since last seen; non-empty only for `UPDATED` |
| `firstSeenAt` | When this item was first observed by this monitoring campaign |
| `lastSeenAt` | When this item was last observed |

These four fields are absent on a normal run (`incrementalMode` off) — the output shape is unchanged.

### Resume & recurring updates

There are two different things here — pick the one that matches what you're doing:

| Need | Use |
| --- | --- |
| A crawl stopped and should continue | `resumeFromRunId` |
| Run the same search every day and receive only changes | `incrementalMode` |
| Keep separate daily campaigns for similar searches | distinct `stateKey` values |
| Run a normal full snapshot | leave both off |

**Resume** (`resumeFromRunId`) continues one specific interrupted or previous large crawl: paste a run ID or dataset ID and this run skips items already collected there, returning only the remaining new items.

**Incremental mode** (`incrementalMode`) is for a schedule (for example, daily): the actor remembers the previous run of the *same* search by itself, so you never paste a run ID. The first run returns everything as `NEW`. Later runs return only `NEW`, `UPDATED`, and `REAPPEARED` items by default — duplicates and unchanged items are suppressed (and not charged for the detail-enrichment surcharge, even when `fetchDetails` fetched them). Turn on `emitUnchanged` or `emitExpired` only when you also want those rows returned (and billed for). State is isolated per search/URL and filter/`fetchDetails` setup automatically; set `stateKey` to name or deliberately share a monitoring campaign.

An item is only compared field-by-field against its previous snapshot when both sides of a field have a value — a field that came back empty on one fetch and populated on the next (ThredUp's session layer occasionally returns a leaner payload) is never counted as a change on its own, so it never falsely bills an `UPDATED` row.

### Fields excluded from change detection

None. Two live fetches of the same items roughly 2.5 minutes apart, through the actor's own listing and detail extraction path with `fetchDetails` on, came back byte-identical across every output field — there is no relative-timestamp or rotating-token field to normalize away here.

### Send results into your apps (MCP connectors)

Optionally pipe results into the apps you already use, through Model Context Protocol (MCP) connectors. Authorize a connector under Apify, Settings, API and Integrations, then select it in the `mcpConnectors` input. For Notion, also set `notionParentPageUrl`. Each item is written as a condensed, human-readable summary (title plus key fields), not the full JSON; the complete record always stays in the Apify dataset. Leave the field empty to skip; it never changes the dataset output.

### Plan requirement

ThredUp only serves connections from the United States and filters non-US and datacenter connections. Use Apify Residential proxy with country US (Starter plan or higher includes Residential proxy access). On a free plan or a non-residential connection, runs will usually return zero items and a notice explaining the upgrade path.

# Actor input Schema

## `mode` (type: `string`):

'search' builds ThredUp queries from your keywords + filters below. 'url' walks the ThredUp category (PLP) or item URLs you paste (filter fields are read from the URL).

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

Keywords to search ThredUp for (e.g. 'nike dress', 'leather boots'). Each is scraped independently. Leave empty to browse a whole department/category by filters.

## `department` (type: `string`):

Top-level department to search within. ThredUp carries women's, juniors and kids (girls/boys) apparel plus maternity, plus, petite, designer, handbags, shoes and accessories.

## `categories` (type: `array`):

Category tags to narrow to (e.g. 'dresses', 'jeans', 'sneakers'). Match ThredUp's category slugs.

## `brands` (type: `array`):

Brand names to filter by (e.g. 'Nike', 'Madewell', 'Lululemon').

## `materials` (type: `array`):

Material tags (e.g. 'Cotton', 'Leather', 'Silk').

## `styles` (type: `array`):

Style tags (e.g. 'Casual', 'Y2K').

## `condition` (type: `array`):

ThredUp condition tags to filter by (advanced; leave empty for any condition). Commonly available values: 'excellent', 'good'. The output 'condition' field shows the item's quality grade.

## `clearanceOnly` (type: `boolean`):

Only return items ThredUp itself flags as clearance (the per-item 'clearance' field). ThredUp has no dedicated browsable clearance/sale section, so results are narrowed by that per-item flag rather than by a sale category. Applies to search mode; in URL mode the pasted link decides what is returned.

## `priceMin` (type: `integer`):

Only return items priced at or above this amount.

## `priceMax` (type: `integer`):

Only return items priced at or below this amount.

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

Order results are returned in.

## `urls` (type: `array`):

Paste ThredUp category (PLP) URLs (https://www.thredup.com/women?...) or item URLs (https://www.thredup.com/product/<itemNumber>). Multi-URL supported; filters and sort are read from each URL.

## `fetchDetails` (type: `boolean`):

Visit each item page to add the full description and measurements (chest, waist, inseam, length, etc.). Slower and adds a small per-item charge; leave off for fast listings (which already include brand, price, size, condition, colors, materials and all photos).

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

The single cap on this run: stop after collecting this many items (across all queries/URLs). Set 0 for unlimited.

## `maxPages` (type: `integer`):

Safety bound on result pages walked per query. Leave at the default; the run stops at Max items, not here.

## `proxy` (type: `object`):

Use Apify Residential proxy with country US for reliable results.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large crawl of items without returning or charging for items already collected there. Use this after an interrupted run, or when continuing an item pull in another run. For recurring daily monitoring of the same search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching items as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED items. Turn on "Emit unchanged" or "Emit expired" only when you also want those items returned (and billed). State is kept separately for each search/URL and filter/fetchDetails setup; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

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

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the search/URL and filter settings — different searches then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return items that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return items that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned the tracked search — not when Max items capped it or when Resume was used. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

## `mcpConnectors` (type: `array`):

Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify → Settings → API & Integrations, then select it here. Notion gets a rich page-per-item export; other connectors get a best-effort write/digest. Leave empty to skip; never changes the dataset output. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "search",
  "searchQueries": [
    "nike dress"
  ],
  "department": "women",
  "clearanceOnly": false,
  "sortBy": "relevance",
  "urls": [
    "https://www.thredup.com/women?department_tags=women"
  ],
  "fetchDetails": false,
  "maxItems": 20,
  "maxPages": 200,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (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 = {
    "mode": "search",
    "searchQueries": [
        "nike dress"
    ],
    "department": "women",
    "urls": [
        "https://www.thredup.com/women?department_tags=women"
    ],
    "maxItems": 20,
    "maxPages": 200,
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/thredup-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 = {
    "mode": "search",
    "searchQueries": ["nike dress"],
    "department": "women",
    "urls": ["https://www.thredup.com/women?department_tags=women"],
    "maxItems": 20,
    "maxPages": 200,
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/thredup-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "search",
  "searchQueries": [
    "nike dress"
  ],
  "department": "women",
  "urls": [
    "https://www.thredup.com/women?department_tags=women"
  ],
  "maxItems": 20,
  "maxPages": 200,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false
}' |
apify call abotapi/thredup-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=abotapi/thredup-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/yzrW8aefXYecZniXr/builds/y1jnuMruNX6xKgH3r/openapi.json
