DEV Community

Sam Smith
Sam Smith

Posted on

How to get eBay sold listings data in 2026 (findCompletedItems is dead)

If you build anything that needs eBay sold prices, 2026 broke your stack twice. Here is the current state of every route to completed listings data, and working code for the one that still works.

Full disclosure up front: I run CompsAPI, which is one of the options below. The rest of this post is accurate whether or not you use it.

What actually happened

A short timeline, because half the tutorials online are now wrong:

  • 2020: eBay deprecates findCompletedItems, the Finding API call everyone used for sold listings.
  • February 2025: the whole Finding API is decommissioned. Code that called it gets security errors, not data.
  • The official replacement, the Marketplace Insights API, is a limited release. You apply with a business case, and approval is restricted to established eBay partners. Most applications go nowhere, and the developer forums are full of people stuck at the same wall.
  • Mid 2026: eBay puts the public sold listings filter behind a login. The LH_Sold=1 URL that every scraper and browser extension relied on now redirects to a sign in page for most traffic.

So the two free routes, the official API and scraping the public page, are both gone. What is left:

Option 1: Terapeak, if a human is doing the looking

eBay's own research tool (now called Product Research, inside Seller Hub) shows up to three years of sold data. If you price items by hand a few times a day, it is genuinely good and costs nothing extra with a seller account.

It stops at the browser: no API, no export of research results, and sellers report daily caps on queries. The moment you need sold prices in a spreadsheet, a pricing model or an app, there is no door.

Option 2: scrapers against the logged in page

Some scrapers now run eBay accounts to get through the login wall. Two structural problems. First, the public sold page shows at most 90 days of history. Second, when a sale closes as an accepted Best Offer, the page shows the asking price, not what the buyer actually paid. In offer heavy categories (watches, cards, anything expensive) those numbers run high by 20 to 40 percent per affected row. A scraper inherits both limits because the page itself is the ceiling.

Option 3: a sold listings API

This is what I build. One GET request, JSON back, up to 1,095 days of history per search, and on accepted Best Offer sales the price field is the real closing amount, flagged per row.

Get a free key at app.compsapi.com (100 searches a month, no card), then:

curl -G "https://api.compsapi.com/v1/sold" \
  -H "Authorization: Bearer sk_live_YOUR_KEY" \
  --data-urlencode "q=charizard psa 10" \
  -d "days=365" -d "best_offer=1"
Enter fullscreen mode Exit fullscreen mode

Response, trimmed:

{
  "ok": true,
  "count": 239,
  "items": [
    {
      "title": "Charizard PSA 10 Base Set Holo",
      "price": 1450.0,
      "best_offer": true,
      "last_sold": "2026-10-04 18:32",
      "format": "FIXED_PRICE",
      "shipping_cost": 12.5,
      "url": "https://www.ebay.com/itm/..."
    }
  ],
  "next_page": true
}
Enter fullscreen mode Exit fullscreen mode

That best_offer: true row is the point. On the eBay page this sale displays its asking price. Here it is the amount it closed at.

The Python version

There is a zero dependency client (github.com/compsapi/compsapi-python):

pip install git+https://github.com/compsapi/compsapi-python.git
Enter fullscreen mode Exit fullscreen mode
from compsapi import CompsAPI

api = CompsAPI("sk_live_YOUR_KEY")
r = api.sold("iphone 14 pro 256gb", days=365, condition="used")
prices = sorted(s["price"] for s in r["items"])
print(f"{r['count']} sales, median ${prices[len(prices)//2]}")
Enter fullscreen mode Exit fullscreen mode

Three years of price history in 15 lines

The thing you could never do with the public page, since it stops at 90 days:

from collections import defaultdict
from statistics import median
from compsapi import CompsAPI

api = CompsAPI("sk_live_YOUR_KEY")
monthly = defaultdict(list)

for sale in api.iter_sold("iphone 14 pro 256gb", days=1095, max_results=3000):
    monthly[sale["last_sold"][:7]].append(sale["price"])

for month in sorted(monthly):
    print(month, round(median(monthly[month]), 2), f"({len(monthly[month])} sales)")
Enter fullscreen mode Exit fullscreen mode

Output is a monthly depreciation curve built from real completed sales. Each page of 240 sales costs one request, so a three year pull like this uses about a dozen requests.

If you want a pre-aggregated answer instead of rows, /v1/comps returns a cleaned median, the spread and the newest matches in one call, with lots, bundles and wrong model junk filtered before the math runs.

Honest limitations

  • eBay US is the primary marketplace, with matching sales from UK, DE, FR, IT, ES, CA and AU merged in or pinned with site=.
  • No seller information on rows.
  • The free tier is 100 requests a month. Paid plans start at $9.

If you came here from dead code

Migrating from findCompletedItems: your keywords becomes q, EndTimeFrom/To becomes days, entriesPerPage becomes size (max 240 instead of 100), and the response is flat JSON instead of a SOAP envelope. There is a field by field mapping at compsapi.com/replace/findcompleteditems.

Questions welcome in the comments. If you hit something the API does not cover, tell me and there is a decent chance it ships.

Top comments (0)