ThredUp Scraper - Resale Fashion, Prices & 35+ Fields
Pricing
from $1.50 / 1,000 listing results
ThredUp Scraper - Resale Fashion, Prices & 35+ Fields
Scrape ThredUp resale listings by keyword, department, brand, size, condition, price or URL. Extract 35+ fields including brand, price, original price, MSRP, size, colours, materials, condition, category and all photos. Supports filters and sorting.
Pricing
from $1.50 / 1,000 listing results
Rating
0.0
(0)
Developer
Abot API
Maintained by CommunityActor stats
0
Bookmarked
6
Total users
3
Monthly active users
18 days ago
Last modified
Categories
Security
Limited permissionsShare
ThredUp Scraper: Secondhand Fashion Prices, Sizes & Photos
ThredUp Scraper turns ThredUp, one of the largest online resale and thrift marketplaces for secondhand clothing, shoes and accessories, into clean structured data. Give it keywords plus a department and filters, or paste ThredUp category and item URLs, and get brand, price, original price, MSRP, size, colors, materials, condition, category and every photo for each listing. Search and URL modes, three sort orders, and brand, category, material, condition and price filters are all built in, and you can export to JSON, CSV or Excel, or pull results straight into your app through the API.
Why This Scraper?
- Rich fields. Returns 35+ named fields per item with no extra request: brand, price, original price, MSRP, size, condition, colors, materials, category, department and every photo URL.
- Two ways in. Two ways to collect: keyword search with department, brand, material, style, condition and price filters, or paste ThredUp category and item URLs directly.
- Sort orders. Four sort orders for search results: relevance, newest first, price low to high, price high to low.
- Full details. Optional full item details on demand: description plus garment measurements (chest, waist, inseam, length).
- Built for schedules. Built for schedules: incremental mode returns only new, updated and reappeared items on recurring runs, and a refused run fails loudly instead of quietly returning an empty dataset.
- Resume. Resume one interrupted crawl with
resumeFromRunIdso you never pay twice for items already collected. - Cost control. Set Max items and Max pages to control run size and cost precisely.
Use Cases
- Resale price tracking: monitor a brand or category's price and MSRP spread over time to spot underpriced or newly listed items.
- Competitive analysis for resale sellers: compare your own listings' pricing and condition grading against similar items already on ThredUp.
- Market research and trend analysis: build a dataset of brand, material, condition and price mix across departments to track secondhand demand.
- Personal shopping bots and alerts: watch a saved search for new drops in your size, brand or price range.
- Academic and sustainability research: study circular-fashion pricing, condition grading and turnover patterns.
Data You Get
Sample shape: values are illustrative placeholders, not from a live listing.
| Field | Example |
|---|---|
id | 200000000 |
itemNumber | 1501420014 |
url | "https://www.thredup.com/product/1501420014" |
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" (also "" for all departments and kids for girls plus boys; ThredUp has no men's department) |
category | "dresses" |
colorNames | ["Black"] |
materials | ["Cotton"] |
newWithTags | false |
clearance | 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" } (when Fetch full details is on) |
changeType | "NEW" (also "UPDATED", "UNCHANGED", "REAPPEARED", "EXPIRED", incremental mode only) |
changedFields | ["price"] (incremental mode only, non-empty only for UPDATED) |
firstSeenAt | "2024-01-01T00:00:00Z" (incremental mode only) |
lastSeenAt | "2024-01-02T00:00:00Z" (incremental mode only) |
Each row also carries the alias fields size (same as sizeDisplay), color (colors joined with commas), discount (whole-percent markdown from original price, null when not discounted), isNewWithTags (same as newWithTags) and scrapedAt (ISO timestamp, ignored by incremental change detection).
Other output rows carry the same shape with extra catalog fields such as sku, brandId, categoryTags, departmentTags, styleTags, availability, state, favoriteCount, warehouseId, supplierId and the raw photoIds list; these fill in from the same listing or item-page fetch with no separate toggle.
How to Use
- Pick a mode:
search(keywords plus filters) orurl(paste ThredUp category and item links). - In search mode, set a department and any filters (brand, category, material, style, condition, price, sort). In url mode, paste the category or item links directly; filters and sort are read from each URL.
- Turn on Fetch full details if you also want the description and garment measurements.
- Set Max items (and optionally Max pages) to control run size and cost, then click Start.
Search a department with filters:
{"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:
{"mode": "search","department": "juniors","categories": ["jeans"],"fetchDetails": true,"maxItems": 40}
Paste ThredUp URLs (category pages and item pages):
{"mode": "url","urls": ["https://www.thredup.com/women","https://www.thredup.com/women/dresses","https://www.thredup.com/women/dresses?page=2","https://www.thredup.com/women?sort=newest_first","https://www.thredup.com/women?search_text=nike","https://www.thredup.com/product/1501420014"]}
Supported URL shapes (url mode). Leaving urls untouched now scrapes one example of each of the first twelve shapes (a behaviour change from scraping nothing).
| Shape | Example | Read as |
|---|---|---|
| Department | https://www.thredup.com/women | department filter |
| Category | https://www.thredup.com/women/dresses | department + category |
| Paginated | https://www.thredup.com/women/dresses?page=2 | starts at that page; a start page above 1 is a partial scope |
| Sorted | https://www.thredup.com/women?sort=newest_first | newest_first, price_low_high, price_high_low; unknown values log a warning and use relevance |
| Keyword | https://www.thredup.com/women?search_text=nike | keyword search |
| Brand | https://www.thredup.com/women/free-people | brand filter; the exact brand name is read from the page |
| Kids brand | https://www.thredup.com/kids/girls/levis | girls or boys department plus brand (levis is read as Levi's) |
| Brand and category | https://www.thredup.com/petite/coldwater-creek/sweaters | brand plus category (a fourth path part is read as a style) |
| Category sub-type | https://www.thredup.com/women/dresses/casual-dresses | department + category + the sub-type filter the site itself applies (casual dresses are the site's casual occasion filter); read from the page, falls back to a style filter with a warning if the page cannot be read |
| Designer | https://www.thredup.com/designer/dresses | designer department + category |
| Brand-named page | https://www.thredup.com/women/available | ThredUp lists this page as the brand named Available (the page title and breadcrumb say so); it is not an availability filter |
| Plain host | https://thredup.com/petite | same as the www form, department root (ThredUp redirects to www) |
| Keyword (search tags) | https://www.thredup.com/women?search_tags=nike+dress&sort=newest_first | keyword search, sorted |
| Department landing | https://www.thredup.com/handbags | department filter |
| Filtered | https://www.thredup.com/women?department_tags=women | filter params passed through (not in the example list) |
| Legacy | https://www.thredup.com/products/women?search_tags=nike+dress | same as the keyword form |
| Item | https://www.thredup.com/product/1501420014 | that single item |
Track new drops in a brand on a schedule:
{"mode": "search","searchQueries": ["Madewell"],"department": "women","incrementalMode": true,"maxItems": 100}
Run it from your code
Python:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("abotapi/thredup-scraper").call(run_input={"mode": "search","searchQueries": ["nike dress"],"department": "women","maxItems": 50,})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["title"], item["brand"], item["price"])
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });const run = await client.actor('abotapi/thredup-scraper').call({mode: 'search',searchQueries: ['nike dress'],department: 'women',maxItems: 50,});const { items } = await client.dataset(run.defaultDatasetId).listItems();
Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the Integrations tab.
How deep a search goes
There is no page limit by default: Max pages is empty (0), and the walk keeps going until ThredUp itself stops serving pages or Max items is reached.
ThredUp's own search backend, however, only serves the first block of about 10,000 matches for any one query or filter set, at 48 items per page. A broad search such as all of women reports a fixed total and stops after roughly 209 pages no matter how many items actually exist behind it. That is a limit of the site, not of this actor, and raising Max pages cannot get past it. To collect more from a large department, split the search into narrower slices (by brand, category, price band or condition) and run them separately; a slice whose real total is below that ceiling is served in full.
This also decides when EXPIRED rows can fire: they only make sense after a run has proven it saw the whole tracked result set, so a search that hit the site's own ceiling is skipped for EXPIRED detection rather than risk billing tombstones for items that are actually still listed.
Resume and recurring updates
There are two different things here, pick the one that matches what you are 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 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, and unchanged items are not billed unless you turn on Emit unchanged. State is isolated per search or URL and filter setup automatically; set stateKey to name or deliberately share a monitoring campaign, but only share it across configurations you actually want merged into one combined baseline (see the FAQ).
What is compared. UPDATED is decided by comparing every output field with the previous run (price, condition, size, color, availability, photos and the rest). A field that is empty on one side is not counted as a change, numbers are compared by value (25 equals 25.0), and scrapedAt is excluded because it changes on every scrape. Use ignoreFieldsForChanges to exclude more.
A few limits worth knowing before you rely on incremental mode for billing-sensitive scheduling:
EXPIREDdetection runs once the normal walk finishes, and theEXPIREDrows it returns count toward Max items: only the remaining share is returned, and any tracked items beyond it stay tracked and are reported on a later run. Turn Emit expired off if you do not want these extra rows.- Combining
resumeFromRunIdwith a first-timeincrementalModebootstrap (a search that has no prior incremental state yet) makes every resumed item read asUPDATED, with every field listed inchangedFields, on the very next ordinary scheduled run. This is a one-time effect per resumed item, not a repeating one. - Each page of results is split against the remaining Max items share before anything is returned, and every item on that page, including the ones past the share, is counted as already handled for the rest of the run. Two things follow. With several search queries or URLs in one call, an item that an earlier query fetched but did not return is skipped by every later query too, so a tight Max items can return fewer items than were actually available. In incremental mode, unchanged items are dropped only after that split, so when the top of a page is unchanged,
NEWitems further down the same page can be skipped for this run (they show up asNEWon a later run). Give Max items generous headroom (or set it to 0) when running several queries together or when using incremental mode. - The first run after this update skips
EXPIREDdetection for searches saved earlier (URL searches in particular now apply the category, brand and style in the link, so they cover a narrower set than before). That run only refreshes the tracked set to the current search, andEXPIREDdetection works normally from the next run. - In url mode, if a pasted item link fails to load, that item is not returned, and the failure does not stop
EXPIREDdetection: if the item was tracked before and Emit expired is on, the run can wrongly mark itEXPIREDeven though it may still be listed. Re-check anyEXPIREDresult on a pasted item link directly before acting on it.
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.
Input Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | string | search | search (keywords plus filters) or url (paste ThredUp URLs). |
searchQueries | array | (none) | Keywords to search ThredUp for (search mode). Leave empty to browse a whole department or category by filters. |
searchQuery | string | (none) | Single-keyword alias for searchQueries; merged into searchQueries when both are given (duplicates dropped). |
department | string | women | Top-level department: women, juniors, girls, boys, maternity, plus, petite, designer, handbags, shoes, accessories. |
categories | array | (none) | Category tags to narrow to (e.g. dresses, jeans, sneakers). |
brands | array | (none) | Brand names to filter by (e.g. Nike, Madewell, Lululemon). Lowercase or hyphenated names such as free-people are read as the display name (Free People). A brand that matches nothing ends the run with a warning that names it. |
materials | array | (none) | Material tags (e.g. Cotton, Leather, Silk). |
styles | array | (none) | Style tags (e.g. Casual, Y2K). |
condition | array | (none) | ThredUp condition tags to filter by (advanced; leave empty for any condition). The output condition field always shows the item's quality grade. |
clearanceOnly | boolean | false | Only return items ThredUp itself flags as clearance. Applies to search mode; in url mode the pasted link decides what is returned. |
priceMin | integer | (none) | Only return items priced at or above this amount, in USD. |
priceMax | integer | (none) | Only return items priced at or below this amount, in USD. |
sortBy | string | relevance | relevance, price_low_high, or price_high_low. |
urls | array | 5 example URLs (one per shape) | ThredUp category (PLP) or item URLs to paste (url mode). Multiple URLs supported; filters, sort, search and start page are read from each one. If you switch to url mode and leave this untouched, the five example URLs below are scraped. |
fetchDetails | boolean | false | Visit each item page to add the full description and measurements. Adds a small per-item charge; leave off for fast listings, which already include brand, price, size, condition, colors, materials and all photos. |
maxItems | integer | 20 | Stop after collecting this many items across all queries or URLs. Set 0 for unlimited. |
maxPages | integer | 0 | Optional cap on result pages per query. Leave empty (or 0) for no page limit: the run then stops only when Max items is reached or ThredUp stops serving further pages. See "How deep a search goes" above for ThredUp's own depth limit. |
proxy | object | Residential US | Proxy configuration. Defaults to Residential US because ThredUp only serves US connections and challenges many other connections. See Plan Requirement. |
resumeFromRunId | string | (none) | Continue one specific previous run or dataset: items already collected there are skipped and not re-billed. For recurring monitoring of the same search, use Incremental mode instead. |
incrementalMode | boolean | false | Daily or recurring monitoring of this same search. The first run returns everything as NEW; later runs return only NEW, UPDATED and REAPPEARED by default. See "Resume and recurring updates" above. |
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 and 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, and narrow enough that ThredUp serves the whole result set). Adds and bills extra synthetic rows; together with the other rows they never exceed Max items, and any left over stay tracked for the next run. |
ignoreFieldsForChanges | array | [] | Incremental mode only. Field names (or dotted nested paths such as photos.url) to exclude from UPDATED detection and changedFields; they still appear in the output. scrapedAt is always excluded. |
mcpConnectors | array | (none) | Optionally send results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize one under Apify, Settings, API and Integrations, then select it here. Leave empty to skip; never changes the dataset output. |
notionParentPageUrl | string | (none) | Notion connector only: URL (or id) of the page under which item pages are created. Ignored by other connectors. |
maxNotifyListings | integer | 50 | Cap on items written to each connector per run. Does not affect the dataset. |
Output Example
Sample shape: values are illustrative placeholders, not from a live listing.
{"id": 200000000,"itemNumber": "1501420014","sku": "SKU0000000","url": "https://www.thredup.com/product/1501420014","title": "Sample Item Title","description": "Short item description appears here.","brand": "Sample Brand","brandId": 1000,"price": 24.99,"originalPrice": 39.99,"msrp": 89,"clearance": false,"newWithTags": false,"condition": "excellent","sizeDisplay": "Size M","department": "women","category": "dresses","categoryTags": ["dresses"],"colorNames": ["Black"],"materials": ["Cotton"],"availability": "available","favoriteCount": 0,"warehouseId": 0,"photoIds": ["000000000"],"imageUrls": ["https://tup-images.thredup.com/assets/000000000/xlarge.jpg"],"imageUrl": "https://tup-images.thredup.com/assets/000000000/xlarge.jpg","fullDescription": null,"measurements": null}
Plan Requirement
ThredUp sits behind a Cloudflare challenge that refuses many plain requests, so this actor needs a real proxy connection and is not guaranteed to work on a datacenter connection. The default is Apify Residential proxy with country US, and ThredUp only serves US connections. Residential proxy access is included from the Starter plan and above; keep it selected under Proxy & connection (the choice and its cost are yours).
If the selected connection is unavailable to your account (Apify free plans do not include any Apify proxy), the run logs "The selected connection is unavailable to this run. Apify free plans do not include residential proxies. Continuing on a backup connection; some pages may still be unavailable." and tries a backup connection. Pages served that way are logged as "Served over the backup connection". Upgrading at apify.com/pricing or supplying your own proxy URLs gives the most reliable results.
When nothing can be loaded the run fails with one of these messages instead of returning an empty dataset:
- "The proxy connection was refused for this account, so no page could be read. Your Apify plan does not include proxy access; a paid plan is required." (also check your own proxy credentials if you entered any).
- "No results: residential proxies are unavailable on this run (Apify free plans do not include them), and the backup connection could not load the requested ThredUp pages."
- "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search. Please try again later."
FAQ
What views does the dataset have?
The dataset has three views: Overview (the main listing columns), Detail (description, measurements, materials, colors and photos) and Changes (changeType, changedFields, firstSeenAt, lastSeenAt for incremental runs).
How is billing counted?
Billing is per listing returned (one event per dataset item), plus one event per item enriched when Fetch full details is on, plus one actor-start event. Current rates are on the Pricing tab. Suppressed, unchanged (unless Emit unchanged is on) and failed items are not charged.
How much does it cost?
You pay per item returned, with the optional full-details fetch billed only when you switch it on. The Pricing tab shows the current rates. Use Max items to cap the cost of a run.
Is it legal to scrape ThredUp?
This actor collects only publicly available resale listings. You are responsible for how you use the data: follow ThredUp's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. Prices and listing details are generally facts, but photos may be subject to third-party rights.
Can I get only new or changed items on a schedule?
Yes. Schedule the actor from the Schedules tab and turn on Incremental mode. Each run then returns only NEW, UPDATED and REAPPEARED items by default, and unchanged items are not billed unless you also turn on Emit unchanged.
What's the difference between Resume and Incremental mode?
Resume (resumeFromRunId) continues one specific interrupted or previous run: paste its run or dataset ID and this run skips items already collected there. Incremental mode is for running the same search again and again, for example daily: the actor remembers the previous run of that search by itself, so you never paste an ID.
Why did my run fail instead of returning an empty dataset?
If a connection to ThredUp cannot be established at all, the run stops with a clear message so "no listings matched" is never confused with "the connection never worked". See Plan Requirement for the exact messages. This is usually transient; try again, and confirm Apify Residential proxy with country US is set.
Why did a scheduled run bill more items than my Max items setting?
Max items also bounds EXPIRED detection: EXPIRED rows are returned only up to the remaining Max items share after the normal walk, and the rest stay tracked for a later run. Turn off Emit expired if you do not want these extra rows.
Why was an item marked UPDATED even though nothing about it seems to have changed?
This can happen once per item after you bootstrap Incremental mode from Resume, meaning resumeFromRunId combined with incrementalMode on a search that has no prior incremental state yet. Items pulled in through Resume are recorded without a comparable snapshot, so the very next ordinary scheduled run has nothing reliable to diff them against and reports them as UPDATED with every field listed in changedFields. It is a one-time effect for each resumed item, not a repeating one.
Can two searches share the same monitoring state?
Yes, by setting the same State key on both, which is useful when you deliberately want one combined "still listed" baseline for two related searches. Doing this across searches that track very different items is not recommended: once one of them scans to completion with Emit expired on, it will tombstone every tracked item it does not itself encounter, including ones that only the other search would find and that may still be live.
Can I use it with AI agents or MCP?
Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.
π Want more classifieds data?
Pair this actor with these related scrapers from the same team:
| π·οΈ Vinted.fr Scrape Vinted.fr, France's C2C second-hand fashion marketplace: search with... | π·οΈ Curtsy Fashion Resale Scraper Scrape Curtsy, the US secondhand fashion marketplace: listings with brand, size, list vs... |
| π·οΈ Poshmark Scraper Scrape Poshmark fashion-resale listings: keyword search, brand and category browse, sold... | π Mango Scraper Scrape Mango (shop.mango.com) fashion products: current price plus strike-through Rebajas... |
| π·οΈ Daangn Scraper Scrape Daangn (λΉκ·Ό) marketplace listings by keyword and region. Returns 35+ fields per... | π KREAM Scraper Scrape KREAM (kream.co.kr) sneaker and fashion resale data by keyword search, filters... |
π Browse all abotapi scrapers
π¬ Support
- π Found a bug or a missing field? Open a ticket on the Issues tab. We usually reply within hours.
- π οΈ Need another site, extra fields or a private build? Email contact@abotapi.com.
- β Enjoying it? A quick review on the actor page helps other users find it.