Start here
Quickstart
- Get a key. Enter your email on the home page. Free keys include 100 lookups a month, no card needed.
- Make a request. Send your search as
qand your key in theX-API-Keyheader. - Use the numbers.
stats.medianis the typical sold price;itemslists every sale behind it.
curl "https://soldstack.fly.dev/v1/sold?q=coach+tabby" \
-H "X-API-Key: ss_your_key"
Base URL: https://soldstack.fly.dev. Everything is HTTPS and JSON.
Keys
Authentication
Every request needs your API key. Send it in the X-API-Key header (recommended), or as an api_key query parameter if your tool can't set headers.
Keep your key on your server. Don't put it in browser JavaScript or a mobile app, where anyone can read it. Call SoldStack from your backend and pass the results to your app.
You can see your key, plan and usage on your account page.
Endpoint
GET /v1/sold
Returns recently sold listings that match a search, with summary stats.
Query parameters
- qstring · required
- What to search for, 2–100 characters. Brand plus item works best, e.g.
lululemon align 6. Case and extra spaces are ignored. - sourcestring · default poshmark
- Which marketplace to search. Only
poshmarkis live today; eBay, Mercari, Depop, Grailed, StockX and Vinted are planned. - limitinteger · 1–200 · default 50
- How many sold listings to return in
items. Stats always use every clean match, whatever the limit.
Filters
Optional. Filters work on the cached search, so trying different filters on the same search doesn't use extra lookups.
- sizestring
- Only this size. Letter sizes are normalized, so
M,mediumandSize Mediummatch each other, and8matches8.0. - conditioncomma-separated
new,used, or any exact condition value, e.g.used_like_new,used_excellent. Listings with no condition are left out when you filter.- daysinteger · 1–365
- Only sales from the last N days.
- min_price / max_pricenumber
- Only sales in this price range.
- excludecomma-separated
- Drop listings whose title contains any of these words, e.g.
exclude=stained,damaged. - cleanboolean · default true
- Clean comps (below). Set
clean=falseto get the marketplace's raw results; your filters still apply.
Clean comps
Marketplace search is loose: "lululemon align" also returns joggers, bodysuits and bundles, and "nike dunk low" mixes toddler sizes with adult pairs. Before computing anything, SoldStack removes:
- off_topic
- Titles missing one of your search words. Numbers in your search can also match the size, so
lululemon align 6means size 6. - bundles
- Bundles, lots and "X & Y" listings of two different items, unless you searched for a bundle.
- kids_sizes
- Toddler, youth and GS sizes, unless your search says kids, toddler, youth or GS.
- other_item_types
- When most matches are one kind of item (bags, leggings, sneakers), other kinds are dropped. Name the type in your search to choose it yourself.
- duplicates
- Identical listings that sold the same day count once.
- price_outliers
- Prices far outside the rest, once there are at least 8 sales to compare.
What comes back
Response
A JSON object with the normalized query, the source, a stats summary, a cleaning report and the items that sold. This is a real response, trimmed to one item:
{
"query": "coach tabby",
"source": "poshmark",
"stats": {
"count": 48,
"median": 194.0,
"mean": 247.25,
"p10": 107.0,
"p90": 409.9,
"min": 50.0,
"max": 775.0,
"median_days_to_sell": 4.2,
"median_net_payout": 155.2,
"suggested_max_buy": 51.73
},
"cleaning": {
"found": 48,
"used": 46,
"removed": { "bundles": 1, "other_item_types": 1 },
"item_type": "bags",
"confidence": "medium",
"confidence_note": "46 matching sales, but prices vary. Add a model, size or condition to narrow it."
},
"items": [
{
"source": "poshmark",
"id": "6aa7b1f5353f0a1005ca7d66",
"title": "Coach Tabby 20",
"brand": "Coach",
"size": "OS",
"condition": "used_good",
"price": 135.0,
"currency": "USD",
"sold_at": "2026-09-22T10:58:58-07:00",
"listed_at": "2026-09-14T01:51:24-07:00",
"days_to_sell": 8.4,
"url": "https://poshmark.com/listing/Coach-Tabby-20-6aa7b1f5353f0a1005ca7d66",
"image": "https://di2ponv0v5otw.cloudfront.net/posts/…jpeg"
}
]
}
stats
Computed over every clean match, not just the ones returned in items. When nothing matched, stats is just {"count": 0} and items is empty.
- countinteger
- How many sold listings matched.
- mediannumber · USD
- The middle sold price. The best single "what it sells for" number, because one unusually high sale doesn't move it.
- meannumber · USD
- The average sold price.
- p10 / p90number · USD
- The realistic low and high end: 10% of sales were below
p10, 10% abovep90. - min / maxnumber · USD
- Cheapest and most expensive sale.
- median_days_to_sellnumber · days · or null
- Typical time from listing to sale. A low number means the price moves quickly.
- median_net_payoutnumber · USD
- What the seller keeps from the median price after the marketplace fee. On Poshmark: $2.95 flat under $15, otherwise 20%.
- suggested_max_buynumber · USD
- A third of the net payout: a common reseller rule for the most to pay when sourcing an item to flip.
cleaning
What was removed and how far to trust the stats.
- foundinteger
- Sold listings the marketplace returned.
- usedinteger
- How many were kept and used for
stats(same asstats.count). - removedobject
- Count per reason:
filters,off_topic,bundles,kids_sizes,other_item_types,duplicates,price_outliers. Reasons with nothing removed are left out. - item_typestring · or null
- The kind of item the comps are about, e.g.
bags,pants/leggings,shoes.mixedwhen no single kind dominates. - confidencehigh · medium · low
high: 15+ matching sales with consistent prices.medium: 6+ sales, or prices that vary.low: few sales, very different prices, or no matches.- confidence_notestring
- A plain-English reason and a tip, ready to show your users.
items[]
Every sold listing comes back in the same shape, whatever the marketplace. Items are in the marketplace's order, which is by relevance, not date.
- sourcestring
- Marketplace the sale came from.
- idstring
- The marketplace's listing ID.
- titlestring
- Listing title as the seller wrote it.
- brandstring · or null
- Brand, when the seller set one.
- sizestring · or null
- Size as listed, e.g.
6,MorOS(one size). - conditionstring · or null
- One of
new_with_tags,not_new_with_tags,used_like_new,used_excellent,used_good,used_fair.nullwhen the seller didn't say. - pricenumber
- The price it sold at, in
currency. - currencystring
- ISO currency code, usually
USD. - sold_atstring · ISO 8601 · or null
- When it sold.
- listed_atstring · ISO 8601 · or null
- When it was first listed.
- days_to_sellnumber · or null
- Days between listing and sale.
- urlstring
- Link to the original listing.
- imagestring · or null
- Link to the listing's main photo.
Endpoint
GET /v1/verdict
Should you buy it? Send the item and what you'd pay (say, the thrift-store tag price) and get buy, pass or maybe, with a reason you can show your users. It uses the same clean comps and filters as /v1/sold and costs the same single lookup, and asking again for the same item is free for 6 hours.
curl "https://soldstack.fly.dev/v1/verdict?q=nike+dunk+low&cost=15&days=90" \
-H "X-API-Key: ss_your_key"
Query parameters
- qstring · required
- The item, as specific as you can, e.g.
coach tabby 26. - costnumber · required
- What you'd pay for it.
- min_profitnumber · default 10
- The smallest profit after fees that's worth the effort.
- limitinteger · 0–50 · default 5
- How many of the comps to include in
compsas evidence. - filtersoptional
- Every filter from
/v1/soldworks here too:size,condition,days,min_price,max_price,exclude,clean.
How the verdict is decided
- pass
- After the marketplace fee, the expected profit (
median_net_payout − cost) is belowmin_profit, or it's a loss. - buy
- The profit clears
min_profit, the cost is at or under the max-buy price (a third of the net payout), confidence isn't low, prices agree (the p10–p90 range is no more than 1.5× the median), and it typically sells within 90 days. A wide price range usually means the search mixes different versions of the item, such as kids' and adult sizes or worn and deadstock pairs, so the verdict asks you to narrow it first. - maybe
- Profitable on paper, but one of the buy checks fails. The
reasonsays which.
Response
{
"query": "nike dunk low",
"verdict": "maybe",
"reason": "Sells for about $78 in about 40 days; at $15 you'd keep about $47.40 after fees. But prices for this search range from $32 to $185, so add the model, size or condition to be sure you're comparing the same item.",
"cost": 15,
"expected_sale_price": 78.0,
"expected_net_payout": 62.4,
"expected_profit": 47.4,
"roi": 3.16,
"max_buy": 20.8,
"days_to_sell": 40.0,
"confidence": "medium",
"stats": { … same as /v1/sold … },
"cleaning": { … same as /v1/sold … },
"comps": [ … up to limit sold items … ]
}
- verdictbuy · pass · maybe
- The answer.
- reasonstring
- One or two plain-English sentences explaining it.
- expected_profitnumber · or null
- Net payout after fees minus
cost. Shipping is paid by the buyer on Poshmark.nullwhen there were no comps. - roinumber · or null
- Profit divided by cost:
3.16means you'd make 3.16 times what you paid. - max_buy / days_to_sell / confidence
- Same meaning as in
/v1/sold, repeated here so you don't have to dig intostats.
When something's wrong
Errors
Errors use normal HTTP status codes and a JSON body with a plain-English detail, e.g. {"detail": "invalid API key"}.
- 400Bad request
qis shorter than 2 or longer than 100 characters,sourceisn't live yet, orconditionhas an unknown value. Nothing is counted.- 401Unauthorized
- The API key is missing or wrong.
- 422Invalid parameter
qis missing, or a parameter has the wrong type or range, e.g.limit=abcorlimit=500.- 429Limit reached
- You've used this month's lookups. Upgrade on your account page, or wait until the 1st.
- 502Marketplace error
- The marketplace didn't answer properly. Nothing was counted against your plan. Retry after a minute.
Plans
Limits & caching
- Free$0
- 100 lookups a month.
- Starter$9 / month
- 2,500 lookups a month.
- Growth$29 / month
- 15,000 lookups a month.
- Scale$79 / month
- 60,000 lookups a month.
- One lookup = one request to
/v1/soldor/v1/verdictthat has to fetch fresh data, whatever thelimitor filters. - Repeats are free. Results are cached for 6 hours per search and source. Asking the same search again within 6 hours returns the cached result and doesn't count.
- Counts reset on the 1st of each month (UTC).
- Speed: cached results return in well under a second; fresh searches usually take 1–2 seconds. Requests to the marketplace are spaced out, so a burst of new searches can take longer.
Copy and paste
Code examples
JavaScript (Node 18+)
const res = await fetch(
"https://soldstack.fly.dev/v1/sold?q=" + encodeURIComponent("coach tabby"),
{ headers: { "X-API-Key": process.env.SOLDSTACK_KEY } }
);
if (!res.ok) throw new Error((await res.json()).detail);
const { stats, items } = await res.json();
console.log(`Sells for about $${stats.median}, pay under $${stats.suggested_max_buy}`);
Python
import os, requests
r = requests.get(
"https://soldstack.fly.dev/v1/sold",
params={"q": "coach tabby", "limit": 10},
headers={"X-API-Key": os.environ["SOLDSTACK_KEY"]},
timeout=30,
)
r.raise_for_status()
stats = r.json()["stats"]
print(f"Sells for about ${stats['median']} in {stats['median_days_to_sell']} days")
Good to know
About the data
- Where it comes from: the marketplace's public sold listings, the same results a shopper sees when they filter a search to sold items.
- Sold price: Poshmark shows the last listed price on a sold item. If the buyer and seller agreed a lower offer privately, the real final price may be a little lower. Use
medianrather than any single sale. - Matching: clean comps remove most mismatches, but a model name can still cover several versions (a Coach Tabby comes in sizes 20, 26 and 36 at very different prices). Add the model number, size or item type to your search for the tightest numbers, and check
cleaning.confidence. - How old the sales are: the marketplace ranks sold results by relevance, so some sales can be months or years old. For today's prices, add
days=90(or your own window). - Freshness: fetched live on the first request, then cached for 6 hours.
Help
Support
Questions, a lost key, or a marketplace you need? Email aarontian2010@gmail.com. You'll get a reply from a person, usually within a day.
Using the API means you agree to the Terms. How we handle data is in the Privacy Policy. For code generators there's an OpenAPI spec.