Sitemap

eBay Sold Listings API: Build a Price Checker With Real Comps

6 min readSep 15, 2026

--

The request is straightforward. Deciding what the returned prices mean takes more care.

In a Shure SM7B search collected for CompSniper on August 30, 2026, the raw results had a $200 median. After relevance filtering, the median was $215. The search had not changed. The group of listings used in the calculation had.

I build CompSniper, the independent service used in this walkthrough. This article was prepared with AI assistance; the sources, code examples and dated sample are identified below.

That microphone example is a useful place to start because both prices look plausible. A price checker displaying either number could appear to work. The question is whether it can show the sales behind its estimate, explain its filters and handle the cases where there is not enough useful evidence.

Here is how to build a small command-line checker that does that. You can reuse the same request in a backend for a web app or an inventory tool.

Press enter or click to view image in full size
Shure SM7B sold-price sample: 240 raw listings with a $200 median versus 207 listings after relevance filtering with a $215 median, captured August 30, 2026.
CompSniper’s Shure SM7B sample, captured August 30, 2026. Filtering reduced 240 rows to 207 and changed the median from $200 to $215. This is one dated observation, not an accuracy score or a current valuation.

First, choose the data you actually need

An active listing answers, “What is someone asking?” A sold listing helps answer, “What has a buyer been willing to pay?” A seller’s order history answers a third question: “What did this particular seller sell?”

These are different datasets. An API returning current asking prices is not a substitute for one returning completed sales.

This distinction matters when following older eBay tutorials. eBay’s Finding API, which included findCompletedItems, was decommissioned on February 4, 2025. Although eBay names Browse as its replacement, eBay Developer Support explicitly says Browse returns active items, not sold listings. Marketplace Insights covers sold-history use cases, but eBay still describes it as restricted and not open to new users. eBay deprecation status, Developer Support clarification, Buy API restrictions.

For occasional manual research, eBay’s own sold search or Product Research may be sufficient. An API becomes useful when another application needs structured results repeatedly. The example below uses CompSniper; it is not an official eBay endpoint.

Describe the comparison before requesting it

“Microphone” is not a useful comparison specification. Even “Shure SM7B” needs context. Are you pricing a used microphone, a new boxed unit, a bundle, or a damaged item sold for parts?

For this example, the starting scope is:

  • Shure SM7B
  • Used condition.
  • The US eBay marketplace.
  • One page of recently ended sold results.

The marketplace belongs in the request. A UK seller should not quietly receive a US sample just because the application left its default unchanged. Set ebaySite=ebay.co.uk when that is the market you intend to research, and retain the response currency beside the amount.

Do not begin by setting a narrow price floor around what you hope the item is worth. That would discard contrary evidence before you inspect it. Use product identity and condition to define the initial group, then examine unusual prices in context.

Make one request

Create a CompSniper API key and store it in your terminal environment as COMPSNIPER_API_KEY. The full key begins with cs_live_.

Get Marc Andrew’s stories in your inbox

Join Medium for free to get updates from this writer.

This cURL command requests one page. It does not start an unbounded collection job:

curl --fail-with-body --get \
--header "Authorization: Bearer ${COMPSNIPER_API_KEY}" \
--data-urlencode "keyword=shure sm7b" \
--data-urlencode "ebaySite=ebay.com" \
--data-urlencode "itemCondition=used" \
--data-urlencode "sold=true" \
--data-urlencode "relevance=true" \
--data-urlencode "count=240" \
--data-urlencode "sortOrder=endedRecently" \
--max-time 75 \
"https://api.compsniper.com/v1/scrape"

The count value is a ceiling, not a promise. If the search returns 38 rows, requesting 240 does not make the other 202 exist. Nor does one page represent every sale on eBay. Keep the response's pagination information if you later add bounded paging. CompSniper API reference.

Turn the response into a small checker

Save this as price-checker.mjs and run it with Node.js 22 or later:

const key = process.env.COMPSNIPER_API_KEY;
if (!key) throw new Error("Set COMPSNIPER_API_KEY first.");
const params = new URLSearchParams({
keyword: process.argv.slice(2).join(" ") || "shure sm7b",
ebaySite: "ebay.com",
itemCondition: "used",
sold: "true",
relevance: "true",
count: "240",
sortOrder: "endedRecently",
});
const response = await fetch(
`https://api.compsniper.com/v1/scrape?${params}`,
{
headers: { Authorization: `Bearer ${key}` },
signal: AbortSignal.timeout(75_000),
},
);
const requestId = response.headers.get("x-request-id");
const data = await response.json().catch(() => null);
if (!response.ok) {
const detail = data?.code || data?.error || "Request failed";
throw new Error(
`HTTP ${response.status}: ${detail}; request ${requestId || "unknown"}`,
);
}
if (!data || !Array.isArray(data.items) || !data.summary) {
throw new Error("Unexpected response shape; do not display a price.");
}
if (data.items.length === 0) {
console.log("No sold listings returned for this search.");
} else {
console.log({
query: params.get("keyword"),
marketplace: params.get("ebaySite"),
checkedAt: new Date().toISOString(),
returnedListings: data.items.length,
pricedSample: data.summary.count,
currency: data.summary.currency,
medianSoldPrice: data.summary.median,
middle50Percent: [data.summary.p25, data.summary.p75],
morePages: data.hasNextPage,
requestId,
});
console.table(data.items.slice(0, 10).map((item) => ({
title: item.title,
condition: item.condition,
price: item.soldPrice,
currency: item.soldCurrency,
shipping: item.shippingPrice,
soldDate: item.endedAt,
bestOffer: item.bestOfferAccepted,
listing: item.url,
})));
}

Run node price-checker.mjs "shure sm7b" after setting the environment variable. The key stays in a trusted server or terminal environment, not in JavaScript shipped to a visitor's browser.

The output deliberately includes the sample size and evidence rows. If you build a web interface around it, make the listing links accessible next to the estimate. checkedAt records when your program received the response, not necessarily when the underlying data was fetched; responses can be cached.

This example makes one request and does not retry automatically. That makes its behavior easy to inspect before introducing scheduled jobs.

Read the sample without overstating it

Here is an abbreviated excerpt from the published August 30 microphone sample. It is historical example data, not a quote for today:

{
"keyword": "shure sm7b",
"totalItems": 207,
"rawMedian": 200,
"rawSampleCount": 240,
"summary": {
"count": 207,
"currency": "USD",
"median": 215,
"p25": 175.71,
"p75": 260
}
}

The underlying published example also includes a listing titled “Shure SM7B Cardioid Dynamic Vocal Microphone — Complete in Box Excellent,” with a displayed sold price of $199.99 and date of August 29, 2026. These fields are available in the original dated example.

Filtering removed 33 of the 240 raw rows. The median moved by $15, or 7.5%. That is a change in the estimate, not proof of a 7.5% improvement in accuracy. The example does not establish that every retained listing is comparable, or that other searches will move in the same direction.

The $175.71 to $260 interquartile range describes the middle half of that priced sample. It is not a guarantee that a particular microphone will sell within the range. A useful interface can label it “middle 50% of sampled prices” instead of presenting it as a promised resale value.

Keep the details that can change the decision

Condition and variant. A low price may describe damage, missing accessories, a different revision or an incomplete bundle. Review those differences before treating a low result as a bargain or an outlier. An unavailable condition should remain unknown; it should not become “used” because that is convenient for the interface.

Shipping. Item price and buyer shipping cost are separate amounts. When comparing delivered cost, use available item-plus-shipping totals on a consistent basis. Missing shipping information is not zero. Neither figure is the seller’s net proceeds after fees, postage and other expenses.

Best Offers. A bestOfferAccepted marker is not disclosure of the accepted amount. The visible price may differ from what the buyer paid. If you exclude marked rows from your own estimate, calculate a new summary for that subset; the API's original summary will not automatically change to match a filter you apply afterward. eBay's own Product Research documents access to actual accepted-offer prices, which is a distinct capability from this example. eBay Product Research.

Dates and coverage. Record the request settings and inspect the returned dates. A sampled page, especially for an uncommon item, is not a complete transaction history. Sparse evidence should stay visible as sparse evidence.

Add automation only after the one-request version is useful

A production checker needs different behavior for an empty result, an authentication failure, a temporary limit and an exhausted allowance. Do not turn every error into an empty array and display “$0.00.”

CompSniper distinguishes a temporary rate_limited response from quota_exceeded. Respect Retry-After for the former; repeatedly retrying the latter will not create more allowance. Keep retries and page counts bounded. A client-side timeout also does not prove that the server never completed the request, so avoid treating a retry as automatically cost-free. Error documentation.

The first useful version can remain small: one well-specified search, a sample summary and links to the returned listings. Once someone can inspect those comps and explain why they belong together, you have a sounder starting point for an application than a bare price with no evidence behind it.

Try the sold-listings API and get a key.

--

--

Marc Andrew
Marc Andrew

Written by Marc Andrew

0 followers

Building CompSniper. Sharing practical guides on eBay sold prices, resale research, and APIs for developers and resellers.