If you need a 130point alternative with an API, you've probably already found the problem: 130point is a lookup website, built for one search at a time in a browser. There's no documented developer API. You can't wire it into a card shop's pricing sheet, a collection tracker or a grading-ROI calculator.
This post shows how to get the same kind of data, eBay sold comps for graded and raw cards, plus the real accepted Best Offer price, from a REST API you can call from Python or JavaScript.
What card people actually need from comps
If you've priced cards for a while, your checklist looks like this:
- The exact card. Right player, year, set, number and parallel. Not the base version when you searched the refractor.
- The right grade. A PSA 9 sale in a PSA 10 search ruins a median.
- What the buyer really paid. On Best Offer sales eBay shows the asking price, not the accepted one.
- Enough history. Slow cards might sell twice a month, so 90 days is often too thin.
- Population. How many 10s exist decides whether a 10 is rare or common.
Here's how to get each one.
1. eBay sold comps for a graded card
curl -H "Authorization: Bearer $COMPSNIPER_API_KEY" \
"https://api.compsniper.com/v1/scrape?keyword=2023+topps+chrome+wembanyama+psa+10&count=240&bestOfferPrice=true"
One request returns up to 240 sold listings with sold price, date, condition, shipping and seller, plus a ready-made summary: median, 25th and 75th percentile, min, max and average shipping.
The part that matters for cards: an AI cleanup step removes wrong matches before the median is calculated. That means other grades, lots, different parallels and "card plus slab plus case" bundles. In a blind 40-search benchmark, 5.4% of the sales it returned were the wrong product, against 25.7% to 31.6% for other eBay sold data APIs tested. Want the raw feed exactly as eBay lists it? Add relevance=false.
2. The real price on Best Offer sales
Card sales close on offers all the time. Add bestOfferPrice=true (as above) and each accepted-offer sale gains acceptedOfferPrice, the price the buyer actually paid.
A real example from October 25, 2026: a PSA 10 Charizard VMAX 074/073 was listed at $349.00 and sold on an offer for $307.00. Without the accepted price, that comp is 12% too high.
3. PSA prices by grade and the population report
For graded cards, PSA's own data adds what eBay can't: prices across every grade and the population.
# Find the card's PSA spec ID
curl -H "Authorization: Bearer $COMPSNIPER_API_KEY" \
"https://api.compsniper.com/v1/psa/search?keyword=charizard+base+set&count=5"
# Prices by grade, recent sales and the population for that card
curl -H "Authorization: Bearer $COMPSNIPER_API_KEY" \
"https://api.compsniper.com/v1/psa/card?specId=5217539&count=20"
/v1/psa/card returns:
-
priceByGrade: for each grade, the number of sales, average, min, max and latest price -
population: total graded, the gem rate (share of PSA 10s) and the count for every grade -
items: recent sales with grade, cert number, sale type and date
You can filter to one grade with grade=10 (half grades and raw work too). There's also /v1/psa/set for the population of every card in a set in one call.
4. More than 90 days of history
For cards that don't move every day, GET /v1/research/sold goes back up to 3 years, between any two dates:
curl -H "Authorization: Bearer $COMPSNIPER_API_KEY" \
"https://api.compsniper.com/v1/research/sold?keyword=wembanyama+topps+chrome+psa+10&startDate=2023-11-05&endDate=2026-11-04&sort=lastSoldDescending&limit=50"
It's included on every plan, free too.
Putting it together in Python: a grading ROI check
Is a raw card worth sending to PSA? Compare what raw copies sell for against what PSA 9s and 10s sell for, weighted by the gem rate.
import os
import requests
API = "https://api.compsniper.com"
H = {"Authorization": f"Bearer {os.environ['COMPSNIPER_API_KEY']}"}
def get(path, **params):
r = requests.get(API + path, headers=H, params=params, timeout=90)
r.raise_for_status()
return r.json()
card = get("/v1/psa/card", keyword="charizard base set unlimited holo 4/102")
by_grade = {g["grade"]: g for g in card["priceByGrade"]}
gem_rate = card["population"]["gemRate"]
raw = get("/v1/scrape", keyword="charizard base set unlimited holo 4/102 -psa -bgs -cgc", count=100)
raw_median = raw["summary"]["median"]
psa10 = by_grade.get("10", {}).get("averagePrice")
psa9 = by_grade.get("9", {}).get("averagePrice")
print(f"raw median ${raw_median}, PSA 9 avg ${psa9}, PSA 10 avg ${psa10}, gem rate {gem_rate}%")
Swap in your own grading fee and shipping and you have a real answer in a few lines.
Same idea in JavaScript
const H = { Authorization: `Bearer ${process.env.COMPSNIPER_API_KEY}` };
const get = async (path, params) => {
const res = await fetch(`https://api.compsniper.com${path}?${new URLSearchParams(params)}`, { headers: H });
if (!res.ok) throw new Error(`API error ${res.status}`);
return res.json();
};
const comps = await get("/v1/scrape", {
keyword: "2023 topps chrome wembanyama psa 10",
count: "240",
bestOfferPrice: "true",
});
console.log("median", comps.summary.median, "from", comps.items.length, "sales");
Beyond eBay
Graded cards also sell at auction houses. The same key returns realized prices from Goldin, Fanatics Collect, Pristine Auction and MySlabs (/v1/goldin/sold, /v1/fanatics/sold, /v1/pristine/sold, /v1/myslabs/sold), and TCGplayer sold history for TCG cards (/v1/tcgplayer/sold).
Try it
Get a free key at compsniper.com (100 requests a month, no card). More detail: Sports card price API, Pokemon card price API and PSA API docs. Code: ebay-sold-listings-api on GitHub.
What's the one comp you always double-check by hand before you trust it: grade, parallel or Best Offer price?
Top comments (0)