eBay sold listings history beyond the 90-day window
eBay shows sold listings for 90 days. The eBay sold listings history behind them is in GET /ebay/v1/sold. This script reads a year of it, one month per call.
eBay's 90-day wall
Open eBay's search filters and tick "Sold items". The list shows sales from about the last 90 days. Older sales are not on the page, and their prices go with them. A sale from last spring cannot be found on the site or in the app, in any category.
eBay gives sellers one way back. Product Research in Seller Hub, formerly Terapeak, reaches further than the public filter. It needs a seller account, and it answers one search at a time. It does not give you the sales as data.
For sales older than 90 days as data, create a free account and do the steps below. The eBay Sold Listings API records sales as they happen and keeps them. It answers for any window in its history.
Do these steps
Click the button to copy this prompt, then paste it into your coding agent. The link tells it which page you came from.
Read https://trawl.dev/llms.txt?from=/guides/ebay-sold-listings-history and help me integrate this API into my project.- Set your API key. Get a key from the free tier. terminal
export TRAWL_API_KEY=your_key - Run the script in your language. Python needs the requests package. Node 18 and Go 1.22 need nothing more. Each month costs 1 credit.
# sold_history.py (pip install requests)
import os
import statistics
import time
import requests
API = "https://api.trawl.dev/ebay/v1/sold"
HEADERS = {"x-api-key": os.environ["TRAWL_API_KEY"]}
QUERY = "iphone 13 128gb unlocked"
# One call per month, across a year. eBay's own sold view stops 90 days back.
MONTHS = [
("2025-10-01", "2025-10-31"),
("2026-01-01", "2026-01-31"),
("2026-04-01", "2026-04-30"),
("2026-07-01", "2026-07-31"),
("2026-09-01", "2026-09-30"),
]
for date_from, date_to in MONTHS:
r = requests.get(API, headers=HEADERS, timeout=30, params={
"query": QUERY, "condition": "used",
"date_from": date_from, "date_to": date_to,
"max_pages": 1, # the newest 100 sales of the month, 1 credit
})
r.raise_for_status()
sold = r.json()
prices = [s["sale_price"] for s in sold["results"]]
median = f"${statistics.median(prices):.2f}" if prices else "no sales"
oldest = min(s["date_sold"] for s in sold["results"])[:10] if prices else "-"
# A full page means the month may have older sales. Repeat with date_to=oldest.
full = ", page full" if sold["count"] == 100 else ""
print(f"{date_from[:7]} {sold['count']:>3} sales median {median:>8} oldest {oldest}{full}")
time.sleep(0.6) # stay under the plan's requests-per-second limit// sold_history.mjs (Node 18 or newer, no package)
const API = "https://api.trawl.dev/ebay/v1/sold";
const HEADERS = { "x-api-key": process.env.TRAWL_API_KEY };
const QUERY = "iphone 13 128gb unlocked";
// One call per month, across a year. eBay's own sold view stops 90 days back.
const MONTHS = [
["2025-10-01", "2025-10-31"],
["2026-01-01", "2026-01-31"],
["2026-04-01", "2026-04-30"],
["2026-07-01", "2026-07-31"],
["2026-09-01", "2026-09-30"],
];
const median = (xs) => {
const s = [...xs].sort((a, b) => a - b), m = s.length >> 1;
return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2;
};
for (const [dateFrom, dateTo] of MONTHS) {
const params = new URLSearchParams({
query: QUERY, condition: "used", date_from: dateFrom, date_to: dateTo,
max_pages: "1", // the newest 100 sales of the month, 1 credit
});
const r = await fetch(`${API}?${params}`, { headers: HEADERS });
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
const sold = await r.json();
const prices = sold.results.map((s) => s.sale_price);
const med = prices.length ? `$${median(prices).toFixed(2)}` : "no sales";
const oldest = prices.length ? sold.results.map((s) => s.date_sold).sort()[0].slice(0, 10) : "-";
// A full page means the month may have older sales. Repeat with date_to=oldest.
const full = sold.count === 100 ? ", page full" : "";
console.log(`${dateFrom.slice(0, 7)} ${String(sold.count).padStart(3)} sales median ${med.padStart(8)} oldest ${oldest}${full}`);
await new Promise((done) => setTimeout(done, 600)); // stay under the plan's requests-per-second limit
}// sold_history.go (Go 1.22 or newer, standard library only)
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"sort"
"time"
)
type sale struct {
SalePrice float64 `json:"sale_price"`
DateSold string `json:"date_sold"`
}
func main() {
query := "iphone 13 128gb unlocked"
// One call per month, across a year. eBay's own sold view stops 90 days back.
months := [][2]string{{"2025-10-01", "2025-10-31"}, {"2026-01-01", "2026-01-31"}, {"2026-04-01", "2026-04-30"}, {"2026-07-01", "2026-07-31"}, {"2026-09-01", "2026-09-30"}}
for _, m := range months {
// max_pages 1: the newest 100 sales of the month, 1 credit
q := url.Values{"query": {query}, "condition": {"used"}, "date_from": {m[0]}, "date_to": {m[1]}, "max_pages": {"1"}}
req, _ := http.NewRequest("GET", "https://api.trawl.dev/ebay/v1/sold?"+q.Encode(), nil)
req.Header.Set("x-api-key", os.Getenv("TRAWL_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil || res.StatusCode != 200 {
panic(fmt.Sprint(err, res))
}
var s struct{ Count int; Results []sale }
json.NewDecoder(res.Body).Decode(&s)
median, oldest, full := "no sales", "-", ""
if n := len(s.Results); n > 0 {
oldest = s.Results[n-1].DateSold[:10] // results come newest first
sort.Slice(s.Results, func(i, j int) bool { return s.Results[i].SalePrice < s.Results[j].SalePrice })
median = fmt.Sprintf("$%.2f", (s.Results[n/2].SalePrice+s.Results[(n-1)/2].SalePrice)/2)
}
if s.Count == 100 { // a full page means the month may have older sales; repeat with date_to=oldest
full = ", page full"
}
fmt.Printf("%s %3d sales median %8s oldest %s%s\n", m[0][:7], s.Count, median, oldest, full)
time.Sleep(600 * time.Millisecond) // stay under the plan's requests-per-second limit
}
}2025-10 5 sales median $293.68 oldest 2025-10-03
2026-01 89 sales median $269.94 oldest 2026-01-01
2026-04 100 sales median $225.00 oldest 2026-04-29, page full
2026-07 100 sales median $230.00 oldest 2026-07-31, page full
2026-09 100 sales median $244.05 oldest 2026-09-29, page full2025-10 5 sales median $293.68 oldest 2025-10-03
2026-01 89 sales median $269.94 oldest 2026-01-01
2026-04 100 sales median $225.00 oldest 2026-04-29, page full
2026-07 100 sales median $230.00 oldest 2026-07-31, page full
2026-09 100 sales median $244.05 oldest 2026-09-29, page full2025-10 5 sales median $293.68 oldest 2025-10-03
2026-01 89 sales median $269.94 oldest 2026-01-01
2026-04 100 sales median $225.00 oldest 2026-04-29, page full
2026-07 100 sales median $230.00 oldest 2026-07-31, page full
2026-09 100 sales median $244.05 oldest 2026-09-29, page fullHow the windows work
date_from and date_to are inclusive calendar dates. The sales in a window come newest first. The first window, October 2025, holds five sales. January holds most of a page. The later months hold a full page.
The medians are the point. This phone sold for a median of $294 in October 2025 and $244 in September 2026. None of that is visible on eBay today.
Page a full month
A count of 100 with max_pages at 1 means the month may have more sales. Raise max_pages to get more pages in one response. Or repeat the call with date_to set to the oldest date_sold received. Stop when a page comes back short. The script prints that oldest date for this purpose.
The scrape eBay sold listings in Python guide shows the shape of one sale and how to open one listing in full.