Launch week: 50% off your first 3 months on any paid plan with code 50% off 3 months: at checkout · ends Oct 12 · See plans

API docs.

Send a search, get back the sales that actually match, the numbers you'd otherwise work out yourself, and a buy or pass verdict.

Quickstart

  1. Get a key. Enter your email on the home page. Free keys include 100 lookups a month, no card needed.
  2. Make a request. Send your search as q and your key in the X-API-Key header.
  3. Use the numbers. stats.median is the typical sold price; items lists every sale behind it.
Terminal
curl "https://soldstack.dev/v1/sold?q=coach+tabby" \
  -H "X-API-Key: ss_your_key"

Base URL: https://soldstack.dev. Everything is HTTPS and JSON. Uptime and incidents: status page.

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.

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: poshmark (US) or poshmark_ca (Poshmark Canada). Canadian results are in CAD (stats.currency and each item's currency say so), link to poshmark.ca, and median_net_payout uses Poshmark Canada's fee: a flat C$3.95 under C$20, otherwise 20%, before GST/HST on the fee. 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. A normal lookup reads the 48 most recent sales, so you'll get at most 48 back unless you pass deep=true.
deepboolean · default false
Read up to about 190 recent sales (four Poshmark result pages) instead of 48. Slower when not cached (about 10 seconds) and still counts as one lookup. Cached separately from the normal lookup. Up to 120 new deep lookups per 10 minutes per key (cached repeats don't count); past that you get a 429 saying so.

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, medium and Size Medium match each other, and 8 matches 8.0.
conditioncomma-separated
new, used, or any exact condition value, e.g. used_like_new,used_excellent. Plain words work too: nwt, like new, excellent, very good, good, fair, poor. Listings with no condition are left out when you filter.
daysinteger · 1–365
Only sales from the last N days. Filters the sales behind stats too, unlike sort, which only orders the sales returned. To get the most recent sales, use days to set the window and sort=recent to order them.
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.
exclude_sellercomma-separated
Drop sales by these seller usernames (case-insensitive), e.g. exclude_seller=closet_a,Closet_B, before the stats are computed. Counted in cleaning.removed as excluded_seller. Sales cached before sellers were recorded have no seller and are never excluded.
departmentcomma-separated
Only sales in these Poshmark departments (case-insensitive): Women, Men, Kids, Home, Pets, Electronics. An unknown department returns a 400 listing the valid ones. department=kids also turns off the default kids_sizes removal, so kids' items are kept. Sales cached before departments were recorded have no department: kids falls back to the kids' size and title check, women and men keep them, and other departments drop them. Counted in removed as department.
categorycomma-separated
Only sales in these Poshmark categories (case-insensitive), e.g. category=Jeans,Shorts. Sales with no recorded category are kept. Counted in removed as category.
sortrecent · price_asc · price_desc
Order of the sales returned in items: recent is newest first (sales with no sold date last), price_asc and price_desc by price. It only orders the returned list, so stats never change; limit then keeps the first N in that order. Leave it out for the marketplace's order. To restrict which sales are used, see days.
include_removedboolean · default false
Add removed_items to the response: the sales cleaning and your filters dropped, each with a removed_reason (see removed_items[]), up to limit.
cleanboolean · default true
Clean comps (below). Set clean=false to 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 6 means size 6.
replicas
Listings whose title says replica, rep, dupe, fake, inspired, 1:1, look-alike or similar, unless you searched for those words. Price outliers (next) also catch most unlabeled fakes, which usually sell far below the real price.
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 asks for them. To price kids' clothing, put girls, boys, kids, toddler, youth or GS in q: girls jeans keeps the girls' sales.
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.
variants
Listings of a different model of the same item, e.g. a Pillow or Twisted Tabby when you searched Tabby 26, are removed.
duplicates
The same listing showing up twice (for example on two result pages) counts once. Two separate listings of the same item are two sales.
price_outliers
Prices far outside the rest, once there are at least 8 sales to compare.

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:

200 OK · application/json
{
  "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
  },
  "by_condition": [
    { "condition": "new_with_tags", "label": "New with tags", "count": 12, "median": 228, "p10": 195, "p90": 265 },
    { "condition": "used_excellent", "label": "Excellent", "count": 2, "median": 170 }
  ],
  "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",
      "seller": "closet_name",
      "department": "Women",
      "category": "Bags"
    }
  ],
  "usage": { "used": 12, "limit": 100, "remaining": 88, "plan": "free", "resets": "2026-11-01" }
}

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% above p90.
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.

by_condition

The same sales split by the condition the seller listed, best condition first, so you can price the item you actually have. Conditions with no sales are left out, as are older listings that have no condition.

conditionstring
new_with_tags, not_new_with_tags, used_like_new, used_excellent, used_good or used_fair. The same values the condition filter takes.
labelstring
Display name, e.g. "Like new".
countinteger
Sales in this condition.
mediannumber · USD
Middle sold price in this condition.
p10 / p90number · USD · optional
Typical range. Only included with 3 or more sales, since a range from one or two sales would mislead.

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 as stats.count).
removedobject
Count per reason: filters, excluded_seller, department, category, off_topic, replicas, bundles, kids_sizes, other_item_types, variants (a different model of the same item, e.g. a Pillow or Twisted Tabby when you searched Tabby 26), duplicates, price_outliers. Reasons with nothing removed are left out. include_removed=true returns the sales behind these counts.
item_typestring · or null
The kind of item the comps are about, e.g. bags, pants/leggings, shoes. mixed when 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.
notestring · optional
Only present with partial_matches (below): says those sales are not part of the stats.

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, M or OS (one size).
conditionstring · or null
One of new_with_tags, not_new_with_tags, used_like_new, used_excellent, used_good, used_fair. null when 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.
sellerstring · or null
The seller's username (Poshmark). Missing on sales cached before this field was added. Use it with exclude_seller.
departmentstring · or null
Poshmark's department: Women, Men, Kids, Home, Pets or Electronics. Missing on older cached sales. Use it with the department filter.
categorystring · or null
Poshmark's category, e.g. Jeans or Bags. Missing on older cached sales. Use it with the category filter.

removed_items[]

Only with include_removed=true. The sales that were dropped before the stats, in the marketplace's order, up to limit. Each has every field of items[] plus:

removed_reasonstring
Why it was dropped: one of the cleaning.removed keys (filters, excluded_seller, department, category, off_topic, replicas, bundles, kids_sizes, other_item_types, variants, duplicates, price_outliers). Counting them all gives cleaning.removed.

partial_matches[]

Only when cleaning leaves no sales and at least one sale was removed as off_topic. Up to 10 of those off-topic sales, closest first, so you can show "nearest matches" instead of nothing. They are not used in stats, and cleaning.note says so. Each has every field of items[] plus:

match_scorenumber · 0.5–1
The share of your search's meaningful words found in the title and brand, rounded to 2 decimals. Sales under 0.5 are left out, so the list can be empty.

usage

On every /v1/sold and /v1/verdict response, counted after this request. The same numbers come back as headers: X-Lookups-Used, X-Lookups-Limit, X-Lookups-Remaining and X-Plan.

usedinteger
Lookups used this month. A cached repeat doesn't add to it.
limitinteger
Your plan's lookups per month.
remaininginteger
limit minus used, never below 0. At 0 the next fresh lookup gets a 429.
planstring
free, starter, growth or scale.
resetsstring · ISO date
The first day of next month (UTC), when the count goes back to 0.

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.

Terminal
curl "https://soldstack.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 comps as evidence.
filtersoptional
Every filter from /v1/sold works here too: size, condition, days, min_price, max_price, exclude, exclude_seller, department, category, sort, include_removed, clean. usage and the X-Lookups-* headers come back too.

How the verdict is decided

pass
After the marketplace fee, the expected profit (median_net_payout − cost) is below min_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 reason says which.

Response

200 OK · application/json · trimmed
{
  "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. null when there were no comps.
roinumber · or null
Profit divided by cost: 3.16 means 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 into stats.

MCP server

SoldStack is also a remote MCP server, so an AI agent (Claude, Cursor, ChatGPT and others) can price resale items itself. There's nothing to install: point your client at the URL and send your API key. Each tool call is one lookup, metered exactly like the REST API.

MCP client config
{
  "mcpServers": {
    "soldstack": {
      "type": "http",
      "url": "https://soldstack.dev/mcp",
      "headers": { "X-API-Key": "ss_your_key" }
    }
  }
}

Add it to Claude (claude.ai and the Claude Desktop app)

Custom connectors sync across your Claude account, so you add SoldStack once and it shows up on the web and in the desktop app. They're available on the Free, Pro, Max, Team and Enterprise plans (Free allows one custom connector; on Team and Enterprise an owner adds it first, then members connect).

  1. Get your key: a free one takes a few seconds, or sign in on your account page.
  2. In claude.ai (or the Claude Desktop app), open Customize, then Connectors. Click + Add, then Add custom connector. On Team and Enterprise an owner goes to Organization settings > Connectors > Add > Custom instead.
  3. Name it SoldStack and enter the server URL https://soldstack.dev/mcp. Continue.
  4. SoldStack uses an API key, not a sign-in, so set authentication to No sign in. Then under Request headers add the header name X-API-Key with your key (ss_...) as the value. Click Add. (Authorization: Bearer ss_your_key works too.)
  5. Start a chat, open the connectors menu and switch SoldStack on. Ask "What did a Lululemon Align legging sell for on Poshmark?" or "I can buy this Coach Tabby 26 for $40, buy or pass?"

Menu names move around a little between Claude releases. If yours differs, look for Connectors and Add custom connector, and see Anthropic's custom connector guide. If your version has no field for request headers, use the URL form https://soldstack.dev/mcp?api_key=ss_your_key as the server URL. That URL contains your key, so keep it private like a password and don't paste it into shared chats or screenshots.

Claude Code, Cursor and other clients

Use the JSON above. For Claude Code you can run this instead:

Claude Code
claude mcp add --transport http soldstack https://soldstack.dev/mcp --header "X-API-Key: ss_your_key"

If your client only takes a URL, use https://soldstack.dev/mcp?api_key=ss_your_key and keep that URL private.

For AI agents: call the tools one item at a time, not in parallel. If a call returns busy, wait the number of seconds it says and call again.

Tools

sold_compsquery, size, condition, days, limit, deep, exclude_seller, sort, department, category, include_removed
What an item sold for: the same stats, by_condition and cleaning as /v1/sold, plus the matching sales, removed_items and partial_matches when they apply, and the same usage object.
buy_or_passquery, cost, min_profit, size, condition
BUY, MAYBE or PASS for an item at a given cost, with the reason, like /v1/verdict, plus usage.

Errors

Errors use normal HTTP status codes and a JSON body with a plain-English detail, e.g. {"detail": "invalid API key"}.

400Bad request
q is shorter than 2 or longer than 100 characters, source isn't live yet, condition has an unknown value, or department isn't one of Women, Men, Kids, Home, Pets, Electronics. Nothing is counted.
401Unauthorized
The API key is missing or wrong.
422Invalid parameter
q is missing, or a parameter has the wrong type or range, e.g. limit=abc or limit=500. detail is a readable string such as "limit: Input should be less than or equal to 200"; the structured list is in errors.
429Limit reached
Either you've used this month's lookups (upgrade on your account page, or wait until the 1st), or you're sending more than 120 requests per minute per key, or more than 120 new deep=true lookups per 10 minutes. The detail says which. Slow down and retry in a minute.
503Busy
A lot of fresh searches are queued ahead of yours. The Retry-After header and the detail say how many seconds to wait; retry after that. If the line was simply full, nothing was counted. If your request waited its turn and timed out, SoldStack keeps fetching it in the background (it counts as one lookup) so the retry returns instantly from cache and isn't counted again. Send one request at a time to avoid it.
502Marketplace error
The marketplace didn't answer properly or in time (e.g. "Poshmark didn't respond in time. Try again in a minute."). Nothing was counted against your plan. Retry after a minute.

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/sold or /v1/verdict that has to fetch fresh data, whatever the limit or 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.

Code examples

No code? Use your key in Google Sheets with =SOLDPRICE("coach tabby 26").

JavaScript (Node 18+)

server.js
const res = await fetch(
  "https://soldstack.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

prices.py
import os, requests

r = requests.get(
    "https://soldstack.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")

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 median rather 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.
  • How accurate it is: what cleaning removed in real searches, how confidence is decided and what it doesn't catch are on How accurate is this?

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.