# eBay sold listings in Node.js and TypeScript

URL: https://ebaysoldlistingsapi.com/blog/ebay-sold-listings-api-nodejs

Updated: 2026-09-29



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");
```

Q: Can I call the API from the browser?
A: 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.

Q: Does it work in Cloudflare Workers or Deno?
A: Yes. The API is plain HTTPS with JSON, so any runtime with fetch works.

Q: Is there an MCP server for AI agents?
A: Yes. Run npx ebaysoldlistingsapi or connect the hosted connector at https://mcp.ebaysoldlistingsapi.com/mcp. See the docs MCP section.
