# eBay sold listings API reference

> GET /v1/ebay/sold searches eBay sold listings by keyword in the US, UK, Canada, Australia, Germany, France, Italy or Spain, with price and condition filters.

```text
GET https://api.soldgraph.com/v1/ebay/sold
```

Search eBay sold listings by keyword on eBay US or seven other countries' sites. Each call returns one page of results, not a full sales history. For a one-page overview of what you get, see the [eBay sold listings API](/ebay-sold-listings-api) page.

Moving off eBay's retired `findCompletedItems`? See [findCompletedItems replacement](https://soldgraph.com/docs/compare/ebay-finding-api.md).

No key yet? [Create a free key](https://soldgraph.com/signup?utm_source=docs-md&utm_medium=ai&utm_content=api%2Fsold). You start with 100 free requests, no credit card required.

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `q` | string | Required | Search keywords, 1–200 characters, as you'd type them into the marketplace's search box. Be specific: model, size, grade, edition. |
| `page` | integer, 1–100 | `1` | Source page to fetch. Where an endpoint has `cursor`, pages after 1 also need it. |
| `min_price` | integer, 0–1000000 |  | Lowest price, in whole units of the country's currency (US dollars for `us`). Optional. Must not be more than `max_price`. You can send it without `max_price`. |
| `max_price` | integer, 0–1000000 |  | Highest price, in whole units of the country's currency (US dollars for `us`). Optional. You can send it without `min_price`. |
| `marketplace` | `ebay` | `ebay` | Optional. If sent, it must be `ebay`. |
| `country` | `us`, `uk`, `ca`, `au`, `de`, `fr`, `it` or `es` | `us` | Which eBay site to search: `us` (ebay.com), `uk` (ebay.co.uk), `ca` (ebay.ca), `au` (ebay.com.au), `de` (ebay.de), `fr` (ebay.fr), `it` (ebay.it) or `es` (ebay.es). Results, prices, `min_price` and `max_price` use that site's currency. Optional. |
| `condition` | One or more of `new`, `open_box`, `refurbished`, `used`, `for_parts`, comma-separated |  | eBay's own condition groups. Send one or more, separated by commas, like `new,open_box`. `new` is Brand New, `open_box` is Open Box or New (Other), `refurbished` is every refurbished grade, `used` is Pre-Owned and `for_parts` is Parts Only. eBay applies the filter. |
| `sort` | `best_match`, `recently_sold`, `price_asc` or `price_desc` |  | Result order. Leave it out for eBay's best match. `recently_sold` is newest sale first. `price_asc` and `price_desc` sort by price plus shipping. |
| `item_location` | `domestic` or `worldwide` |  | Where sellers are. `domestic` keeps sellers in the country you search; `worldwide` includes every country. Leave it out for the eBay site's own default. On `fr` the default is narrow: send `worldwide` for far more results. |
| `buying_format` | `auction`, `buy_it_now` or `best_offer` |  | Only one kind of listing: `auction`, `buy_it_now` (fixed price, including listings that also take offers) or `best_offer` (listings that take offers). eBay applies the filter. |
| `count` | `40`, `60`, `120` or `200` |  | Rows per page: `200`, `120`, `60` or `40`. Send `200` for the most eBay gives; leaving it out means 40. A search costs one request at any size. Optional. |
| `category_id` | integer, 1–2147483647 |  | eBay numeric category ID, such as `27386` for graphics cards. Keeps only listings in that category. Optional. |

```bash title="cURL"
curl -G https://api.soldgraph.com/v1/ebay/sold \
  -H "Authorization: Bearer $SOLDGRAPH_KEY" \
  --data-urlencode "q=sony wh-1000xm5" \
  -d page=1 \
  -d count=200
```

```typescript title="TypeScript"
const params = new URLSearchParams({
  q: "sony wh-1000xm5",
  page: "1",
  count: "200",
});

const res = await fetch(`https://api.soldgraph.com/v1/ebay/sold?${params}`, {
  headers: { Authorization: `Bearer ${process.env.SOLDGRAPH_KEY}` },
});
const data = await res.json();
```

```python title="Python"
import os, requests

res = requests.get(
    "https://api.soldgraph.com/v1/ebay/sold",
    params={"q": "sony wh-1000xm5", "page": 1, "count": 200},
    headers={"Authorization": f"Bearer {os.environ['SOLDGRAPH_KEY']}"},
)
data = res.json()
```

The first call usually returns **202** and a job to poll. [Search jobs](https://soldgraph.com/docs/api/overview.md#search-jobs) covers polling, retries and pagination.

## Filters

- `condition` takes one or more of eBay's condition groups, separated by commas, like `new,open_box`. The groups are `new` (Brand New), `open_box` (Open Box or New (Other)), `refurbished` (every refurbished grade), `used` (Pre-Owned) and `for_parts` (Parts Only).
- `min_price` and `max_price` are whole units of the site's currency, like dollars on `us` or pounds on `uk`. You can send either one alone.
- `sort` orders results: `recently_sold` (newest sale first), `price_asc` or `price_desc`. Price sorts include shipping. Leave it out for eBay's best match.
- `item_location` picks where sellers are: `domestic` for sellers in the country you search, or `worldwide`. Leave it out for the eBay site's own default.
- `buying_format` keeps one kind of listing: `auction`, `buy_it_now` (fixed price, including listings that also take offers) or `best_offer` (listings that take offers).
- `category_id` keeps one eBay category, by its numeric ID. For example, `ryzen` matched 58,661 sold listings, and 47 with `category_id=27386` (graphics cards). The ID is the number after `/b/.../` or in `_sacat=` on an eBay category page.
- To leave a word out, put a minus sign in front of it in `q`, like `q=RX 580 -8GB`. eBay does the excluding, so the count drops too: that search went from 2,158 results to 765.

eBay applies the filters, so `reported_total` counts only matching listings.

## Countries

`country` picks the eBay site. Leave it out for eBay US.

| `country` | eBay site | Currency |
| --- | --- | --- |
| `us` | ebay.com | USD |
| `uk` | ebay.co.uk | GBP |
| `ca` | ebay.ca | CAD |
| `au` | ebay.com.au | AUD |
| `de` | ebay.de | EUR |
| `fr` | ebay.fr | EUR |
| `it` | ebay.it | EUR |
| `es` | ebay.es | EUR |

- Prices, shipping, `min_price` and `max_price` are in that site's currency.
- `link` points to the listing on that site.
- Results are what that eBay site shows its own buyers. They can include listings from sellers in other countries. Send `item_location=domestic` to keep only local sellers.
- eBay France's default seller locations are narrow. For example, `iphone 13` sold returned 314 results by default and 3,870 with `item_location=worldwide`.
- Text fields are as the site shows them. `condition` and `shipping_text` can be in the local language, like `Gebraucht` or `+ €10.49 de envío`. The `condition` filter works the same way on every site.
- Each country is its own search and costs one request.

## Pages

Start with `page=1`. For the next page, send the same search with `page` set to `next_page` from the last result. eBay pages by number, so there's no cursor.

- When `next_page` is null, you're on the last page. You can request at most 100 pages. A page past the last one comes back complete with no rows and `next_page: null`, and isn't charged.
- A page has 40 rows unless you send `count`. `count` can be `40`, `60`, `120` or `200`. A bigger page is still one search and costs one request.
- Keep `count` the same while you page. Page 2 at `count=120` starts at row 121.
- `page_size` is the page size eBay reports.

## Price summary

**Pricing recipe.** For comps, send `sort=recently_sold`, `count=120` and `item_location=domestic`, use `summary.p25` to `summary.p75` as the working range, and read `summary.median_excluding_best_offer` when `best_offer_accepted_count` is high. Put a minus sign before words to drop accessories, like `q=sony wh-1000xm5 -case -pads`. A search with no results still costs one request.

`summary` is plain maths over the rows on the page you got: how many show one price, the median, the lowest and the highest, plus the oldest and newest sold dates.

- Nothing is removed from `data`. The summary counts rows; it never filters them.
- Rows that show a price range, or no readable price, are left out of the maths. `priced_count` says how many rows were used.
- `best_offer_accepted_count` says how many rows sold by accepted Best Offer. eBay shows only the asking price on those, so `median_excluding_best_offer` leaves them out and gives the median of real sale prices.
- It covers one page. Send a bigger `count` to summarize more rows in one search.

## Example response

```json title="Response"
{
  "request_id": "00000000-0000-4000-8000-000000000001",
  "status": "complete",
  "credits": 1,
  "cached": false,
  "result": {
    "provider": "ebay",
    "country": "us",
    "query": "sony wh-1000xm5",
    "page": 1,
    "page_size": 200,
    "count": 1,
    "reported_total": 1,
    "next_page": null,
    "next_cursor": null,
    "collected_at": "2026-09-23T14:02:11Z",
    "schema_version": 2,
    "completeness": "provider_page_only",
    "summary": {
      "priced_count": 1,
      "currency": "USD",
      "median": 189,
      "low": 189,
      "high": 189,
      "p25": 189,
      "p75": 189,
      "earliest_sold_date": "2026-09-21",
      "latest_sold_date": "2026-09-21",
      "best_offer_accepted_count": 0,
      "median_excluding_best_offer": 189
    },
    "data": [
      {
        "id": "123456789012",
        "title": "Example eBay listing",
        "link": "https://www.ebay.com/itm/123456789012",
        "format": "fixed_price",
        "displayed_price": {
          "amount": 189,
          "currency": "USD"
        },
        "displayed_price_text": "$189.00",
        "displayed_price_range": null,
        "best_offer_accepted": false,
        "displayed_shipping": {
          "amount": 9.99,
          "currency": "USD"
        },
        "shipping_text": "+$9.99 delivery",
        "sold_date": "2026-09-21",
        "sold_date_text": "Sep-21 14:35",
        "condition": "Pre-Owned",
        "bid_count": null,
        "seller_text": "sonyfan_outlet (2,418) 99.6%",
        "location_text": "Located in United States",
        "image": "https://i.ebayimg.com/images/g/example/s-l500.jpg"
      }
    ],
    "field_notes": "Fields that could not be read are null. Displayed prices are not verified accepted-offer prices."
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `request_id` | string | ID of this request. Poll it at `/v1/jobs/{request_id}`. |
| `status` | string | `pending`, `complete` or `failed`. Always check it, even on HTTP 200. |
| `credits` | integer | Requests charged: 1 when complete, 0 for a page past the last one, while pending, or when failed. |
| `cached` | boolean | True when the result came from the 15-minute cache. |
| `poll_url` | string | Only while pending. A path like `/v1/jobs/{id}`: join it to `https://api.soldgraph.com`, not to the `/v1` base URL. |
| `error.code` | string | Only when failed. See [failed job codes](https://soldgraph.com/docs/api/errors.md#failed-job-codes). |
| `error.message` | string | Only when failed. What the code means, in plain words. |
| `result` | object | Only when complete. One source page from the marketplace. |
| `result.provider` | string | Marketplace ID, like `ebay` or `poshmark`. |
| `result.country` | string | The country searched: `us`, or on eBay the country you sent. |
| `result.query` | string | The `q` you sent, with extra spaces removed. |
| `result.page` | integer | The page you asked for. |
| `result.count` | integer | Rows in `result.data` on this page. |
| `result.reported_total` | integer | Total matches the marketplace reports for the search. It may be rounded or capped. |
| `result.next_page` | integer \| null | Send this as `page` to get the next page. Null on the last page, and on page 100, the deepest page served. |
| `result.collected_at` | string | When we collected the page, as an ISO 8601 timestamp. |
| `result.schema_version` | integer | Version of the result's shape for that marketplace: `1` or `2`. It only rises when a field changes meaning. |
| `result.completeness` | string | `provider_page_only`: one page as the marketplace served it, not a full history. TCGplayer recent sales say `recent_sample_only`. |
| `result.field_notes` | string | Plain-text notes on how to read this page's fields. |
| `result.next_cursor` | null | Always null. eBay pages by number: send `page=next_page` for the next page. |
| `result.page_size` | integer | Rows per page, as eBay reports it. |
| `result.summary` | object | Price maths over the rows on this page: `priced_count`, `currency`, `median`, `low`, `high`, `p25`, `p75`, `earliest_sold_date`, `latest_sold_date` and `best_offer_accepted_count`. No row is removed from `data`. Rows with a price range or an unread price are left out of the maths. |
| `result.data[].id` | string | The marketplace's listing ID. |
| `result.data[].title` | string \| null | Listing title as shown. |
| `result.data[].link` | string | URL of the listing on the eBay site you searched, like `https://www.ebay.co.uk/itm/…`. |
| `result.data[].format` | string | `auction` when the sale had bids, otherwise `fixed_price`. |
| `result.data[].displayed_price` | object \| null | `{ amount, currency }` parsed from the price shown. Null if it couldn't be read. Null when eBay shows a price range for a multi-variation listing ("$247.00 to $911.00"): the amount wouldn't say which variant. The range is in `displayed_price_range` and `displayed_price_text`. When `best_offer_accepted` is true, this is the asking price, not the sale price. |
| `result.data[].displayed_price_text` | string \| null | The price text as shown, like `$189.00`, `£124.46` or `€230.00`. |
| `result.data[].displayed_price_range` | object \| null | `{ low, high, currency }` when eBay shows a price range for a multi-variation listing ("$247.00 to $911.00"). Null otherwise. `displayed_price` is null for these rows, so they stay out of the summary maths. |
| `result.data[].best_offer_accepted` | boolean | True when the sale closed on an accepted Best Offer. eBay does not publish the accepted amount on sold listings, so on these rows `displayed_price` is the seller's asking price. `summary.median_excluding_best_offer` gives the median of the rows whose price is the sale price. |
| `result.data[].displayed_shipping` | object \| null | `{ amount, currency }` read from exact shipping text, in the site's currency, like `+$9.99 delivery`, `+£3.92 delivery` or `+€6.99 Lieferung`. Free shipping is 0, in every country's wording. Estimates and `Shipping not specified` are null. |
| `result.data[].shipping_text` | string \| null | Shipping text as shown, like `+$9.99 delivery`, `Free delivery` or `+ €10.49 de envío`. |
| `result.data[].sold_date` | string \| null | Sale date as `YYYY-MM-DD`. eBay's text has no year, so we use the most recent date that matches. Null if the text couldn't be read. |
| `result.data[].sold_date_text` | string \| null | Sold date text as shown. eBay leaves out the year. The order depends on the site: `Sep-21 14:35` (US, Australia, France, Italy, Spain), `21-Sep 14:35` (UK, Canada) or `21. Sep. 14:35` (Germany). |
| `result.data[].condition` | string \| null | eBay's condition text as shown, in the site's language, like `Pre-Owned`, `Pre-owned` or `Gebraucht`. To filter by condition in any language, use `condition`. |
| `result.data[].bid_count` | integer \| null | Number of bids, for auctions. Null for fixed-price sales. |
| `result.data[].seller_text` | string \| null | Seller text as eBay shows it: username, feedback count and positive percentage, e.g. `clutch_supply (1,325) 99.7%`. |
| `result.data[].location_text` | string \| null | Seller location text as eBay shows it, like `Located in United States` or `da Germania`. Often null for sellers in the country you searched. |
| `result.data[].image` | string \| null | Image URL on `i.ebayimg.com`. |

`displayed_price` is the price eBay shows. It can differ from what the buyer paid. Fields we can't read on a given row are null.

- `best_offer_accepted` is true when the sale closed on an accepted Best Offer. eBay does not publish the accepted amount on sold listings, so on these rows `displayed_price` is the asking price. Every such row is flagged, and `summary.median_excluding_best_offer` is computed without them.
- `format` is `auction` when the sale had bids. `bid_count` says how many.
- `location_text` is the seller's location as eBay shows it. It's often null for sellers in the country you searched.

- `sold_date` is the sale date as `YYYY-MM-DD`. eBay's `sold_date_text` has no year, so we use the most recent date that matches. The text's order depends on the site: `Sep-21 14:35`, `21-Sep 14:35` (UK, Canada) or `21. Sep. 14:35` (Germany).
- `displayed_shipping` is read only from exact text like `+$9.99 delivery` or `+£3.92 delivery`, in the site's currency. Free shipping is 0, however the site words it. Estimates and `Shipping not specified` are null.
- `condition` is eBay's own text in the site's language, like `Pre-Owned` or `Gebraucht`.
