ebaysoldlistingsAPI
Free template

eBay sold prices in Google Sheets: free template (2026)

Short answer: add one Apps Script to your sheet and type =ESLA_MEDIAN("nintendo switch oled") in any cell to get the median eBay sold price. =ESLA_SOLD() lists the recent sales row by row, and a profit column subtracts fees and your costs, so your whole inventory prices itself.

Updated

What your sheet looks like

ABCDEFG
1ItemSold medianItem costShipping costFeesProfitMargin
2nintendo switch oled=ESLA_MEDIAN(A2)18012=IF(ISNUMBER(B2), B2*$I$1 + $I$2, "")=IF(ISNUMBER(B2), B2 - E2 - C2 - D2, "")=IF(ISNUMBER(F2), F2 / B2, "")
  • A Item: What you sell, as you would search it on eBay
  • B Sold median: Median price buyers paid recently
  • C Item cost: What you paid
  • D Shipping cost: Your label
  • E Fees: eBay fee rate in I1 (13.6%) plus the per-order fee in I2 ($0.40)
  • F Profit: Sold median minus fees minus your costs
  • G Margin: Format as percent

Set it up in five minutes

  1. Create a free account and copy your API key from the dashboard. It starts with eb_.
  2. Open a Google Sheet and choose Extensions, Apps Script.
  3. Delete the sample code, paste the script below with the Copy button, and click Save.
  4. Reload the sheet. A Sold prices menu appears. Choose Set API key, allow the permissions Google asks for, and paste your key.
  5. Type an item in A2 and =ESLA_MEDIAN(A2) in B2. Add the cost, fee and profit columns from the layout above, then fill down for every item.
Code.gs
/**
 * eBay Sold Listings API for Google Sheets
 * https://ebaysoldlistingsapi.com/tools/google-sheets-template
 *
 *   =ESLA_MEDIAN("nintendo switch oled")         median sold price
 *   =ESLA_MEDIAN(A2, "ebay.co.uk")               another marketplace, local currency
 *   =ESLA_SOLD(A2)                               the 10 most recent sales as rows
 *   =ESLA_SOLD(A2, "ebay.com", 50)               up to 240 rows
 *   =ESLA_COUNT(A2)                              number of sales found
 *
 * Setup: reload the sheet, then use the menu "Sold prices" > "Set API key".
 * Your key is saved in this script's properties, never in a cell.
 */

var ESLA_API = "https://api.ebaysoldlistingsapi.com/scrape";
var ESLA_CACHE_SECONDS = 6 * 60 * 60; // repeat formulas within 6 hours reuse the answer (no extra request)

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu("Sold prices")
    .addItem("Set API key", "eslaSetApiKey")
    .addItem("Test connection", "eslaTest")
    .addToUi();
}

function eslaSetApiKey() {
  var ui = SpreadsheetApp.getUi();
  var res = ui.prompt(
    "eBay Sold Listings API key",
    "Paste your API key (it starts with eb_). It is stored in this script's properties, not in the sheet.",
    ui.ButtonSet.OK_CANCEL
  );
  if (res.getSelectedButton() !== ui.Button.OK) return;
  var key = res.getResponseText().trim();
  if (key.indexOf("eb_") !== 0) {
    ui.alert("That does not look like an API key. Keys start with eb_. Copy yours from your dashboard.");
    return;
  }
  PropertiesService.getScriptProperties().setProperty("ESLA_API_KEY", key);
  ui.alert("Saved. Your sold price formulas are ready.");
}

function eslaTest() {
  var prices = eslaFetch_("nintendo switch oled", "ebay.com");
  SpreadsheetApp.getUi().alert("Connected. Found " + prices.length + " sales for nintendo switch oled.");
}

function eslaFetch_(keyword, site) {
  keyword = String(keyword || "").trim();
  site = String(site || "ebay.com").trim();
  if (!keyword) throw new Error("Add a search, for example nintendo switch oled.");
  var key = PropertiesService.getScriptProperties().getProperty("ESLA_API_KEY");
  if (!key) throw new Error("Add your API key: menu Sold prices > Set API key.");

  var cache = CacheService.getScriptCache();
  var cacheKey = "esla:" + site + ":" + keyword.toLowerCase();
  var hit = cache.get(cacheKey);
  if (hit) return JSON.parse(hit);

  var url = ESLA_API + "?keyword=" + encodeURIComponent(keyword) +
    "&ebaySite=" + encodeURIComponent(site) + "&count=240&bestOfferPrice=true";
  var res = UrlFetchApp.fetch(url, {
    headers: { Authorization: "Bearer " + key },
    muteHttpExceptions: true
  });
  var code = res.getResponseCode();
  var body = {};
  try { body = JSON.parse(res.getContentText()); } catch (e) { body = {}; }
  if (code === 401) throw new Error("API key not accepted. Set it again from the Sold prices menu.");
  if (code === 402) throw new Error("Monthly requests used up. Upgrade at ebaysoldlistingsapi.com/#pricing");
  if (code === 429) throw new Error("Too many requests this minute. The cell retries when the sheet recalculates.");
  if (code !== 200) throw new Error(body.error || ("Request failed (" + code + "). Try again shortly."));

  var sales = (body.results || []).map(function (r) {
    // Best Offer sales: use the price the buyer actually paid when it is known.
    var paid = r.acceptedOfferPriceStatus === "found" && r.acceptedOfferPrice ? Number(r.acceptedOfferPrice) : Number(r.soldPrice);
    return {
      date: r.endedAt || r.soldDate || "",
      title: r.title || "",
      price: paid,
      shipping: r.shippingType === "free" ? 0 : Number(r.shippingPrice || 0),
      condition: r.condition || "",
      url: r.url || ""
    };
  }).filter(function (s) { return s.price > 0; });

  try { cache.put(cacheKey, JSON.stringify(sales), ESLA_CACHE_SECONDS); } catch (e) { /* too large to cache */ }
  return sales;
}

/**
 * Median eBay sold price for a search.
 * @param {string} keyword What to search, e.g. "nintendo switch oled" or a cell like A2.
 * @param {string} site Optional marketplace: ebay.com (default), ebay.co.uk, ebay.de, ebay.com.au, ebay.ca, ebay.fr, ebay.it, ebay.es.
 * @param {any} refresh Optional. Put TODAY() here to refresh once a day.
 * @return The median sold price in the marketplace's currency.
 * @customfunction
 */
function ESLA_MEDIAN(keyword, site, refresh) {
  var prices = eslaFetch_(keyword, site).map(function (s) { return s.price; }).sort(function (a, b) { return a - b; });
  if (!prices.length) return "No sales found";
  var mid = Math.floor(prices.length / 2);
  return prices.length % 2 ? prices[mid] : (prices[mid - 1] + prices[mid]) / 2;
}

/**
 * Recent eBay sales for a search, one row per sale.
 * @param {string} keyword What to search, or a cell like A2.
 * @param {string} site Optional marketplace, default ebay.com.
 * @param {number} rows Optional number of sales to show, default 10, up to 240.
 * @return Rows of sold date, title, price paid, shipping, condition and link.
 * @customfunction
 */
function ESLA_SOLD(keyword, site, rows) {
  var limit = Math.max(1, Math.min(240, Number(rows) || 10));
  var sales = eslaFetch_(keyword, site).slice(0, limit);
  var out = [["Sold date", "Title", "Price paid", "Shipping", "Condition", "Link"]];
  sales.forEach(function (s) { out.push([s.date, s.title, s.price, s.shipping, s.condition, s.url]); });
  if (out.length === 1) out.push(["No sales found", "", "", "", "", ""]);
  return out;
}

/**
 * Number of recent eBay sales found for a search.
 * @param {string} keyword What to search, or a cell like A2.
 * @param {string} site Optional marketplace, default ebay.com.
 * @customfunction
 */
function ESLA_COUNT(keyword, site) {
  return eslaFetch_(keyword, site).length;
}

The formulas

FormulaReturns
=ESLA_MEDIAN(A2)Median price paid on recent eBay US sales. Best Offer sales count at the accepted price when known.
=ESLA_MEDIAN(A2, "ebay.de")The same on another marketplace, in its own currency.
=ESLA_MEDIAN(A2, "ebay.com", TODAY())Recalculates once a day.
=ESLA_SOLD(A2, "ebay.com", 25)The 25 most recent sales as rows: date, title, price paid, shipping, condition, link.
=ESLA_COUNT(A2)How many recent sales were found, a quick demand check.

Price thousands of rows

Each new search uses one request, and repeats within six hours are free. A free key covers 50 searches a month, enough for a small sheet. For a full inventory sheet that refreshes every day, ESLA Unlimited gives you unlimited requests for $299 a month. Need fees for other marketplaces in the profit column? Take the rates from the seller fee comparison. For a script that fills a whole tab with every sale on a daily schedule, see the guide to pulling eBay sold prices into Google Sheets automatically.

ESLA Unlimited

Price your whole inventory, not one item at a time

The same sold data, straight into your app, sheet or repricer. Unlimited requests for $299 a month, up to 240 completed sales per call, eight eBay marketplaces, CSV export and batch jobs of 100 keywords. Start free with 50 requests a month, no card needed.

Frequently asked questions

How do I get eBay sold prices into Google Sheets?

Paste the Apps Script on this page into Extensions, Apps Script, save your API key from the Sold prices menu, then type =ESLA_MEDIAN(A2) next to any item. The cell fills with the median price buyers recently paid on eBay.

Is my API key safe in the sheet?

The key is stored in the script's properties, not in any cell, so it does not show in the sheet, in exports or in version history. Anyone you give edit access to the script can still use it, so share edit access only with people you trust.

How many requests does the sheet use?

One request per new search. Answers are cached for six hours, so reopening the sheet or copying a formula for the same item does not use another request. The free plan includes 50 requests a month; ESLA Unlimited has no monthly cap.

How do I refresh the prices every day?

Add TODAY() as the third argument, for example =ESLA_MEDIAN(A2, "ebay.com", TODAY()). The formula then recalculates once a day.

Does it work for eBay UK, Germany or Australia?

Yes. Pass the marketplace as the second argument, for example =ESLA_MEDIAN(A2, "ebay.co.uk"). Prices come back in that marketplace's currency.

More free tools

All free tools