eBay sold listings in Node.js and TypeScript
Fetch eBay sold listings from Node.js or TypeScript with native fetch: typed results, retries with Retry-After, a median price helper, and batch jobs.
Node 18+ ships fetch, so you need no dependencies to pull eBay sold data. Below is a typed client you can paste into any TypeScript project, server, or worker.
Typed client
const API = "https://api.ebaysoldlistingsapi.com";
const KEY = process.env.EBAY_SOLD_API_KEY!;
export type Sale = {
itemId: string;
title: string;
soldPrice: string;
soldCurrency: string;
endedAt: string;
condition: string;
buyingFormat: "auction" | "buyItNow";
shippingPrice: string | null;
totalPrice: string | null;
sellerUsername: string;
url: string;
bestOfferAccepted?: boolean;
};
const RETRY = new Set([429, 502, 503, 504]);
export async function sold(keyword: string, params: Record<string, string> = {}): Promise<Sale[]> {
const qs = new URLSearchParams({ keyword, ...params });
for (let attempt = 0; attempt < 4; attempt++) {
const res = await fetch(`${API}/scrape?${qs}`, {
headers: { Authorization: `Bearer ${KEY}` },
signal: AbortSignal.timeout(90_000),
});
if (!RETRY.has(res.status)) {
const body = await res.json();
if (!res.ok) throw new Error(`${body.code}: ${body.error}`);
return body.results as Sale[];
}
const wait = Number(res.headers.get("Retry-After") ?? 2 ** attempt);
await new Promise((r) => setTimeout(r, wait * 1000));
}
throw new Error("eBay sold search failed after retries");
}Median price helper
export function median(sales: Sale[]): number | null {
const p = sales.map((s) => Number(s.soldPrice)).filter(Number.isFinite).sort((a, b) => a - b);
if (!p.length) return null;
const m = Math.floor(p.length / 2);
return p.length % 2 ? p[m] : (p[m - 1] + p[m]) / 2;
}
const sales = await sold("steam deck oled", { itemCondition: "used" });
console.log(sales.length, "sales, median", median(sales));Next.js route handler
Keep your key on the server. A small route handler lets your front end ask for comps without exposing it:
// app/api/comps/route.ts
import { sold, median } from "@/lib/ebay-sold";
export async function GET(req: Request) {
const keyword = new URL(req.url).searchParams.get("q") ?? "";
if (!keyword) return Response.json({ error: "q is required" }, { status: 400 });
const sales = await sold(keyword);
return Response.json({ count: sales.length, median: median(sales) });
}Batch jobs
const job = await fetch(`${API}/scrape/batch`, {
method: "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ keywords: ["ps5 slim", "xbox series x"], ebaySite: "ebay.com" }),
}).then((r) => r.json());
let status;
do {
await new Promise((r) => setTimeout(r, 5000));
status = await fetch(`${API}/scrape/batch/${job.jobId}`, {
headers: { Authorization: `Bearer ${KEY}` },
}).then((r) => r.json());
} while (status.status !== "complete");Can I call the API from the browser?
Do not ship your API key to the browser. Call it from a server, serverless function, or route handler and return only what your UI needs.
Does it work in Cloudflare Workers or Deno?
Yes. The API is plain HTTPS with JSON, so any runtime with fetch works.
Is there an MCP server for AI agents?
Yes. Run npx ebaysoldlistingsapi or connect the hosted connector at https://mcp.ebaysoldlistingsapi.com/mcp. See the docs MCP section.
Try it with real data
Get completed eBay sales as JSON. Fifty free requests a month, no card required.
Get your API key