Pull eBay Sold Prices into Google Sheets Automatically (2026)
Put real eBay sold prices into Google Sheets and keep them fresh on a schedule: one Apps Script, the eBay Sold Listings API, and a daily trigger. Sold price, date, title, condition, shipping and listing URL, one row per sale.
Short answer: to pull eBay sold prices into Google Sheets automatically, paste one Apps Script into your sheet that calls the eBay Sold Listings API (GET https://api.ebaysoldlistingsapi.com/scrape) for each keyword, writes up to 240 completed sales per keyword as rows (sold price, sold date, title, condition, shipping, listing URL), and runs on a daily time-driven trigger. Setup takes about 5 minutes and the free plan includes 50 requests a month, no card.
What you get
- A Keywords tab where you list what to track, one keyword per row, with an optional eBay marketplace.
- A Sold tab rewritten on every refresh: one row per completed sale with keyword, sold price, currency, sold date, title, condition, shipping, total price, Best Offer flag, accepted offer price and the eBay listing URL.
- An eBay Sold menu in the sheet: refresh now, refresh every day, stop refreshing.
- Up to 240 sold listings per keyword per refresh, across 8 eBay marketplaces (ebay.com, ebay.co.uk, ebay.de, ebay.com.au, ebay.ca, ebay.fr, ebay.it, ebay.es), in each marketplace's own currency.
Step 1: Get an API key
Create a free account and copy your API key from the dashboard. Keys start with eb_. The free plan includes 50 requests a month, and each keyword in a refresh uses one request. Errors are never billed.
Step 2: Set up the sheet
- Create a tab named Keywords. Put
Keywordin A1 andMarketplacein B1. - From row 2 down, add one keyword per row in column A, for example
steam deck oledorpokemon charizard ex 199/165 psa 10. Keep keywords short: brand plus model, or card name plus set and number. - Column B is optional. Leave it empty for ebay.com, or enter
ebay.co.uk,ebay.de,ebay.com.au,ebay.ca,ebay.fr,ebay.itorebay.es.
Step 3: Paste the Apps Script
Open Extensions, Apps Script, replace the contents of Code.gs with the script below, and save. Then open Project Settings, Script properties, and add a property named EBAY_SOLD_API_KEY with your key as the value, so the key never sits in a cell.
// eBay sold prices into Google Sheets, refreshed automatically.
// "Keywords" tab: column A = keyword, column B = optional marketplace (ebay.com, ebay.co.uk, ...).
// "Sold" tab: rewritten on every refresh, one row per completed sale.
const API_URL = "https://api.ebaysoldlistingsapi.com/scrape";
const HEADERS = ["Keyword", "Sold price", "Currency", "Sold date", "Title", "Condition",
"Shipping", "Total price", "Best Offer", "Accepted offer", "URL"];
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("eBay Sold")
.addItem("Refresh sold prices now", "refreshSoldPrices")
.addItem("Refresh every day", "installDailyTrigger")
.addItem("Stop automatic refresh", "removeDailyTrigger")
.addToUi();
}
function refreshSoldPrices() {
const ss = SpreadsheetApp.getActive();
const input = ss.getSheetByName("Keywords");
if (!input) throw new Error('Create a tab named "Keywords" with one keyword per row in column A.');
if (input.getLastRow() < 2) return;
const key = PropertiesService.getScriptProperties().getProperty("EBAY_SOLD_API_KEY");
if (!key) throw new Error("Add EBAY_SOLD_API_KEY in Project Settings, Script properties.");
const jobs = input.getRange(2, 1, input.getLastRow() - 1, 2).getValues()
.map((r) => ({ keyword: String(r[0]).trim(), site: String(r[1] || "ebay.com").trim() }))
.filter((j) => j.keyword);
const rows = [];
const errors = [];
jobs.forEach((job) => {
const data = fetchSold_(job, key);
if (data.error) {
errors.push(job.keyword + ": " + data.error);
return;
}
data.results.forEach((r) => rows.push([
job.keyword,
Number(r.soldPrice) || "",
r.soldCurrency || "",
r.endedAt ? new Date(r.endedAt + "T00:00:00") : "",
r.title || "",
r.condition || "",
Number(r.shippingPrice) || 0,
Number(r.totalPrice) || "",
r.bestOfferAccepted ? "Yes" : "",
r.acceptedOfferPrice ? Number(r.acceptedOfferPrice) : "",
r.url || "",
]));
});
// Keep the last good data if every keyword failed.
if (!rows.length && errors.length) throw new Error(errors.join(" | "));
const out = ss.getSheetByName("Sold") || ss.insertSheet("Sold");
out.clearContents();
out.getRange(1, 1, 1, HEADERS.length).setValues([HEADERS]).setFontWeight("bold");
if (rows.length) out.getRange(2, 1, rows.length, HEADERS.length).setValues(rows);
out.getRange(1, HEADERS.length + 2).setValue("Last refresh: " + new Date().toLocaleString());
out.getRange(2, HEADERS.length + 2).setValue(errors.join(" | "));
}
function fetchSold_(job, key) {
const url = API_URL + "?keyword=" + encodeURIComponent(job.keyword) +
"&ebaySite=" + encodeURIComponent(job.site) + "&bestOfferPrice=true";
for (let attempt = 0; attempt < 3; attempt++) {
const res = UrlFetchApp.fetch(url, {
headers: { Authorization: "Bearer " + key },
muteHttpExceptions: true,
});
const code = res.getResponseCode();
if (code === 200) return JSON.parse(res.getContentText());
if (code === 429 || code >= 500) {
const h = res.getHeaders();
const wait = Number(h["Retry-After"] || h["retry-after"]) || 5;
Utilities.sleep(Math.min(wait, 30) * 1000);
continue;
}
let message = "HTTP " + code;
try { message = JSON.parse(res.getContentText()).error || message; } catch (e) {}
return { error: message };
}
return { error: "still failing after 3 tries, the next refresh will retry" };
}
function installDailyTrigger() {
removeDailyTrigger();
ScriptApp.newTrigger("refreshSoldPrices").timeBased().everyDays(1).atHour(6).create();
SpreadsheetApp.getActive().toast("Sold prices will refresh every day around 6 AM.");
}
function removeDailyTrigger() {
ScriptApp.getProjectTriggers()
.filter((t) => t.getHandlerFunction() === "refreshSoldPrices")
.forEach((t) => ScriptApp.deleteTrigger(t));
}The same script, kept up to date, is in the public Google Sheets example alongside the Python and Node.js examples.
Step 4: Run it once, then turn on automatic refresh
- Reload the spreadsheet. An eBay Sold menu appears next to Help.
- Click eBay Sold, Refresh sold prices now. Google asks you to authorize the script the first time (it needs to call an external URL and edit this sheet).
- Check the Sold tab: one row per completed sale, newest data from eBay.
- Click eBay Sold, Refresh every day. A time-driven trigger now refreshes the Sold tab every morning around 6 AM, even when the sheet is closed.
Want a different schedule? Change everyDays(1).atHour(6) to everyHours(12) for twice a day, or everyWeeks(1).onWeekDay(ScriptApp.WeekDay.MONDAY) for weekly. If a scheduled refresh fails, Google emails you the error and the Sold tab keeps its last good data.
Step 5: Turn the rows into prices
Because every sale is a row, plain Sheets formulas give you the numbers you price from. With your keyword in A2 of any tab:
| You want | Formula |
|---|---|
| Median sold price | =MEDIAN(FILTER(Sold!B:B, Sold!A:A=A2)) |
| Number of sales | =COUNTIF(Sold!A:A, A2) |
| Average delivered price (price plus shipping) | =AVERAGE(FILTER(Sold!H:H, Sold!A:A=A2)) |
| Lowest and highest sale | =MIN(FILTER(Sold!B:B, Sold!A:A=A2)) and =MAX(...) |
| Most recent sale date | =MAX(FILTER(Sold!D:D, Sold!A:A=A2)) |
On sales closed through an accepted Best Offer, eBay shows the listing price, so Sold price is an upper bound on those rows. The Accepted offer column holds the price the buyer actually paid when it is available, and the daily refresh fills in prices that were still pending. See Best Offer prices. For a full pricing method (sample size, outliers, condition), read how to price items from eBay sold comps.
How many requests does it use?
One request per keyword per refresh, and each request returns up to 240 sold listings.
| Keywords tracked | Refresh | Requests per month | Plan that fits |
|---|---|---|---|
| 1 | Daily | about 30 | Free (50 requests) |
| 10 | Weekly | about 45 | Free (50 requests) |
| 20 | Daily | about 600 | Starter (3,000 requests) |
| 100 | Daily | about 3,000 | Starter or Growth (15,000 requests) |
| 500 | Daily | about 15,000 | Growth (15,000 requests) |
Apps Script stops a single run after 6 minutes, so for hundreds of keywords split them across sheets or use batch jobs: send up to 100 keywords in one job and import the finished CSV. See plans and pricing.
Ways to get eBay sold prices into Google Sheets, compared
| Method | Sold prices | Refreshes automatically | Rows per keyword | Setup |
|---|---|---|---|---|
| Manual copy and paste from eBay's sold search | Yes | No | What you copy by hand | None, but repeated every time |
| IMPORTXML or IMPORTHTML on an eBay page | Unreliable | Breaks often | Usually none | One formula, fails without notice |
| eBay Browse API | No, active listings only | Yes, with code | Active listings | eBay developer account and OAuth |
| eBay Finding API (findCompletedItems) | No, retired in February 2025 | No | None | Not available |
| eBay Marketplace Insights API | Yes | Yes, with code | Sold listings | Restricted, needs eBay approval |
| eBay Sold Listings API + Apps Script | Yes | Yes, daily trigger | Up to 240 per request | Free key and one script, about 5 minutes |
More on eBay's own options: eBay official APIs compared and does the eBay Browse API return sold listings?.
Variations
- One number per cell: a custom
=EBAYSOLD(A2)function that returns the median sold price directly. See eBay sold prices in Google Sheets with a custom function. - Used or new only: add
&itemCondition=used(ornew) to the URL infetchSold_. - A date window: add
&soldAfter=2026-10-01to only keep recent sales. - CSV instead of JSON: add
&format=csvand parse withUtilities.parseCsv. See CSV export. - Excel or a local file: export eBay sold data to a spreadsheet with a short Python script.
How can I pull eBay sold prices into Google Sheets automatically?
Add an Apps Script to your sheet that calls the eBay Sold Listings API (GET https://api.ebaysoldlistingsapi.com/scrape?keyword=...) with your API key, writes each completed sale as a row (sold price, date, title, condition, shipping, URL), and runs on a daily time-driven trigger. The full script is on this page and the free plan includes 50 requests a month.
Can Google Sheets get eBay sold prices for free?
Yes. The eBay Sold Listings API free plan includes 50 requests a month with no card, and each keyword refresh uses one request returning up to 240 sold listings. That covers one keyword refreshed daily or about ten keywords refreshed weekly.
Why does IMPORTXML not work for eBay sold listings?
eBay's sold search pages are not built to be read by Google Sheets formulas: requests from Google's servers are often blocked and the page layout changes, so IMPORTXML returns errors or empty cells. An API returns stable JSON with named fields.
Does the official eBay API work in Google Sheets for sold prices?
Not for most people. The Browse API returns active listings only, the Finding API (findCompletedItems) was retired in February 2025, and the Marketplace Insights API, which has sold data, needs eBay approval. The eBay Sold Listings API returns sold listings with an API key and no eBay approval.
How often can the sheet refresh?
As often as Apps Script triggers allow, from every hour to once a week. Each keyword uses one request per refresh, so a daily refresh of 20 keywords uses about 600 requests a month.
Which eBay marketplaces work in Google Sheets?
All 8: ebay.com, ebay.co.uk, ebay.de, ebay.com.au, ebay.ca, ebay.fr, ebay.it and ebay.es. Put the marketplace in column B of the Keywords tab. Prices come back in that marketplace's local currency.
Try it with real data
Get completed eBay sales as JSON. Fifty free requests a month, no card required.
Get your API key