Get eBay sold prices in Node.js
You want real sold prices in JavaScript — what an item actually sold for, not its asking price. This is the shortest path: one fetch call, no scraping stack to babysit, no approval to get in.
The whole call
One fetch returns cleaned eBay completed sales for a keyword, plus a median/range summary computed over those rows. Works in Node 18+, Bun, Deno, and any edge runtime.
const KEY = process.env.GRAIL_KEY // free key: https://grail.solutions/signup
const r = await fetch(
"https://grail.solutions/v1/scrape?keyword=leica+m6&count=60&sold=true",
{ headers: { Authorization: `Bearer ${KEY}` } },
)
if (!r.ok) throw new Error(`Grail ${r.status}`)
const data = await r.json()
console.log(data.rawMedian) // 1549.5
console.log(data.items.length) // 58 real completed sales
console.log(data.items[0].soldPrice, data.items[0].endedAt)Get a key at /signup — 100 requests a month free, no card. Then mint the secret at /dashboard/api-keys (shown once).
It is typed
The response shape is stable and versioned, so you can describe it once — no XML, no cursor chasing, no shifting markup:
type SoldRow = {
itemId: string
title: string
url: string
condition: string
soldPrice: string // decimal string
shippingPrice: string
totalPrice: string
endedAt: string // yyyy-mm-dd
buyingFormat: string
}
type ScrapeResponse = {
keyword: string
totalItems: number
rawMedian: number
rawSampleCount: number
summary: { count: number; median: number; mean: number; min: number; max: number; p25: number; p75: number }
items: SoldRow[]
}Why the obvious paths are harder than they look
- eBay’s Product Research (Terapeak) is a dashboard — no JSON API, aggregates only.
- eBay’s official sold-data API (Marketplace Insights) is approval-gated and currently restricted, with no self-serve path in.
- Scraping eBay yourself means residential proxies, CAPTCHAs, session rotation, and a parser that breaks on layout changes — a second product to maintain.
One gotcha that bites everyone
Prices come back as decimal strings (e.g. "1549.50"), not numbers — this avoids silent float rounding on money. Coerce before you do math:
const prices = data.items
.map((row) => Number(row.soldPrice))
.filter((n) => Number.isFinite(n))Where to go next
- Python version →
- API reference → — every parameter, filter, and field.
- MCP server → — hand the same data to an agent in one tool call.
- Max Mode → — deep pagination and async batch beyond 240.
Ready? Grab a free key and make the call: grail.solutions/signup.