# eBay Sold Listings Scraper (`dami_studio/ebay-sold-listings-scraper`) Actor

Search eBay sold and completed listings: sale price, currency, shipping, sold date, condition, buying format, bids and location as rows. Nine marketplaces, no eBay API key, no browser. $0.70 per 1,000 - the lowest per-listing price of any eBay scraper on Apify with an active user base.

- **URL**: https://apify.com/dami\_studio/ebay-sold-listings-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** E-commerce, Lead generation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 sold listing returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## eBay Sold Listings Scraper — sold comps API, no eBay key

Search eBay's **sold and completed** listings and get them back as rows: item id, title, sale price,
currency, shipping cost, sold date, condition, buying format, seller location and image.

This is what you use to price something. What did this actually sell for, how many times, at what
spread, in the last N days.

No eBay developer account, no OAuth, no browser.

### Output

| Field | Notes |
|---|---|
| `itemId`, `title`, `listingUrl` | the listing |
| `salePrice`, `currency` | the price it sold for, parsed to a number, plus the marketplace currency |
| `shippingCost`, `shippingText` | shipping parsed to a number; `Free delivery` becomes `0`. `shippingText` keeps eBay's raw string |
| `soldDate`, `soldDateText` | ISO timestamp and eBay's own label ("Aug 9, 2026" / "10 Aug 2026") |
| `condition` | "Pre-Owned", "Brand New", "Open Box", "Parts Only", "Very Good - Refurbished" … |
| `buyingFormat` | "Buy It Now", "or Best Offer", "Best offer accepted", "4 bids" |
| `bids` | number of bids, when the listing was an auction |
| `itemLocation` | where the seller shipped from |
| `imageUrl` | the gallery thumbnail URL (the image is not downloaded) |
| `page`, `source`, `scrapedAt` | provenance |

#### Measured field coverage

Measured on a real 300-row platform run on 2026-08-10: query `nintendo switch`, 2 pages,
`ebay.com`. 300/300 rows real, 0 diagnostics, 54 s.

| Field | Filled |
|---|---|
| `itemId`, `title`, `listingUrl`, `salePrice`, `currency`, `soldDate`, `soldDateText`, `condition`, `buyingFormat`, `itemLocation`, `imageUrl` | 100% |
| `shippingCost`, `shippingText` | 99.7% |
| `bids` | 19.0% |

`bids` is only present on auction listings — 19% of that sample. No column was empty on every row.
Median sale price in the sample: $126.75.

### Input

- `query` — keywords. Leave it empty and you get one labelled, uncharged sample row.
- `site` — `ebay.com`, `.ca`, `.co.uk`, `.de`, `.com.au`, `.fr`, `.it`, `.es`, `.ie`.
- `categoryId` — eBay category ID (e.g. `625` for cameras). Verified to change results.
- `condition` — `new`, `open box`, `refurbished`, `used`, `parts`. eBay applies it server-side.
- `minPrice` / `maxPrice` — applied by eBay and re-checked on the parsed price.
- `soldFrom` / `soldTo` — date window, applied after parsing.
- `currency` — keep only listings in one currency.
- `maxItems` (default 120), `maxPages` (default 3, ~200 listings per page),
  `maxSessions` (default 8), `requestTimeoutSecs`.
- `proxyConfiguration` — optional; leave it empty for the tuned default.

Every filter above was run live before being documented. Rows a filter drops are neither returned
nor charged.

### How it gets past eBay's block, and what it cost to find out

Two independent things were wrong with the previous build, and both are measured, not guessed.

**1. The fingerprint.** Through Apify datacenter exits, `impit`'s Chrome profile 403s on every
session and Firefox does too. `ios18` is the profile eBay accepts. `firefox144` is the worst of both
worlds — HTTP 200 with a 14 KB interstitial and zero listings.

**2. The cookie warm-up.** Even on `ios18`, hitting the search URL cold gave **2 clean pages out of
12**. Fetching `https://www.ebay.com/` first, keeping its ~14 cookies, then requesting the search
page with a same-origin referer gave **6 out of 6**, and the same warmed session then paginated three
deep without a single refusal. `impit` has no cookie jar by default, so this is not optional.

A session that starts refusing is thrown away and a new one warmed on a new address — a burned eBay
session stays burned. Interstitials arrive as **HTTP 200** with a small body, so the actor judges the
body content, never the status code.

The old parser also looked for `.s-item__title`. eBay now emits `.s-card__title`, so even an
unblocked page would have parsed to zero rows. Both are handled.

### Billing

**$0.70 per 1,000 sold listings** ($0.0007 each), plus $0.001 each time a run starts — the lowest
per-listing price of any eBay scraper on the Apify Store with an active user base; the next
cheapest, `automation-lab/ebay-scraper` (147 monthly users), is $0.84 per 1,000 even on its cheapest
volume tier.

Pay per event, event name `listing`. One event per unique real listing. Duplicates across pages are
charged once. Never charged: the empty-input sample, filtered-out rows, `EBAY_BLOCKED`,
`NO_LISTINGS` and `NO_MATCHES` diagnostics.

Verified on 2026-08-10 against the Apify Store API. Among sold-listing scrapers specifically,
`caffein.dev/ebay-sold-listings` (455 monthly users) is $2.50 per 1,000 on its cheapest tier and
`sync-network/ebay-sold-listings-scraper` is $2.00. This actor is below all of them. Every rival
figure quoted here is that actor's **lowest** volume tier, not the headline price a free account
sees.

### What this does not do

- No seller username or feedback score. eBay removed those from the search-result card; this actor
  will not fabricate them. Use a seller-profile scraper if you need them.
- No per-item detail pages, so no item specifics, description HTML, or full photo set.
- No live/active listings — this is the sold and completed view only.
- No eBay API, no OAuth, no application key.
- No residential proxy.
- eBay caps a search at roughly 200 listings per page; very deep pagination eventually stops
  returning new items and the actor writes an uncharged `NO_LISTINGS` row rather than looping.

### FAQ

**How do I find what something sold for on eBay?**
Put the product name in `query`, set `soldFrom` to the start of the window you care about, and read
`salePrice` and `soldDate` from each row.

**How many sold listings per run?**
About 200 per page. `maxPages` up to 20, `maxItems` up to 2,000. A 300-row run took 54 seconds.

**Do I need an eBay API key?**
No. Nothing to register, nothing to authorise.

**Can I search a non-US eBay site?**
Yes — set `site`. Prices come back in that marketplace's currency and `soldDateText` in its date
format; `soldDate` is normalised to ISO either way. Note that non-US sites publish fewer attributes,
so `condition` is much sparser there than the 100% measured on `ebay.com`.

**Does it include shipping in the price?**
No. `salePrice` is the item price and `shippingCost` is separate, so you can add them yourself or
compare item-only prices.

**What happens if eBay blocks it?**
Each page gets up to `maxSessions` freshly warmed sessions on new addresses. If all of them are
refused you get one uncharged `EBAY_BLOCKED` row saying so, and zero listing charges.

**Is the sample row charged?**
No. Empty input returns one `_sample: true` row and zero billable events.

# Actor input Schema

## `query` (type: `string`):

Keywords to search sold and completed listings for, for example vintage camera. Leave empty to get one labeled, uncharged sample row.

## `site` (type: `string`):

Which eBay site to search. Prices and sold dates come back in that marketplace's currency and date format.

## `categoryId` (type: `string`):

Optional eBay category ID to restrict the search, for example 625 for cameras and photo.

## `condition` (type: `string`):

Optional condition filter, applied by eBay itself through LH\_ItemCondition.

## `minPrice` (type: `number`):

Applied by eBay through \_udlo and re-checked on the parsed price.

## `maxPrice` (type: `number`):

Applied by eBay through \_udhi and re-checked on the parsed price.

## `soldFrom` (type: `string`):

Keep only listings whose sold date is on or after this date. Applied after parsing.

## `soldTo` (type: `string`):

Keep only listings whose sold date is on or before this date. Applied after parsing.

## `currency` (type: `string`):

Keep only listings priced in this currency, for example USD or GBP. Useful on marketplaces that mix currencies.

## `maxItems` (type: `integer`):

Maximum unique sold listings to return and charge for.

## `maxPages` (type: `integer`):

Result pages to walk. Each page carries about 200 sold listings.

## `maxSessions` (type: `integer`):

eBay burns sessions. Each attempt warms a brand new session on a new proxy address before requesting the page.

## `requestTimeoutSecs` (type: `integer`):

Maximum duration of each request.

## `proxyConfiguration` (type: `object`):

Optional. Leave empty to use Apify datacenter proxy with a fresh session per warm-up, which is what this actor is tuned for. Supply proxyUrls to use your own addresses. Residential proxy is never used.

## Actor input object example

```json
{
  "query": "nintendo switch",
  "site": "ebay.com",
  "maxItems": 120,
  "maxPages": 3,
  "maxSessions": 8,
  "requestTimeoutSecs": 30
}
```

# Actor output Schema

## `results` (type: `string`):

Results in the default dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "query": "nintendo switch"
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/ebay-sold-listings-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "query": "nintendo switch" }

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/ebay-sold-listings-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "query": "nintendo switch"
}' |
apify call dami_studio/ebay-sold-listings-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/ebay-sold-listings-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/OlPRAT4Is6wd20rIM/builds/vERde7Z5kRpGie4V1/openapi.json
