TCGplayer Sold Prices API
Get TCGplayer completed sales, market price history and active listings for Pokemon, Magic, Yu-Gi-Oh and other trading cards with one API key.
CompSniper has two TCGplayer endpoints for trading cards and sealed products (Pokemon, Magic: The Gathering, Yu-Gi-Oh, One Piece, Lorcana, sports and more):
| Endpoint | What you get |
|---|---|
GET /v1/tcgplayer/sold | Completed sales (price, condition, variant, language, quantity, date) plus optional market price history |
GET /v1/tcgplayer/listings | Active listings with price, shipping, condition and seller details |
Both use the same API key, per-minute limit, monthly allowance and purchased credits as the eBay endpoints. No TCGplayer account is needed, and you get the full sale history for each product, not just the latest few sales.
For an overview with code in cURL, Python and JavaScript, see the TCGplayer API page.
Find a product
Send a keyword (card name, set, number) or an exact TCGplayer productId. With a keyword, CompSniper picks
the best matching product and lists other close matches in otherMatches, so you can repeat the call with
the right productId when a name is ambiguous.
curl --fail-with-body \
-H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" \
"https://api.compsniper.com/v1/tcgplayer/sold?keyword=charizard+ex+obsidian+flames+223&count=100&history=quarter"The productId is the number in a TCGplayer product URL, for example
https://www.tcgplayer.com/product/123456/... has productId=123456.
Search by product name, not an eBay title
The keyword is matched against TCGplayer's own product names. An eBay or marketplace listing title pasted as is (with words like "sealed", "mint", "AU stock", "eBay Live", emojis or lot sizes) usually matches no product, so the call returns no sales. Several products in one keyword ("Prismatic Evolutions + 151 + CR") also match nothing: send one call per product.
| Returns no sales | Works |
|---|---|
#001 - [EBAY LIVE] Pokémon TCG Scarlet & Violet 151 English Booster Bundle - New | Pokemon 151 Booster Bundles |
Pokemon 151 Japanese Booster Box AU stock on hand | Pokemon Japanese 151 booster box |
Use the card or product name, the set, and the card number when there is one. Once you know the right
product, send its productId: it always returns that exact product and skips the name match.
Sold parameters
| Parameter | Values | Default |
|---|---|---|
keyword | Card or product name, up to 300 characters | Required unless productId is sent |
productId | TCGplayer product ID | None |
count | 1 to 250 sales | 100 |
condition | For example Near Mint, Lightly Played | All conditions |
variant | For example Normal, Holofoil, Reverse Holofoil | All variants |
language | For example English, Japanese | All languages |
history | none, month, quarter, annual (market price buckets) | none |
soldAfter | YYYY-MM-DD: only sales on or after this date (UTC) | None |
soldBefore | YYYY-MM-DD: only sales on or before this date (UTC) | None |
fresh | true fetches the latest sales live from TCGplayer | false |
Daily pulls and busy products
Results come back instantly: repeat calls can be answered from a short-lived saved copy. When you need
the newest sales right now, add fresh=true and the call goes live to TCGplayer. It costs one request,
the same as any other call.
soldAfter and soldBefore trim the returned sales to a date window, so a nightly job can ask only for
what it has not stored yet (keep a one-day overlap and deduplicate on your side). They filter the newest
sales TCGplayer exposes for the product (up to 250); they do not reach further back. count applies
before the date filter, so send count=250 with a date window. For a product that sells more than 250
copies a day, call it two or three times a day with fresh=true and soldAfter set to today.
Sold response
{
"keyword": "charizard ex obsidian flames 223",
"marketplace": "tcgplayer",
"product": {
"productId": 123456,
"name": "Charizard ex - 223/197",
"productLine": "Pokemon",
"setName": "SV03: Obsidian Flames",
"number": "223/197",
"rarity": "Special Illustration Rare",
"sealed": false,
"marketPrice": 74.5,
"lowestPrice": 69.99,
"lowestPriceWithShipping": 71.98,
"totalListings": 212,
"url": "https://www.tcgplayer.com/product/123456"
},
"otherMatches": [],
"salesHistory": "full",
"totalSales": 1840,
"totalItems": 100,
"items": [
{
"title": "Charizard ex - 223/197",
"condition": "Near Mint",
"variant": "Holofoil",
"language": "English",
"quantity": 1,
"price": 73.0,
"shipping": 1.99,
"totalPrice": 74.99,
"soldAt": "2026-10-09T18:22:41Z",
"listingType": "ListingWithoutPhotos"
}
],
"priceHistory": [
{
"date": "2026-10-08",
"variant": "Holofoil",
"condition": "Near Mint",
"language": "English",
"marketPrice": 74.12,
"quantitySold": 31,
"transactionCount": 27,
"lowSalePrice": 68.0,
"highSalePrice": 79.95
}
],
"summary": { "count": 100, "currency": "USD", "median": 73.5, "mean": 73.62, "min": 64, "max": 82.5, "p25": 71, "p75": 76, "avgShipping": null },
"scrapedAt": "2026-10-10T01:12:09Z"
}salesHistoryis"full"when the complete history was returned. If it ever says"limited", only the most recent sales were available for that call; retry later for the full list.totalSalesis TCGplayer's count of all recorded sales for the product;itemsholds up tocountof the newest ones.priceHistory(only withhistory=month|quarter|annual) gives daily or weekly market-price buckets with units sold, transaction count and the low and high sale price. It isnullwhen not requested.summaryholds the count, median, mean, min, max, p25 and p75 of the returned sale prices.
Active listings
curl --fail-with-body \
-H "Authorization: Bearer cs_live_REPLACEWITHYOURKEY" \
"https://api.compsniper.com/v1/tcgplayer/listings?productId=123456&condition=Near+Mint&count=25&page=1"| Parameter | Values | Default |
|---|---|---|
keyword or productId | Same as the sold endpoint | Required |
page | 1 to 50 | 1 |
count | 1 to 50 listings per page | 25 |
condition, variant, language | Same filters as the sold endpoint | All |
Each listing has listingId, price, shipping, totalPrice, condition, variant, language,
quantity, sellerName, sellerRating, sellerSales, sellerLocation, and the goldSeller,
verifiedSeller and directSeller flags. The response includes totalListings and hasNextPage for
paging. Asking prices are not sales, so listings responses carry no price summary.
Billing
- One successful call is one request, for sold and listings alike.
- Failed requests are free.
- Repeat calls can return instantly from a short-lived saved copy and count as one request. Add
fresh=truefor a live fetch.
Errors
The TCGplayer endpoints use the shared error format (code, error, requestId). The ones you are most
likely to see:
| Status | Code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_params | Missing keyword and productId, or a value out of range | Fix the request |
| 429 | rate_limited / quota_exceeded | Per-minute limit or monthly allowance reached | See Rate limits |
| 502, 503 | upstream_blocked / server_busy | TCGplayer did not answer this time | Retry after a few seconds, with a bound |
See Errors for the full list.