ebayecommerceprice-tracking

eBay Price History API for Collectibles

Track sold prices for collectibles, sneakers, vintage items via eBay SERP data. Build price history charts without eBay's restrictive official API.

8 min

eBay's Terapeak price history tool is UI-only with no API access. Collectors tracking Pokemon cards, sports memorabilia, and comics need programmatic price data. The workaround: search Google Shopping and eBay SERPs via a search API, extract sold prices from result snippets, and build your own price history database over time.

Why Terapeak is not enough

Terapeak shows sold item prices on eBay's website, but it is locked behind the eBay seller dashboard with no API. You cannot export data, set price alerts, or feed it into a spreadsheet automatically. For collectors who track hundreds of items across sets and conditions, manual lookups are not feasible. eBay's Browse API exists but does not expose completed/sold listings to third-party developers.

The SERP-based approach

Google indexes eBay sold listings. Searching "Pokemon Charizard Base Set sold price site:ebay.com" returns sold listing snippets with prices. By running these searches programmatically and extracting price data from snippets, you can build a price history database without scraping eBay directly.

Python
import requests, os, re, json
from datetime import datetime

def search_sold_prices(item_name: str, condition: str = "") -> list:
    """Search for eBay sold prices via Google SERP."""
    query = f"{item_name} {condition} sold price site:ebay.com"

    resp = requests.post(
        "https://api.scavio.dev/api/v1/search",
        headers={"x-api-key": os.environ["SCAVIO_API_KEY"]},
        json={"query": query, "platform": "google", "country_code": "us"},
        timeout=15,
    )
    results = resp.json().get("organic_results", [])

    prices = []
    for r in results:
        snippet = r.get("snippet", "")
        title = r.get("title", "")

        # Extract price patterns from snippets
        price_matches = re.findall(r'$[d,]+.?d*', snippet + " " + title)
        for price_str in price_matches:
            price = float(price_str.replace("$", "").replace(",", ""))
            if 0.99 < price < 100000:  # Filter obvious non-prices
                prices.append({
                    "price": price,
                    "source": r.get("link", ""),
                    "title": title,
                    "snippet": snippet,
                    "captured": datetime.utcnow().isoformat(),
                })

    return prices

# 1 credit per search
results = search_sold_prices("Pokemon Charizard Base Set Holo", "PSA 9")
for r in results[:5]:
    print(f"${r['price']:.2f} -- {r['title'][:60]}")

Building a price history database

Python
import sqlite3
from statistics import mean, median

DB_PATH = "collectible_prices.db"

def init_price_db():
    conn = sqlite3.connect(DB_PATH)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS price_history (
            id INTEGER PRIMARY KEY,
            item_name TEXT,
            condition TEXT,
            price REAL,
            source_url TEXT,
            captured_at TEXT,
            UNIQUE(item_name, condition, source_url)
        )
    """)
    conn.commit()
    return conn

def store_prices(conn, item_name: str, condition: str, prices: list):
    """Store prices, deduplicating by source URL."""
    inserted = 0
    for p in prices:
        try:
            conn.execute(
                "INSERT OR IGNORE INTO price_history (item_name, condition, price, source_url, captured_at) VALUES (?, ?, ?, ?, ?)",
                (item_name, condition, p["price"], p["source"], p["captured"]),
            )
            inserted += 1
        except sqlite3.IntegrityError:
            pass
    conn.commit()
    return inserted

def get_price_summary(conn, item_name: str, condition: str = "") -> dict:
    """Get price statistics for an item."""
    query = "SELECT price FROM price_history WHERE item_name = ?"
    params = [item_name]
    if condition:
        query += " AND condition = ?"
        params.append(condition)

    rows = conn.execute(query, params).fetchall()
    prices = [r[0] for r in rows]

    if not prices:
        return {"item": item_name, "data_points": 0}

    return {
        "item": item_name,
        "condition": condition,
        "data_points": len(prices),
        "mean": round(mean(prices), 2),
        "median": round(median(prices), 2),
        "min": min(prices),
        "max": max(prices),
        "range": round(max(prices) - min(prices), 2),
    }

Tracking a portfolio of collectibles

Python
# Define your collection to track
portfolio = [
    {"name": "Pokemon Charizard Base Set Holo", "condition": "PSA 9"},
    {"name": "Pokemon Charizard Base Set Holo", "condition": "PSA 10"},
    {"name": "Michael Jordan Fleer Rookie 1986", "condition": "PSA 8"},
    {"name": "Spider-Man Amazing Fantasy 15", "condition": "CGC 4.0"},
    {"name": "Magic The Gathering Black Lotus", "condition": "BGS 9"},
]

def daily_price_update(portfolio: list):
    """Run daily to build price history over time."""
    conn = init_price_db()
    total_new = 0

    for item in portfolio:
        prices = search_sold_prices(item["name"], item["condition"])
        new = store_prices(conn, item["name"], item["condition"], prices)
        total_new += new

        summary = get_price_summary(conn, item["name"], item["condition"])
        if summary["data_points"] > 0:
            print(f"{item['name']} ({item['condition']}): "
                  f"median ${summary['median']}, "
                  f"{summary['data_points']} data points")

    print(f"Added {total_new} new price records")
    conn.close()

# 5 items = 5 credits = $0.025/day = $0.75/month
# After 30 days you have a meaningful price history dataset

Limitations of this approach

  • Price extraction from snippets is approximate -- not every snippet contains a clean price
  • Google does not index every sold listing -- you get a sample, not the complete record
  • Sold prices in snippets may be listing prices, not final auction prices
  • No condition grading data beyond what appears in the title
  • Results lag actual sales by days to weeks (Google indexing delay)

Alternatives for more precise data

  • PriceCharting.com: manual lookups, no API, but curated price data for cards and games
  • PSA CardFacts: population reports and auction prices for graded cards, no bulk API
  • 130point.com: eBay sold price aggregator, no API but structured web data
  • eBay Browse API: active listings only, not sold prices -- insufficient for price history

The SERP-based approach is not perfect, but it is the only programmatic method that works across all collectible categories without scraping eBay directly. Run it daily for a month and you will have enough data points to see real price trends. Combine with manual spot-checks from Terapeak to validate accuracy.