What happened to findCompletedItems
For years, findCompletedItems in eBay's Finding API was how developers got sold and completed listings. eBay decommissioned the whole Finding API on February 4, 2025. The Browse API that replaced it for search returns items currently for sale only, and Marketplace Insights, the one official API with sold items, is a Limited Release that is not accepting new developers. The full history is in does eBay have a sold listings API?
This guide is the practical part: what to change in your code, filter by filter and field by field.
Before and after
# Before: eBay Finding API (decommissioned February 4, 2025). This call now fails.
import requests
params = {
"OPERATION-NAME": "findCompletedItems",
"SERVICE-VERSION": "1.13.0",
"SECURITY-APPNAME": "YOUR-APP-ID",
"RESPONSE-DATA-FORMAT": "JSON",
"GLOBAL-ID": "EBAY-US",
"keywords": "nintendo switch oled",
"itemFilter(0).name": "SoldItemsOnly",
"itemFilter(0).value": "true",
"sortOrder": "EndTimeSoonest",
"paginationInput.entriesPerPage": "100",
}
r = requests.get("https://svcs.ebay.com/services/search/FindingService/v1", params=params)Create a free key at compsniper.com/signup (100 requests a month, no credit card) and set it as COMPSNIPER_API_KEY. No eBay developer account, App ID or OAuth flow is needed.
Request filters: old to new
| findCompletedItems | CompSniper /v1/scrape | Notes |
|---|---|---|
| keywords | keyword | Same search text. eBay matching rules apply as before. |
| categoryId | categoryId | Same eBay category IDs. |
| GLOBAL-ID (EBAY-US, EBAY-GB, EBAY-DE, EBAY-FR, EBAY-IT, EBAY-ES, EBAY-ENCA, EBAY-AU) | ebaySite (ebay.com, ebay.co.uk, ebay.de, ebay.fr, ebay.it, ebay.es, ebay.ca, ebay.com.au) | Marketplace IDs such as EBAY_GB are also accepted. |
| itemFilter SoldItemsOnly=true | sold=true (the default) | Every row is a completed sale. Use sold=false for active listings. |
| itemFilter Condition | itemCondition=new|used or conditionId | conditionId takes eBay's numeric condition IDs (1000, 3000 and so on). |
| itemFilter MinPrice / MaxPrice | minPrice / maxPrice | In the marketplace's currency. |
| itemFilter ListingType (Auction, FixedPrice) | buyingFormat=auction|buyItNow|acceptsOffers | |
| itemFilter LocatedIn | itemLocation=domestic|worldwide | Nearest equivalent: domestic keeps sellers in the marketplace's country. |
| itemFilter EndTimeFrom / EndTimeTo | soldAfter / soldBefore | Dates as YYYY-MM-DD. |
| sortOrder EndTimeSoonest | sortOrder=endedRecently | Newest sales first. |
| sortOrder PricePlusShippingLowest / Highest | sortOrder=pricePlusPostageLowest / pricePlusPostageHighest | |
| sortOrder StartTimeNewest | sortOrder=timeNewlyListed | |
| paginationInput.entriesPerPage (max 100) | count (max 240) | Up to 240 sold listings per request, billed as one request. |
| paginationInput.pageNumber | page | Read hasNextPage in the response. |
| outputSelector SellerInfo | Always included | Seller name, feedback score and positive percent on every row. |
| SECURITY-APPNAME (App ID) | Authorization: Bearer cs_... | A key from the dashboard. No eBay developer account or approval. |
Invalid values (an unknown sortOrder, a malformed date) return 400 invalid_params with a message naming the parameter. Every parameter is in the API reference.
Response fields: old to new
| findCompletedItems | CompSniper |
|---|---|
| searchResult.item[] | items[] |
| itemId | itemId |
| title | title |
| viewItemURL | url |
| galleryURL | thumbnailUrl (fullResThumbnailUrl for a larger image) |
| primaryCategory.categoryId | categoryId |
| sellingStatus.currentPrice | soldPrice + soldCurrency (string amount, marketplace currency) |
| sellingStatus.sellingState = EndedWithSales | Not needed: every sold row is a completed sale |
| sellingStatus.bidCount | bidCount |
| shippingInfo.shippingServiceCost | shippingPrice + shippingCurrency |
| shippingInfo.shippingType | shippingType |
| listingInfo.endTime | endedAt |
| listingInfo.listingType | listingType and buyingFormat |
| listingInfo.bestOfferEnabled | bestOfferAccepted (the offer was accepted, not just enabled) and, with bestOfferPrice=true, acceptedOfferPrice |
| condition.conditionId / conditionDisplayName | conditionId / condition |
| sellerInfo.sellerUserName | sellerUsername |
| sellerInfo.feedbackScore / positiveFeedbackPercent | sellerFeedbackScore / sellerPositivePercent |
| location / country | itemLocation |
| productId (ePID) | epid (usually null on sold results) |
| paginationOutput.totalEntries | totalItems, with hasNextPage |
| (none) | summary: median, p25, p75, mean, min, max and average shipping, calculated for you |
Prices are strings in the marketplace's own currency, so an ebay.co.uk search returns GBP and an ebay.de search returns EUR. There is no converted-currency field; convert in your own code if you need one currency.
Keep old code running with a drop-in adapter
If your code reads item["sellingStatus"]["currentPrice"] all over the place, wrap the new call so it returns the old shape. This adapter also asks for real Best Offer prices and puts the price the buyer actually paid into currentPrice when it is known:
# Drop-in adapter: returns rows shaped like findCompletedItems items,
# so code that read item["sellingStatus"]["currentPrice"] keeps working.
import os
import requests
SITES = {"EBAY-US": "ebay.com", "EBAY-GB": "ebay.co.uk", "EBAY-DE": "ebay.de",
"EBAY-FR": "ebay.fr", "EBAY-IT": "ebay.it", "EBAY-ES": "ebay.es",
"EBAY-ENCA": "ebay.ca", "EBAY-AU": "ebay.com.au"}
def find_completed_items(keywords, global_id="EBAY-US", page=1, **filters):
r = requests.get(
"https://api.compsniper.com/v1/scrape",
headers={"Authorization": f"Bearer {os.environ['COMPSNIPER_API_KEY']}"},
params={"keyword": keywords, "ebaySite": SITES[global_id], "sold": "true",
"count": 240, "page": page, "bestOfferPrice": "true", **filters},
timeout=90,
)
r.raise_for_status()
data = r.json()
items = []
for row in data["items"]:
paid = row.get("acceptedOfferPrice") or row["soldPrice"]
items.append({
"itemId": row["itemId"],
"title": row["title"],
"viewItemURL": row["url"],
"galleryURL": row.get("thumbnailUrl"),
"primaryCategory": {"categoryId": row.get("categoryId")},
"sellingStatus": {
"currentPrice": {"value": paid, "currencyId": row["soldCurrency"]},
"sellingState": "EndedWithSales",
"bidCount": row.get("bidCount"),
},
"shippingInfo": {"shippingServiceCost": {"value": row.get("shippingPrice"),
"currencyId": row.get("shippingCurrency")}},
"listingInfo": {"endTime": row.get("endedAt"),
"listingType": row.get("listingType"),
"bestOfferAccepted": row.get("bestOfferAccepted")},
"condition": {"conditionId": row.get("conditionId"),
"conditionDisplayName": row.get("condition")},
"sellerInfo": {"sellerUserName": row.get("sellerUsername"),
"feedbackScore": row.get("sellerFeedbackScore"),
"positiveFeedbackPercent": row.get("sellerPositivePercent")},
})
return {"items": items, "hasNextPage": data.get("hasNextPage"), "summary": data.get("summary")}Prefer the native response for new code: it is flatter and includes the calculated summary. The JavaScript and TypeScript guide shows the same call in Node.
What you gain over findCompletedItems
- 240 sold items per request instead of 100, billed as one request.
- Real Best Offer prices. findCompletedItems only told you an offer was possible.
bestOfferPrice=truereturnsacceptedOfferPrice, what the buyer actually paid, on US-dollar sales at no extra cost. See accepted Best Offer prices. - 3 years of history.
GET /v1/research/soldgoes back up to 3 years, on every plan including free. - A price summary in every response: median, 25th to 75th percentile, mean, min, max and average shipping.
- Optional AI cleanup that removes accessories, parts and wrong models before the median, or
relevance=falsefor eBay's results exactly as listed. - Beyond 240: Max Mode pages deeper with a CSV download, and bulk search runs up to 20 keywords at once.
Migration checklist
- Create a key and store it server-side as
COMPSNIPER_API_KEY. - Replace the Finding API URL and App ID with
GET /v1/scrapeand theAuthorizationheader. - Map your
itemFiltervalues using the request table above. SetebaySiteon every call. - Read
items[]andhasNextPage, or drop in the adapter. - Retry
429and503with backoff. Failed requests are not charged. See the error guide. - Cache per keyword for a day; sold prices change daily, not every second.
- Optional: add
bestOfferPrice=trueand switch long-range reports to/v1/research/sold.
findCompletedItems migration questions
Does findCompletedItems still work?
No. findCompletedItems was part of eBay's Finding API, which eBay decommissioned on February 4, 2025. Calls to it now fail, and eBay's Browse API, its official successor for search, returns active listings only.
What is the official replacement for findCompletedItems?
eBay's Marketplace Insights API returns sold items from the last 90 days, but it is a Limited Release that is not open to new developers. Most teams replace findCompletedItems with a third-party sold listings API that works without eBay approval.
How do I migrate findCompletedItems code to CompSniper?
Swap the Finding API call for GET /v1/scrape with your API key, map keywords to keyword, GLOBAL-ID to ebaySite, the sold, condition, price and end-time item filters to sold, itemCondition, minPrice, maxPrice, soldAfter and soldBefore, and read items[] instead of searchResult.item[]. The adapter on this page returns rows in the old shape so existing code keeps working.
Does the replacement return more than findCompletedItems did?
Yes. Up to 240 sold items per request against 100, a calculated median and price range in every response, the real accepted price on Best Offer sales with bestOfferPrice=true, and up to 3 years of sold history on /v1/research/sold, on every plan including free.
How far back does it go?
Like findCompletedItems, the standard sold search covers eBay's recent sold window of about 90 days. For older sales, GET /v1/research/sold returns up to 3 years of eBay sold history with the same key.
Is it free?
There is a free plan with 100 requests a month and no credit card. Paid plans start at $9 a month for 2,000 requests. Each request returns up to 240 sold listings and failed requests are not charged.
eBay API status and CompSniper parameters checked October 14, 2026.