ThredUp API & Scraper
The ThredUp API turns thredup.com, the largest US online consignment and thrift marketplace, into clean JSON in three actions.
🤖 Using an AI assistant? Copy this link into ChatGPT / Claude / Cursor — it reads every endpoint and parameter instantly and tells you if this API fits your use case.
search takes a free-text keyword, a department, or both, and narrows by brand, category, condition grade, colour, material, style, price band, who sells the item, and whether the single copy is still for sale or already sold. Every row is a full listing record: the item number and the listing URL, the brand and ThredUp's own category, the size as ThredUp displays it plus the raw size value and scale, the condition grade with the wear note ThredUp's graders wrote, the garment measurements in inches, the fibre composition, the colours, the favourite count, and three separate prices — the price today, ThredUp's earlier list price, and the estimated new retail price — with the discount against retail given both as ThredUp states it and as recomputed from those prices. item/detail returns one listing in full by item number, adding ThredUp's per-garment attribute block (fit, pattern, neckline, sleeve length, stretch, jean wash, rise, heel height, where the category has them), the authenticity certificate for designer pieces, and for items sold by an individual ThredUp seller the seller block with the seller id, the display name, the ship-from city and state, whether they cover shipping, and the buyer-protection fee in dollars. brand/resolve checks brand names against ThredUp's own brand list and hands back the tag the brand filter takes along with ThredUp's canonical spelling, so levis comes back as Levi's and a name ThredUp does not carry is listed as unresolved instead of being silently dropped. Two things make resale different from retail and this API treats both honestly. First, every ThredUp listing is a single copy with no variants and no restock, and ThredUp publishes no quantity field for it at all — so this API publishes none either, and instead returns availability, the listing state (listed, purchased or packed) and a derived is_available, rather than inventing a stock number. Second, sold listings are not hidden: listing_state can be set to purchased or packed on purpose, because in resale the price something actually sold for is the data people buy. No ThredUp account, one ReefAPI key and the standard { ok, data, meta, error } envelope.
Three prices on every listing, and why there is no field called price
ThredUp prints three different money figures for the same garment and they do not mean the same thing. This API never blends them and never ships a bare price key, because a single number would be ambiguous. Fill rates below are over 324 live listings sampled across eight department, category and keyword combinations.
| Field | What it is | How often it was filled |
|---|---|---|
| price_usd | What a buyer pays today. | 324 of 324 |
| thredup_list_price_usd | ThredUp's own earlier list price for the item. It is NOT a was-price: it was lower than price_usd on the first items measured (30.74 against 30.99, and 25.74 against 33.99). | 324 of 324 |
| retail_price_usd | ThredUp's estimated price for the item new at retail. This is the number the headline discount is taken against. | 324 of 324 |
| discount_vs_retail_pct | The discount against retail exactly as ThredUp states it. | 324 of 324 |
| discount_vs_retail_pct_computed | The same discount recomputed here from price_usd and retail_price_usd. | 324 of 324 |
| discount_disagrees_with_source | True when those two differ by more than a tenth of a point. Measured on 324 rows: 284 agreed, 40 disagreed. | Set on every row |
The disagreements are ThredUp's, not this API's, and they are flagged rather than smoothed. On one listing ThredUp stated 67.98 % off while its own 58.99 price against a 178 retail works out at 66.86 %, and on another it stated 75.02 % where the prices give 57.83 %. Both numbers ship so you can pick which one your product needs, and the flag tells you when you have to choose.
Real request and response JSON
Captured from the indexed primary action, search, on .
{
"method": "POST",
"url": "https://api.reefapi.com/thredup/v1/search",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"query": "nike dress",
"per_page": 24
}
}{
"ok": true,
"meta": {
"api": "thredup",
"endpoint": "search",
"mode": "live",
"latency_ms": 2063.5,
"record_count": 24,
"bytes": 181445,
"cache_hit": false,
"upstream_requests": 2,
"mint_attempts": 1,
"charged_credits": 1,
"version": "0.1.0",
"request_id": "b37223290c974b94",
"queue_ms": 1.6
},
"data": {
"query": "nike dress",
"filters_applied": null,
"results": [
{
"item_number": 236851117,
"item_id": 235814349,
"url": "https://www.thredup.com/item/236851117",
"title": "Nike Casual Dress",
"brand": "Nike",
"brand_id": 268,
"brand_style_name": null,
"category": "Casual Dress",
"category_id": 346,
"category_tags": [
"dresses"
],
"department_tags": [
"juniors",
"women"
],
"department": "women",
"gender": "women",
"style_tags": [
"bodycon"
],
"attribute_tags": [
"Crew Neck neckline"
],
"price_usd": 37.99,
"thredup_list_price_usd": 30.74,
"retail_price_usd": 102,
"out_of_region_price_usd": 37.99,
"discount_vs_retail_pct": 62.75,
"discount_vs_retail_pct_computed": 62.75,
"discount_disagrees_with_source": false,
"is_on_sale": false,
"is_clearance": false,
"is_final_sale": false,
"condition": "excellent",
"condition_code": "Q1",
"condition_label": "excellent",
"condition_detail": null,
"condition_notes": null,
"new_with_tags": false,
"availability": "InStock",
"listing_state": "listed",
"is_available": true,
"favorite_count": 5,
"size_display": "Size M",
"size_display_detailed": "Size M",
"size_display_eu": null,
"size_display_non_standard": null,
"size_value": "M",
"size_scale": "ALPHA",
"size_chart": null,
"measurements": {
"chest_in": null,
"waist_in": null,
"inseam_in": null,
"length_in": 36.25,
"rise_in": null,
"height_in": null,
"heel_height_in": null,
"depth_in": null,
"measured_by": "automated",
"display": null
},
"materials": [
"45% POLYESTER",
"53% NYLON",
"2% SPANDEX"
],
"colors": [
"Black"
],
"mpn": null,
"mpn_title": null,
"description": null,
"ships_from": null,
"authentication": null,
"warehouse_id": 16,
"is_seller_listing": false,
"partner": null,
"photo_numbers": [
"968907741",
"968907764",
"968907813"
],
"photos": [
{
"photo_number": "[trimmed-depth]",
"kind": "[trimmed-depth]",
"zoomable": "[trimmed-depth]",
"aspect_ratio": "[trimmed-depth]"
},
{
"photo_number": "[trimmed-depth]",
"kind": "[trimmed-depth]",
"zoomable": "[trimmed-depth]",
"aspect_ratio": "[trimmed-depth]"
},
{
"photo_number": "[trimmed-depth]",
"kind": "[trimmed-depth]",
"zoomable": "[trimmed-depth]",
"aspect_ratio": "[trimmed-depth]"
}
]
},
{
"item_number": 236140020,
"item_id": 235035070,
"url": "https://www.thredup.com/item/236140020",
"title": "Nike Romper",
"brand": "Nike",
"brand_id": 268,
"brand_style_name": null,
"category": "Romper",
"category_id": 354,
"category_tags": [
"one-pieces"
],
"department_tags": [
"juniors",
"women"
],
"department": "women",
"gender": "women",
"style_tags": [
"rompers"
],
"attribute_tags": [
"Scoop Neck neckline"
],
"price_usd": 30.99,
"thredup_list_price_usd": 30.74,
"retail_price_usd": 102,
"out_of_region_price_usd": 30.99,
"discount_vs_retail_pct": 69.62,
"discount_vs_retail_pct_computed": 69.62,
"discount_disagrees_with_source": false,
"is_on_sale": false,
"is_clearance": false,
"is_final_sale": false,
"condition": "very_good",
"condition_code": "Q2",
"condition_label": "very_good",
"condition_detail": "minor wear on fabric",
"condition_notes": null,
"new_with_tags": false,
"availability": "InStock",
"listing_state": "listed",
"is_available": true,
"favorite_count": 4,
"size_display": "Size XS",
"size_display_detailed": "Size XS",
"size_display_eu": null,
"size_display_non_standard": null,
"size_value": "XS",
"size_scale": "ALPHA",
"size_chart": null,
"measurements": {
"chest_in": null,
"waist_in": null,
"inseam_in": null,
"length_in": 29,
"rise_in": null,
"height_in": null,
"heel_height_in": null,
"depth_in": null,
"measured_by": "automated",
"display": null
},
"materials": [
"80% POLYESTER",
"20% SPANDEX"
],
"colors": [
"Black"
],
"mpn": null,
"mpn_title": null,
"description": null,
"ships_from": null,
"authentication": null,
"warehouse_id": 16,
"is_seller_listing": false,
"partner": null,
"photo_numbers": [
"964817700",
"964817817",
"964611378"
],
"photos": [
{
"photo_number": "[trimmed-depth]",
"kind": "[trimmed-depth]",
"zoomable": "[trimmed-depth]",
"aspect_ratio": "[trimmed-depth]"
},
{
"photo_number": "[trimmed-depth]",
"kind": "[trimmed-depth]",
"zoomable": "[trimmed-depth]",
"aspect_ratio": "[trimmed-depth]"
},
{
"photo_number": "[trimmed-depth]",
"kind": "[trimmed-depth]",
"zoomable": "[trimmed-depth]",
"aspect_ratio": "[trimmed-depth]"
}
]
},
{
"item_number": 231601243,
"item_id": 230196750,
"url": "https://www.thredup.com/item/231601243",
"title": "Assorted Brands Casual Dress",
"brand": "Assorted Brands",
"brand_id": 44193,
"brand_style_name": null,
"category": "Casual Dress",
"category_id": 346,
"category_tags": [
"dresses"
],
"department_tags": [
"women"
],
"department": "women",
"gender": "women",
"style_tags": [
"drop-waist"
],
"attribute_tags": [
"Crew Neck neckline"
],
"price_usd": 23.99,
"thredup_list_price_usd": 24.74,
"retail_price_usd": 51,
"out_of_region_price_usd": 22.99,
"discount_vs_retail_pct": 54.92,
"discount_vs_retail_pct_computed": 52.96,
"discount_disagrees_with_source": true,
"is_on_sale": false,
"is_clearance": false,
"is_final_sale": false,
"condition": "very_good",
"condition_code": "Q2",
"condition_label": "good",
"condition_detail": "Minor stain|minor wear on fabric",
"condition_notes": null,
"new_with_tags": false,
"availability": "InStock",
"listing_state": "listed",
"is_available": true,
"favorite_count": 8,
"size_display": "Size M",
"size_display_detailed": "Size M",
"size_display_eu": null,
"size_display_non_standard": null,
"size_value": "M",
"size_scale": "ALPHA",
"size_chart": null,
"measurements": {
"chest_in": null,
"waist_in": null,
"inseam_in": null,
"length_in": 35.75,
"rise_in": null,
"height_in": null,
"heel_height_in": null,
"depth_in": null,
"measured_by": "physical",
"display": null
},
"materials": [
"95% COTTON",
"5% ELASTANE"
],
"colors": [
"White"
],
"mpn": null,
"mpn_title": null,
"description": null,
"ships_from": null,
"authentication": null,
"warehouse_id": 16,
"is_seller_listing": false,
"partner": null,
"photo_numbers": [
"914506991",
"914507030",
"914507129"
],
"photos": [
{
"photo_number": "[trimmed-depth]",
"kind": "[trimmed-depth]",
"zoomable": "[trimmed-depth]",
"aspect_ratio": "[trimmed-depth]"
},
{
"photo_number": "[trimmed-depth]",
"kind": "[trimmed-depth]",
"zoomable": "[trimmed-depth]",
"aspect_ratio": "[trimmed-depth]"
},
{
"photo_number": "[trimmed-depth]",
"kind": "[trimmed-depth]",
"zoomable": "[trimmed-depth]",
"aspect_ratio": "[trimmed-depth]"
}
]
}
],
"count": 24,
"keyword_match_pct": 87.5,
"looks_like_padding": false,
"page": 1,
"per_page": 24,
"has_more": true,
"duplicate_in_page": 0,
"total_count": 10001,
"total_count_is_capped": true,
"sorts_offered": [
{
"sort": "newest",
"upstream_value": "newest_first",
"label": "Newest first",
"selected": false
},
{
"sort": "price_high_low",
"upstream_value": "price_high_low",
"label": "Price high to low",
"selected": false
},
{
"sort": "price_low_high",
"upstream_value": "price_low_high",
"label": "Price low to high",
"selected": false
}
],
"sort": null,
"measured_at": "2026-10-02T00:35:58Z"
}
}What the ThredUp API does
| Action | Description | Concrete use case | Key params |
|---|---|---|---|
| search | Search ThredUp. Give a keyword, a department, or both - one of the two is required (a bare call cannot be answered and returns MISSING_PARAM). Narrow with brand, category, condition grade, colour, material, style, price band, who sells it, and whether the copy is still for sale or already sold. Every row carries the full listing record: brand, size (US and EU where ThredUp has it), condition grade and its wear note, garment measurements in inches, fibre composition, colour, the price today, ThredUp's earlier list price, the estimated new-retail price and the discount both as ThredUp states it and as recomputed here. ThredUp caps its own `totalCount` at 10001 for every query, so that number is returned flagged as a cap and never as a result count. | Pricing teams call search to search ThredUp. | query, department, brand, category, condition, ... |
| item/detail | One ThredUp listing in full by item number: everything the search row carries plus ThredUp's per-garment attribute block (fit, pattern, neckline, sleeve length, stretch, jean wash, rise, heel height where the category has them), the dropshipper and authenticity certificate when there is one, the partner description, and ThredUp's own sustainability estimate for the item, and - for a listing sold by an individual ThredUp seller - the seller block, which exists ONLY here and is null on every search row. A listing that has already sold is returned with `listing_state: purchased` - that is an answer, not an error. An item number ThredUp does not know returns NOT_FOUND and is not retried. | Marketplace operators call item/detail to get one ThredUp listing in full by item number. | item_number |
| brand/resolve | Check brand names against ThredUp's own brand list and get back the tag its filters take. Send one or several names ('Calvin Klein', 'levis', 'Free People'); each comes back with the tag to pass to `search`'s `brand` parameter and with ThredUp's own canonical spelling ('levis' -> "Levi's"). A name ThredUp does not carry is listed in `unresolved` instead of being silently dropped. | Catalog enrichment teams call brand/resolve to check brand names against ThredUp's own brand list and get back the tag its filters take. | name |
Call search from your stack
curl -X POST https://api.reefapi.com/thredup/v1/search \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"query":"nike dress","per_page":24}'import requests
r = requests.post(
"https://api.reefapi.com/thredup/v1/search",
headers={"x-api-key": REEF_KEY},
json={
"query": "nike dress",
"per_page": 24
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/thredup/v1/search", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"query": "nike dress",
"per_page": 24
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.thredup.search with {"query":"nike dress","per_page":24}.Who uses this API and why
- Resale price research: pull the same brand and category with listing_state set to purchased to see what actually sold, then compare against the listed rows to measure how far asking prices sit above clearing prices.
- Brand and category monitoring: track how much of ThredUp's catalogue a brand holds, at what condition grades, and at what discount against new retail, using the condition grade and the two discount figures on every row.
- Sizing and fit tools: every listing carries the displayed size, the raw size value and scale, and the garment measurements in inches that ThredUp's warehouse recorded, with the measurement method stated as automated or physical.
- Sustainable-fashion and circular-economy dashboards: item/detail returns ThredUp's own per-item impact estimate (water, lighting hours, car miles), alongside the fibre composition that drives it.
- Competitive assortment analysis for resale and thrift operators: filter by designer-only, clearance-only, or by whether ThredUp or an individual seller is selling, and compare condition mix and price bands across departments.
Questions developers ask before integrating
Does a listing come with a stock or quantity number?
No, and that is deliberate. ThredUp is single-copy resale: each listing is one physical garment with no size or colour variants and no restock, and ThredUp publishes no quantity field for it anywhere on this surface — all 62 fields of its item record were enumerated and there is no stock, quantity or qty among them. Inventing one, or shipping a constant that looks like stock, would be worse than publishing nothing. What you get instead is availability as ThredUp states it, listing_state (listed, purchased or packed) and a derived is_available, which is true only while the state is listed.
Can I get listings that have already sold?
Yes, on purpose. Set listing_state to purchased or packed and the search returns items that are already bought, with their price. In resale the sold price is usually the number people are after, so this API treats a sold listing as data rather than as a dead record: a listing that sold between your search and your detail call comes back with listing_state purchased and a full record, not an error. Measured: a purchased-only search returned 24 of 24 rows in that state.
Why does total_count always say 10001?
Because that is ThredUp's own ceiling, not a count. Thirteen different filters, all returning visibly different listings, every one reported exactly 10001. So this API returns the number ThredUp gave together with total_count_is_capped set to true, and never presents it as a result total. The useful count is the count field, which is the number of rows in the page you asked for.
How many rows per page, and can I page through a whole category?
You choose the page size and ThredUp honours it exactly: measured one-for-one at 12, 24, 36, 48, 60, 96, 119, 120 and 200 rows, and capped here at 120. Paging works, but ThredUp's ranked index shifts between calls, so pages overlap: four pages of 48 on one keyword returned 178 distinct item numbers rather than 192, with page 2 repeating one row and page 4 repeating thirteen. The API reports duplicate_in_page for repeats inside the page you asked for, and de-duplicating across pages on your side is still worth doing.
What happens if my keyword matches nothing?
ThredUp does not return an empty result for an unmatchable keyword — it fills the page with unrelated stock and still reports its usual count. So this API measures how many of the returned listings actually carry your keyword in their title, brand, category or tags, and publishes that as keyword_match_pct with a looks_like_padding flag. Measured across two runs: nike dress 87.5 to 91.7 %, levis 501 95.8 %, coach bag 100 %, and two deliberately nonsense keywords 0.0 % each, flagged in both runs. Without that flag an unmatchable search looks like a successful one.
Are there image URLs?
No. ThredUp's listing record carries photo numbers and the kind of each photo (the regular shots, the annotated condition close-ups, the 360-degree frames) and this API returns all of them, but the image host would not serve any of the six address shapes tested, so no image URL is published. Guessing one would hand you links that break. If images matter for your use case, say so and we will add them once the address is confirmed.
Which departments work, and is there menswear?
Thirteen departments returned listings in the run measured: women, juniors, plus, petite, tall, maternity, designer, handbags, shoes, accessories, jewelry, boys and girls. Menswear and a generic kids department both returned zero listings in that same run, so neither is accepted — asking for them gives a clear parameter error with the reason instead of an empty success. ThredUp's live catalogue on this surface is womenswear plus boys and girls.
Do the filters actually narrow the results?
Each one was checked on its returned rows, not on the result count, because ThredUp's count is capped. Brand Nike gave 48 of 48 rows branded Nike. Category dresses gave 48 of 48. Condition excellent gave 24 of 24 at that grade. Colour Black gave 24 of 24 where the unfiltered page carried eight colours. Clearance-only gave 24 of 24 clearance rows where the unfiltered page had none. Designer-only returned Bottega Veneta, Burberry, Bvlgari and Céline where the unfiltered page carried nineteen mixed brands. The price band is ThredUp's own and carries slack: asking 0 to 15 returned prices of 8.99 to 19.99, 50 to 60 returned 40.99 to 60.99, and 100 to 200 returned 107.99 to 197.99 — the band applies to ThredUp's pre-promotion figure, which is why it is documented on the parameter rather than hidden. Two filters ThredUp accepts but does not honour were tested and deliberately left out of this API rather than shipped as handles that do nothing.
What is the ThredUp API?
ThredUp API is a ReefAPI endpoint group for us fashion resale: secondhand clothing, shoes and bags with brand, size, condition grade, garment measurements and the price today beside the estimated new-retail price. It returns live JSON through POST requests under /thredup/v1.
Is the ThredUp API free to try?
Yes. ReefAPI starts with 1,000 free credits, no card required. ThredUp calls use the same shared credit balance as every other ReefAPI engine.
Do I need a ThredUp login or account?
No login to ThredUp is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.
How fresh is the ThredUp data?
The page example is captured from a live search call, and production requests fetch live data through ReefAPI rather than a static sample.
How many credits does the ThredUp API use?
ThredUp actions currently cost 2-3 credits per successful call. Failed or blocked calls are free. All APIs draw from one credit pool.
Can I call ThredUp from an AI assistant or MCP client?
Yes. Connect ReefAPI once through MCP and your assistant can call thredup actions with the same key, credit pool and JSON envelope used by normal REST requests.