eBay Sold Prices API - Confirmed Sale Prices avatar

eBay Sold Prices API - Confirmed Sale Prices

Pricing

from $2.50 / 1,000 results

Go to Apify Store
eBay Sold Prices API - Confirmed Sale Prices

eBay Sold Prices API - Confirmed Sale Prices

Get confirmed eBay sale prices - what buyers actually paid, not asking prices. 40,000+ completed orders reachable per seller, filterable by keyword. eBay's sold search needs a login; this does not. No API key.

Pricing

from $2.50 / 1,000 results

Rating

0.0

(0)

Developer

Blackcube Agency AB

Blackcube Agency AB

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

31 minutes ago

Last modified

Share

eBay Data Suite  •  7 Actors, one codebase, one result billed once
eBay Scraper API
Sold Prices, Listings & Sellers
eBay Watch Count Scraper API
Watchers & Sold
eBay Seller Scraper API
Inventory & Sales
eBay Sold Prices API
➤ You are here
eBay Card Comps API
PSA, BGS, CGC & Raw
eBay Price Tracker API
Monitor Listings
eBay Motors Scraper API
Cars, Parts & Bikes

More from this account: YouTube Transcript Suite · Website Contact & Email Suite · Career Site & ATS Jobs Suite · Google News Suite · Keyword Research Suite · Shopify Store Intelligence Suite · Amazon Reviews Suite · Reddit · Meta Ad Library · Vinted

Confirmed eBay sale prices — what buyers actually paid, not what sellers asked.

eBay started requiring a signed-in account for sold and completed search results on 23 July 2026. This actor does not need one. For any seller it returns their real completed orders — price paid, item, item number, buyer rating and comment — and you can filter that feed by keyword to get comparable sales for one product.

Measured 18 August 2026: 40,000+ transactions reachable per seller. Price coverage depends on age - recent orders carry a price and an item link on essentially every row, older ones lose the item link first and then the price. The measured gradient across five sellers is in the table below; use the Sales period filter to stay in the high-coverage window.

Unofficial and not affiliated with, endorsed by, or sponsored by eBay Inc. Public, unauthenticated pages only.


What you get

ModeGive itGet back
Searchkeywords, or any eBay search URLevery matching active listing, up to Max results
Itemitem numbers or /itm/ URLseverything on the item page
Sellera usernamethat seller's whole active inventory
Salesa username (+ optional keyword)confirmed orders with the price paid

Most runs also get a price summary row — min, 25th percentile, median, 75th, 90th, max, mean, standard deviation, total value and graded share. It is billed once per query, not per result, and it is skipped entirely when a query returned fewer than 10 priced rows. Item mode does not produce one.

Every run that gets past input validation also gets a free diagnostics row telling you how many pages were fetched, how many were blocked, and exactly why the run stopped.

Listing rows

itemId · url · title · price · priceMax · currency · priceIsRange · condition · listingFormat · bestOffer · bids · timeLeft · shippingCost · freeShipping · shippingIsEstimate · itemLocation · soldCount · watchers · imageUrl · isGraded · gradingCompany · grade · queryTokenOverlap · titleMatchesQuery · sellerName · sellerFeedbackScore · sellerPositivePct

Two notes on those last three. eBay does not put the seller on a search result card, so sellerName is filled in Seller mode (you named them) and on Item rows, and is null on a plain keyword search rather than guessed. And every field on a row comes from that row's own listing — nothing is ever borrowed from a neighbouring card, which is the quiet way a comps table gets corrupted.

titleMatchesQuery is the other rare one. Thin result pages come back mixed with loosely related listings that no filter catches, and they are the likeliest reason a median comes out wrong. Each row scores how much of your query its title actually contains, so you can drop the padding instead of averaging it into your numbers.

watchers is the rarest field here. Reviewing the published output fields of the twenty largest eBay actors on the Apify Store in August 2026, we found none returning watch counts. It is the cheapest demand signal eBay publishes, and it costs you nothing extra to receive.

Item rows

itemId · url · title · price · currency · condition · conditionCode · soldCount · watchers · shippingCost · sellerName · sellerFeedbackScore · sellerPositivePct · isGraded · gradingCompany · grade — plus availability, quantityAvailable, shippingCurrency, returnsAccepted, returnDays, brand, mpn, model, color, images[], productRating, productRatingCount, itemSpecifics{} and categoryPath[].

An item row is not a superset of a listing row: auction-only fields (bids, timeLeft, bestOffer, listingFormat) and the search-relevance fields live on listing rows only.

Sale rows

feedbackId · seller · price · currency · itemId · itemUrl · itemTitle · rating · comment · buyer · buyerFeedbackScore · verifiedPurchase · automatedFeedback · periodBucket · recencyRank · isGraded · gradingCompany · grade


Honest limits, stated up front

Every actor in this category has these. Most do not tell you.

LimitThe truth
Sold-listing keyword searchNot offered. eBay requires a login for it — see above.
Sale dateseBay publishes only a coarse recency band, never a date, so there is no soldDate field — there is no honest way to fill one. recencyRank gives you exact ordering instead.
Sale price coverageVaries by seller, and recent orders fare best. Measured across five sellers: 100%, 100%, 99%, 100% and 11%. Older feedback tends to keep the price but lose the link to the item. Use the Sales period filter to stay in the high-coverage window, and check rowsFilteredOut and the price fill in your own output rather than assuming.
Search depthAny one query can only be paged so deep. This actor detects the end of the real results, never bills you twice for the same listing, and says so in the diagnostics row. Split a broad query by price band or category to reach more.
Seller inventory depthFar deeper — 25,000+ items reachable for a large seller.
Watchers & sold countseBay prints these on some cards only, and which one it prints varies by surface. Measured on our own captured pages: watchers on 8-32% of US search cards, 0% of seller-inventory cards and 0% on DE/FR/IT; sold counts are the reverse, 98% on seller inventory. Always null, never guessed — so filter on them, do not assume them.
Seller on search rowseBay does not print the seller on a search card. Use Seller mode, or Item mode, where it is filled.
BlockseBay challenges some requests. When it does and will not relent, you get a free error row naming the input that failed. This actor never reports success on a page it could not read, and never bills you for one.
Filters run after the fetchexcludeKeywords and gradeFilter apply to rows eBay already returned. The diagnostics row reports how many were removed, and if they removed everything you get a free ALL_FILTERED row rather than an empty dataset.

Pricing

You are charged forFREEBRONZESILVERGOLD+
Each listing row$4.00 / 1,000$3.50 / 1,000$3.00 / 1,000$2.50 / 1,000
Each full item-detail row$9.00 / 1,000$8.00 / 1,000$7.00 / 1,000$6.00 / 1,000
Each confirmed sale row$6.00 / 1,000$5.30 / 1,000$4.60 / 1,000$4.00 / 1,000
Each price-summary row$0.05$0.044$0.039$0.035

The price summary is one row per query, not per result, and it is skipped entirely on a query that returned fewer than 10 priced rows — a percentile spread over a handful of listings is noise, and you should not pay for noise. Turn it off with includeMarketSummary: false.

Never charged: pressing Run, error rows, diagnostics rows, duplicate rows, or a run that returns nothing.

Item-detail costs more than a listing row because an item page is roughly 25 times the data. If you only need price, condition, shipping and seller, stay in Search mode — it is the cheap one, and it is what the headline price refers to.

Set maxCostUsd and the run stops before it can exceed it, priced at the highest tier so the ceiling holds whatever plan you are on. Reviewing the input schemas of the twenty largest eBay actors in August 2026, we found none exposing a spend ceiling as a run input.


How to scrape eBay sold prices for a seller

Set What to scrape to Sales, put the seller's username in Sellers, and optionally set Filter sales by keyword.

{
"mode": "sales",
"sellers": ["musicmagpie"],
"salesKeyword": "vinyl",
"salesPeriod": "SIX_MONTHS",
"maxItems": 1000
}

Every row is a completed order with the amount paid. Filtering by keyword is how you get comparable sales for one product: pick the sellers who move that product, then read what their buyers actually paid.

How to scrape eBay listings by keyword

{
"mode": "search",
"searchQueries": ["nintendo switch oled", "steam deck oled"],
"condition": "used",
"minPrice": 100,
"maxPrice": 400,
"sortBy": "price_lowest",
"maxItems": 500
}

There is no limit on how many keywords you can pass — the largest actor in this category caps its keyword list at six. Note that maxItems is a ceiling for the whole run, shared across every keyword, so raise it when you pass many. Keywords the run never reached get a free NOT_REACHED row naming them, rather than vanishing.

How to scrape an eBay seller's entire inventory

{ "mode": "seller", "sellers": ["musicmagpie"], "maxItems": 5000 }

You can add a keyword to search inside that seller's inventory, and any of the price, condition or format filters.

Search and item detail are two calls on purpose, so you are never billed for detail pages you did not ask for. Run the search, keep the itemId of the rows you actually care about, then feed those into Item mode:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const search = await client.actor('vonsensey/ebay-scraper-api').call({
mode: 'search', searchQueries: ['nintendo switch oled'], minPrice: 150, maxItems: 200,
});
const { items } = await client.dataset(search.defaultDatasetId).listItems();
const shortlist = items
.filter((i) => i.type === 'listing' && (i.watchers ?? 0) > 20)
.map((i) => i.itemId);
if (shortlist.length > 0) {
await client.actor('vonsensey/ebay-scraper-api').call({
mode: 'item', itemIds: shortlist, maxItems: shortlist.length,
});
}

Filtering between the two calls is the whole point: an item page costs about 25 times a listing row to fetch, so the cheap pass finds the candidates and the expensive pass runs only on those.

How to scrape eBay item details in bulk

{
"mode": "item",
"itemIds": ["326350933758", "https://www.ebay.com/itm/298593858549"],
"maxItems": 200
}

Item numbers and full URLs both work, mixed in the same list.

How to scrape eBay for trading card comps (PSA, BGS, CGC, SGC)

Card titles are parsed for a third-party grade, so you can separate slabs from raw cards without keyword guesswork:

{
"mode": "search",
"searchQueries": ["charizard base set holo"],
"gradeFilter": "graded",
"excludeKeywords": ["proxy", "custom", "reprint", "lot"],
"maxItems": 1000
}

gradeFilter accepts graded, raw or any. Every row carries isGraded, gradingCompany and grade as separate fields, so you can group by grade in a spreadsheet. Graders recognised: PSA, BGS/Beckett, CGC, SGC, PCGS and WATA on the name alone; TAG, ACE, HGA, CSG, MNT, AGS, GMA, KSA, VGA and NGC only when a grade number sits beside them — because "TAG Heuer" and "ACE Hardware" are not graded slabs, and treating them as such would drop whole categories out of a raw filter.

Set gradeFilter to raw to get ungraded copies only — the comparison most card sellers actually want, and the one a keyword blacklist gets wrong because most raw listings never contain the word "raw".

How to scrape eBay Germany, Italy, France and 16 other marketplaces

Pick the site in eBay marketplace, or just paste a URL from that site — the marketplace is read from the URL and wins over the dropdown. Nineteen sites are supported; four are measured end-to-end, and the table below is exactly what we measured.

ebay.com · ebay.co.uk · ebay.de · ebay.fr · ebay.it · ebay.es · ebay.ca · ebay.com.au · ebay.at · ebay.ch · ebay.ie · ebay.nl · ebay.be · ebay.pl · ebay.com.sg · ebay.com.my · ebay.ph · ebay.com.hk · ebay.in

Each site is served from its own country, so a German search returns EUR and an Italian search returns EUR quoted the Italian way. Every run also reports the currency it actually saw against the one that site should quote, so a converted price can never pass silently as a native one — which is the difference between a comps table you can trust and one that is quietly wrong.

eBay also writes the condition, the shipping line and the buying format in each site's own language. So every row carries both eBay's own wording in condition and a conditionCode that means the same thing on every site we measured — NEW, NEW_OTHER, OPEN_BOX, REFURBISHED, USED, FOR_PARTS — which is what makes a mixed-market dataset filterable in one pass.

Measured on live pages from four marketplaces, 18 August 2026:

Fieldebay.comebay.deebay.frebay.it
title, price, currency100%100%100%100%
condition + conditionCode100%100%100%100%
shipping cost or free-shipping flag100%63%100%100%
buying format44%48%40%60%
item location100%2%0%0%

Item location is near-zero outside the US because eBay only prints it for cross-border listings — a domestic German listing on ebay.de simply does not carry one. It is null there, not guessed.

How to monitor eBay prices on a schedule

Add a Schedule in the Apify Console and point it at a saved input. There is no start fee, so a run that finds five new listings costs you the price of five rows and nothing else.

Use maxCostUsd to put a hard cap on any single scheduled run.

How to call the eBay Scraper API from Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("vonsensey/ebay-scraper-api").call(run_input={
"mode": "sales",
"sellers": ["musicmagpie"],
"salesKeyword": "vinyl",
"maxItems": 500,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
if row["type"] == "sale":
print(row["price"], row["currency"], row["itemTitle"])

How to call it from Node.js

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('vonsensey/ebay-scraper-api').call({
mode: 'search',
searchQueries: ['pokemon card psa 10'],
gradeFilter: 'graded',
maxItems: 500,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.table(items.filter((i) => i.type === 'listing'));

How to export eBay data to CSV, JSON or Excel

Every run's dataset exports as JSON, CSV, Excel, XML or JSONL from the Console, or from the API with ?format=csv. The dataset ships with five saved views — All results, Listings, Confirmed sales, Price summary and Run diagnostics — so a mixed run opens as a clean table instead of a wall of nulls.


Why not eBay's own API?

Checked against eBay's own developer documentation in August 2026.

RouteStatus
Finding APIDecommissioned 4 February 2025.
Shopping APIDecommissioned 4 February 2025.
Browse APIActive listings only. No sold data. Needs an approved developer account.
Marketplace Insights APIThe only official sold-price API. Limited Release — closed to new applicants, and capped at 90 days.
TerapeakFree with a seller account, but aggregated only. It never shows you the individual transactions, and there is no API.

No key, no application, no approval queue. Run it and get rows.


Reading the output

The dataset mixes row types; every row carries a type field.

typeMeaningCharged
listingAn active listing from a search or a seller's inventoryyes
itemFull detail from an item pageyes
saleA confirmed order with the price paidyes
summaryPrice distribution over one queryonce per query
errorAn input that produced nothing, and whyfree
diagnosticsOne per run: pages, blocks, and why it stoppedfree

Error rows carry a stable machine-readable reason, so you can branch on it: INVALID_INPUT, INVALID_URL, NO_RESULTS, NO_SALES, ITEM_NOT_FOUND, ALL_FILTERED, NOT_REACHED, FETCH_FAILED, SOLD_SEARCH_LOGIN_GATED, BLOCKED_CHALLENGE, BLOCKED_REFUSED, BLOCKED_LOGIN_REQUIRED, RUN_FAILED.

Null means "eBay did not publish it here", never "we guessed". A price range gives you price, priceMax and priceIsRange: true rather than a single made-up number. A card listing with no grade gives you grade: null, not 0.


Public, unauthenticated pages only. No logins, no accounts, and no personal data beyond the masked buyer handles eBay publishes publicly. You are responsible for how you use the data, including under GDPR and eBay's terms.

Unofficial. Not affiliated with, endorsed by, or sponsored by eBay Inc.


Support

Open an issue on the Actor's Issues tab. Include the run ID — the diagnostics row in that run's dataset already contains most of what is needed to answer you.

Run it without configuring anythingGet confirmed eBay sold prices, a ready-made example you can start as-is or copy.

Use cases

  • Price something before you list it. Confirmed sale prices, not asking prices, are what tell you the number a buyer actually paid.
  • Track a price over time. Run it on a schedule and the dataset becomes a price history you own.
  • Watch a seller. Pull a competitor's whole live inventory and what has been selling out of it.
  • Value graded cards. Comps split by PSA, BGS, CGC and raw, so a valuation has evidence behind it.

Run it on a schedule

A one-off pull answers a question; a schedule answers it every day without you. Open Schedules in the Apify Console, point a cron at this Actor, and the dataset keeps filling on its own — no server, no cron box, no babysitting. Everything here is built to be re-run: you are billed per result delivered, so a scheduled run that finds nothing new costs nothing.

FAQ

Can I get sold prices, not just active listings?

Yes. Confirmed sale prices with their dates come back alongside the live listings, in the same run.

Which eBay marketplaces are covered?

19 country marketplaces, chosen per run.

Can I see how many people are watching a listing?

Yes — watcher and sold counts are fields on the row, which is the closest public proxy for real demand.

Do failed or blocked lookups cost me anything?

No. Anything that could not be read comes back as a free diagnostic row rather than silently vanishing.


Something wrong, or a field you need that is missing? Open an issue on the Issues tab — it is read and it gets fixed. If this saved you time, a rating on the Store page helps the next person find it.