Prices: Cardmarket, TCGplayer and graded
Every card carries a European quote and a North American one, in their native currencies, and a graded series by grading company and grade. This page covers how to pick between them and how to read what each reports.
Two markets, one card
Trading cards do not have a price. They have a price in a market. A Pokémon card that settles at $80 on TCGplayer can sit at €55 on Cardmarket, and the gap is not a conversion artefact — European print runs, distribution and demand genuinely differ from American ones. An API that gives you one number and calls it “the price” is quietly wrong for half the world.
So every card in TCGGraph carries at least two quotes: tcgplayer in USD for North America, and cardmarket in EUR for Europe. Neither is converted from the other. Both are the marketplace’s own numbers, in the marketplace’s own currency.
{
"prices": [
{
"source": "tcgplayer",
"region": "NA",
"finish": "normal",
"currency": "USD",
"market": 1994.99,
"low": 1650.00,
"trend": null,
"avg1": null,
"avg7": null,
"avg30": null,
"basis": null
},
{
"source": "cardmarket",
"region": "EU",
"finish": "normal",
"currency": "EUR",
"market": 1832.38,
"low": 1282.38,
"trend": 1832.38,
"avg1": 1784.13,
"avg7": 1797.50,
"avg30": 1867.76,
"basis": "product-page"
},
{
"source": "cardmarket",
"region": "EU",
"finish": "foil",
"currency": "EUR",
"market": 2617.08,
"low": 2145.00,
"trend": 2617.08,
"avg1": 2588.40,
"avg7": 2601.15,
"avg30": 2640.92,
"basis": "price-guide"
}
],
"gradedPrices": [ ... ],
"priceStatus": { "cardmarket": "priced", "tcgplayer": "priced", "graded": "priced" }
}Choosing a source
Every card always returns every quote it has, so a plain request needs no extra parameters. What the source parameter changes is which quote filtering and sorting run against. Price bounds are read in that source’s currency, so source=cardmarket&minPrice=50 means €50, not $50.
# Sort and filter on the European market
GET /v1/cards?game=pokemon&source=cardmarket&minPrice=50&sort=-price
# region=EU is the same thing, without naming a vendor
GET /v1/cards?game=pokemon®ion=EU&minPrice=50&sort=-priceThe response echoes the decision back as meta.priceSource, which is worth asserting on in tests. Getting a USD sort when you meant a EUR one is the kind of bug that surfaces as a mildly wrong list rather than an error.
{
cards(
filter: { game: POKEMON, source: CARDMARKET, minPrice: 50 }
sort: PRICE_DESC
limit: 20
) {
pageInfo { totalCount priceSource }
nodes {
name
prices {
source
currency
finish
market
trend
avg7
avg30
}
}
}
}What TCGplayer reports
TCGplayer publishes two figures per product and finish that are worth serving: market is its own smoothed sale-price estimate, the number the marketplace shows buyers, and low is the cheapest Near Mint listing at the time of the read. The rolling averages and trend fields are Cardmarket vocabulary and stay null on a TCGplayer row rather than being faked from the two numbers we have. Magic is the one game whose TCGplayer quote is taken from MTGJSON’s daily file; every other game is read once a day from TCGplayer’s daily price publication, which lands around 20:00 UTC.
What Cardmarket reports
Cardmarket exposes more of its own statistics than TCGplayer does, and they are the fields European sellers actually price against. We serve them under stable names rather than passing the marketplace’s vocabulary through unchanged.
| Cardmarket | TCGGraph | Notes |
|---|---|---|
| trend | trend | Cardmarket's own trend price. We also copy it to market. |
| avg | avg30 | Their all-time average is not comparable across reprints; we serve the 30-day window instead. |
| avg1 | avg1 | Mean of yesterday's sales. |
| avg7 | avg7 | Mean of the last seven days. |
| avg30 | avg30 | Mean of the last thirty days. |
| low | low | Cheapest live article in any condition. |
| lowFoil | low | Served on the foil finish row rather than as a separate field. |
| idProduct | externalIds.cardmarketId | Kept so you can deep-link to the product page. |
trend is the one to reach for when repricing. It is Cardmarket’s own smoothed estimate and it is what sellers on the platform anchor to, so a listing generated from avg30 will look mispriced next to the competition even when it is a defensible number. Use avg1 and avg7 to detect movement, not to set a price.
market is the field to read when you want one number and do not care where it came from: it carries trend when the marketplace publishes one, and falls back to the plain average on products too thin to have a trend. When that distinction matters, read trend directly and treat a null as the signal it is.
Every Cardmarket row says where its figures were read, in basis. Cardmarket publishes a daily price guide file, and its short averages can lag the product page by days: on 29 September 2026, 1,559 of 3,995 Pokémon products that had sold since 18 September still carried their 18 September figure as the 1-day average, and Lugia (Unseen Forces 29) stood at a 7-day average of EUR 134.93 in the file against EUR 13.76 on its page. So where our crawl has read a product’s page in the last eight days, the row is that page’s summary — From, Price Trend and the 30-, 7- and 1-day averages as Cardmarket showed them — with basis: "product-page" and updatedAt the moment of the visit, and it is rewritten each time the page is read again. Otherwise it is the file’s, with basis: "price-guide". The crawl reads nearly every product for Pokémon, One Piece, Lorcana and Dragon Ball Super, part of Yu-Gi-Oh! and Star Wars: Unlimited, and not Magic’s.
A Cardmarket product can be sold in two versions, and its page says so with a switch above the prices: Foil on Lorcana, Dragon Ball Super and Star Wars: Unlimited, Reverse Holo on Pokémon. The page as served shows the plain version; switched on it shows the other, whose trend is the file’s foil or reverse holo figure to the cent (Rhyperior, Primal Clash 77: EUR 14.96, with its three averages). The crawl reads both views, so a foil or reverse-holo row is page-based on the same terms as the plain one. A product with no switch is sold in one version only, and the file’s columns for the version that does not exist are either a copy of the real one or the few listings sellers flagged by mistake: a Lorcana Enchanted’s plain columns read EUR 95.00 four times beside a foil trend of EUR 109.76. Once the crawl has read such a page, no row is served for the missing version, so an Enchanted has a foil row and no normal one, and a Yu-Gi-Oh! card whose page has no switch has no foil row.
Condition ladders do not line up
The two marketplaces grade differently, and they do not use the same number of rungs. Cardmarket runs seven grades from Mint down to Poor; TCGplayer runs five. We keep each marketplace’s own grade on its own quote rather than inventing a shared scale that would misrepresent both.
| TCGplayer | Closest Cardmarket | Notes |
|---|---|---|
| NM — Near Mint | NM | The default both marketplaces quote. |
| LP — Lightly Played | EX | Cardmarket's Excellent is the closest grade. |
| MP — Moderately Played | GD | Good. |
| HP — Heavily Played | LP | Light Played, despite the initials. |
| DMG — Damaged | PL / PO | Played or Poor, depending on the listing. |
The full Cardmarket ladder is MT (Mint), NM (Near Mint), EX (Excellent), GD (Good), LP (Light Played), PL (Played), PO (Poor). Note that LP means Lightly Played on TCGplayer and Light Played on Cardmarket, but they sit at different points on their respective ladders — TCGplayer’s is second from the top, Cardmarket’s is fifth. Mapping them onto each other by string equality is the single most common mistake in this area.
Reading the spread
Because both quotes are native, the difference between them is real information rather than rounding. Cross-border sellers use it to decide which market to list in; collectors use it to decide which market to buy from.
type Quote = { source: string; currency: string; market: number };
const EUR_PER_USD = 0.92;
/** Positive means the card is dearer in Europe than in the States. */
function spread(prices: Quote[]) {
const us = prices.find((p) => p.source === "tcgplayer")?.market;
const eu = prices.find((p) => p.source === "cardmarket")?.market;
if (us === undefined || eu === undefined) return null;
return ((eu / EUR_PER_USD - us) / us) * 100;
}Convert only at the point of display, and only with a rate you control. We do not fold a conversion into the stored data, because a price that silently moves when the currency market does is impossible to reconcile against a sale that already happened.
When a card has no Cardmarket price
Some printings carry no cardmarket row, and an empty list on its own cannot tell you whether that is a gap in our data or a fact about the market. So every card says which, on priceStatus.cardmarket. The rule behind it is that a quote is served only when exactly one Cardmarket product can be tied to the printing: a wrong price, quoted confidently, is worse than an honest blank.
GET /v1/prices/swu_shd_028
{
"data": {
"cardId": "swu_shd_028",
"prices": [],
"graded": [],
"priceStatus": { "cardmarket": "ambiguous", "tcgplayer": "ambiguous", "graded": "ambiguous" }
},
"meta": { "priced": false, "graded": false }
}| Status | Meaning |
|---|---|
| priced | A Cardmarket product is identified and quoted. The quote is on prices[]. |
| unquoted | The product is identified but Cardmarket publishes no usable quote for it yet, which is typical of a new release before its first sales. |
| ambiguous | Cardmarket lists several products under this name in the expansion this printing belongs to, and nothing it publishes says which one is this printing. We do not guess. |
| collapsed | Every candidate product under this name is identified as another printing of the same card, so none is left for this one. Those printings carry their quotes; this variant does not. |
| unlisted | The expansion this printing belongs to is on Cardmarket but no product there carries this card's name and number. |
| set_unlisted | Cardmarket has no expansion for this set, or none we could identify with confidence, so nothing in it can be priced. |
| game_unlisted | Cardmarket does not carry this game. |
ambiguous and collapsed are the ones to know about. Cardmarket lists a card’s alternate treatments as separate products with identical names and no collector number, so for Star Wars Unlimited showcase printings, One Piece parallel arts and the like, nothing in what the marketplace publishes says which product is which printing. Rather than pair them by position, which we measured at barely better than chance, the base printing is priced and the variants say so. Where a game does give us something to go on (Pokémon attack names, the Lorcana Enchanted tier; for One Piece the card source’s own product ids, the product each printing shipped in, and the order Cardmarket entered a card’s printings, checked against TCGplayer’s ranking of them; for One Piece DON!! cards, which have no number, the art’s subject and the product it shipped in) the variants are priced too.
When a card has no TCGplayer price
priceStatus.tcgplayer answers the same question for the American market, with the same vocabulary, so a caller who has learnt one has learnt both. TCGplayer does give its products a collector number, which is why its ambiguous is rarer: it is left for the cases where a number is shared by several products (a parallel art, a stamped promo reprint) and neither the product names nor a comparison of the product image against our card images settles which is which. Magic cards answer null here; their TCGplayer quote arrives through MTGJSON and is not subject to this matching.
| Status | Meaning |
|---|---|
| priced | A TCGplayer product is identified and quoted. The quote is on prices[]. |
| unquoted | The product is identified but TCGplayer publishes no market or low price for it yet, which is typical of a new release before its first sales. |
| ambiguous | TCGplayer lists several products under this number in this set and neither their names nor their images settle which one is this printing. We do not guess. |
| unlisted | The set is on TCGplayer but no product there carries this card's number. |
| set_unlisted | TCGplayer has no set matching this one, or none we could identify with confidence, so nothing in it can be priced. |
Graded prices
A slab is a different product from the card inside it. A PSA 10 Base Set Charizard and a played raw copy share a catalog record and nothing else about their price, so the marketplace quotes above never fold graded sales in, and graded copies get a series of their own. Every card carries gradedPrices[]: what a graded copy of that printing sells for, in USD, keyed by grader and grade, refreshed daily from recorded sales and stamped with updatedAt. The same rows come back as graded[] on the price endpoint.
GET /v1/prices/pkm_base1_4
{
"data": {
"cardId": "pkm_base1_4",
"prices": [ ... ],
"graded": [
{ "grader": "raw", "grade": "ungraded", "currency": "USD", "price": 312.50, "salesVolume": 1840, "updatedAt": "2026-09-10T09:24:11.000Z" },
{ "grader": "any", "grade": "7", "currency": "USD", "price": 640.00, "salesVolume": 1840, "updatedAt": "2026-09-10T09:24:11.000Z" },
{ "grader": "any", "grade": "8", "currency": "USD", "price": 951.00, "salesVolume": 1840, "updatedAt": "2026-09-10T09:24:11.000Z" },
{ "grader": "any", "grade": "9", "currency": "USD", "price": 2210.00, "salesVolume": 1840, "updatedAt": "2026-09-10T09:24:11.000Z" },
{ "grader": "psa", "grade": "10", "currency": "USD", "price": 21500.00, "salesVolume": 1840, "updatedAt": "2026-09-10T09:24:11.000Z" }
],
"priceStatus": { "cardmarket": "priced", "tcgplayer": "priced", "graded": "priced" }
},
"meta": { "priced": true, "graded": true }
}Nine slots are published, and the grid is deliberately not a full cross product. The market does not separate a PSA 8 from a BGS 8 closely enough to publish them apart, so the 7, 8, 9 and 9.5 buckets are quoted across grading companies and labelled any — never PSA. The companies are told apart only at 10, where the gap between them is real money. raw is an ungraded copy on the same scale, which is the number to compare a grading fee against. salesVolume is units of the printing sold in the last year across all grades, so a thin market shows as a small number rather than as a confident price.
| grader | grade | Meaning |
|---|---|---|
| raw | ungraded | An ungraded copy. |
| any | 7 | Graded 7 by any grading company. |
| any | 8 | Graded 8 by any grading company. |
| any | 9 | Graded 9 by any grading company. |
| any | 9.5 | Graded 9.5 by any grading company. |
| psa | 10 | PSA 10. |
| bgs | 10 | BGS 10. |
| cgc | 10 | CGC 10. |
| sgc | 10 | SGC 10. |
History works the same way as the marketplace series: name a slot with grader and grade on the history endpoint and the daily series for that slot comes back dense, held forward between moves. On /v1/cards, sort=gradedPrice orders by one slot, PSA 10 unless grader and grade say otherwise. The two parameters go together: one without the other is a 400 that lists the vocabulary.
# One slot's daily series
GET /v1/prices/pkm_base1_4/history?grader=psa&grade=10&from=2026-01-01
# The dearest PSA 10s in a set
GET /v1/cards?game=pokemon&set=base1&sort=-gradedPrice
# The same, sorted on ungraded copies
GET /v1/cards?game=pokemon&set=base1&sort=-gradedPrice&grader=raw&grade=ungraded{
card(id: "pkm_base1_4") {
name
gradedPrices(grader: PSA, grade: G10) { price currency salesVolume updatedAt }
priceStatus { graded }
}
}The same honesty rule applies as for Cardmarket, and it bites harder here: a card’s first edition, shadowless and unlimited printings are separate graded products under one collector number, and quoting the unlimited card at the first edition’s price would be wrong by a factor of ten. So a printing is quoted only when exactly one graded product can be tied to it, and priceStatus.graded says why when none can.
| Status | Meaning |
|---|---|
| priced | This printing is identified in the graded index and quoted. The quotes are on gradedPrices[]. |
| unquoted | The printing is identified in the graded index but no grade has enough recorded sales to quote yet, which is typical of a new release. |
| ambiguous | The graded index lists several products under this name and number in this set and nothing distinguishes which is this printing. We do not guess. |
| collapsed | This printing shares its only candidate in the graded index with another printing of the same card. The base printing carries the quotes; this variant does not. |
| unlisted | The set is in the graded index but no product there carries this card's name and number. |
| set_unlisted | The graded index has no entry for this set, or none we could identify with confidence, so nothing in it can be quoted. |
| game_unlisted | The graded index does not cover this game. |
Finishes
A card is one design; a printing is one physical way it was produced, and both marketplaces price each printing as its own product. So a foil and a nonfoil arrive as separate rows on prices, distinguished by finish, and so do a Poké Ball pattern, a Prerelease stamp or a Shadowless print. The vocabulary is shared across sources and games: a Cardmarket row and a TCGplayer row for the same physical card carry the same finish, so a holo card is foil on both, never normal on one and foil on the other.
The list is closed. These are the only values a row is ever filed under; a marketplace subtype we do not recognise is left unpriced and logged rather than passed through, so a client that whitelists these values drops nothing. Adding a value is an API change and appears in the changelog first.
| finish | label | kind | seen on |
|---|---|---|---|
| normal | Normal | surface | all |
| foil | Holofoil | surface | all |
| etched | Etched foil | surface | Magic |
| reverse-holo | Reverse holo | surface | Pokémon |
| cosmos-holo | Cosmos holo | surface | Pokémon |
| cracked-ice | Cracked Ice holo | surface | Pokémon |
| pokeball | Poké Ball pattern | pattern | Pokémon |
| masterball | Master Ball pattern | pattern | Pokémon |
| energy-symbol | Energy symbol pattern | pattern | Pokémon |
| duskball | Dusk Ball pattern | pattern | Pokémon |
| loveball | Love Ball pattern | pattern | Pokémon |
| friendball | Friend Ball pattern | pattern | Pokémon |
| quickball | Quick Ball pattern | pattern | Pokémon |
| team-rocket | Team Rocket pattern | pattern | Pokémon |
| staff | Staff stamp | stamp | Pokémon |
| prerelease | Prerelease stamp | stamp | Pokémon |
| league | League stamp | stamp | Pokémon |
| pokemon-center | Pokémon Center stamp | stamp | Pokémon |
| 1st-edition | 1st Edition | edition | Pokémon, Yu-Gi-Oh! |
| 1st-edition-foil | 1st Edition holofoil | edition | Pokémon |
| shadowless | Shadowless | edition | Pokémon |
| limited | Limited Edition | edition | Yu-Gi-Oh! |
kind says how a printing differs from the plain one, for a picker that wants to group them: surface is how the card face is treated; pattern is a foil pattern pressed across the whole card; stamp is a mark added for an event; edition is a print run of the same card. A game only ever carries the finishes its marketplaces publish, so a Pokémon card can have four TCGplayer rows where a Lorcana card has two. 1st-edition and shadowless are editions rather than surfaces, but they are how the marketplaces separate the products and they price very differently, so they are finishes here too: Base Set Charizard is 1st-edition-foil, shadowless and foil (Unlimited), three rows, three prices.
Printings
prices is flat: one row per source and finish. printings[] is the same quotes regrouped by finish, one entry per physical printing, each with a human label, its kind, the picture that shows it and the marketplace products it was read from. Plain card first, then surfaces, patterns, stamps and editions.
{
"id": "pkm_sv8pt5_1",
"name": "Exeggcute",
"prices": [ ...one row per source and finish, flat... ],
"printings": [
{
"key": "normal",
"label": "Normal",
"kind": "surface",
"images": { "small": "...", "normal": "...", "large": "..." },
"imageFrom": "card",
"externalIds": { "cardmarketId": 802921, "tcgplayerId": 611845 },
"prices": [
{ "source": "cardmarket", "finish": "normal", "currency": "EUR", "trend": 0.05, ... },
{ "source": "tcgplayer", "finish": "normal", "currency": "USD", "market": 0.09, ... }
],
"cardId": "pkm_sv8pt5_1"
},
{ "key": "reverse-holo", "label": "Reverse holo", "kind": "surface", "imageFrom": "card", ... },
{
"key": "pokeball",
"label": "Poké Ball pattern",
"kind": "pattern",
"images": { "small": "https://cards.tcggraph.io/sv/pkm_sv8pt5_1/pokeball/small.webp", ... },
"imageFrom": "printing",
"externalIds": { "cardmarketId": null, "tcgplayerId": 616463 },
"prices": [ { "source": "tcgplayer", "finish": "pokeball", "currency": "USD", "market": 1.62, ... } ],
"cardId": "pkm_sv8pt5_1"
},
{ "key": "masterball", "label": "Master Ball pattern", "kind": "pattern", "imageFrom": "printing", ... }
]
}keyis thefinishvalue and the handle to key a user’s selection on. It does not change when a marketplace product is re-resolved; onlyexternalIdswould. A reprint sold on its own is keyed by its card id, below.imagesis the printing’s own picture where a marketplace shows one — a pattern and a stamp look different, and a picker should show the art — and the card’s picture otherwise.imageFromsays which:printingorcard. Variant pictures are served from our CDN, like the card’s; one not yet mirrored, or whose product has no photograph, shows the card’s.- After the card’s own finishes come its stamped and holo reprints that are sold as products of their own (Pokémon):
pkm_sv3_130, Obsidian Flames’ Umbreon, lists the GameStop, EB Games and “Obsidian Flames Stamped” exclusives and Trick or Trade 2024, eachkind: "stamp"; Prize Pack, Battle Academy and League & Championship copies and Cosmos or Cracked Ice holos are listed the same way. For these,keyis the reprint’s card id (a stamp is not one of the closed finish values),externalIdsandpricesare the reprint’s, and itspricesmay span finishes, each quote naming its own. The reprint is a card of its own too, withbaseCardIdpointing back. cardIdis the cardpricesare kept on: the id to ask/v1/prices/{cardId}, its/historyand its/salesfor. The card’s own id for its own finishes (the English twin’s underpricesFrom: "englishTwin", thepricesTwinid under"localizedTwin"), the reprint’s for a reprint.externalIdsare the products this printing is read from: the card’s own for the plain finishes, the variant’s own product for a pattern or stamp,nullwhere a marketplace has no product for it. A stamp or treatment Cardmarket lists apart (Staff, Pokémon Center, a Cosmos holo, a store exclusive) is a product with the same name as the plain one, told apart only by the caption printed on its picture: we read that caption, and bind the printing when exactly one product’s caption names it. Poké Ball, Master Ball and the other pattern printings are products too, in an “Additionals” expansion beside the set, with no caption: the pattern is the only difference. Where a card has two patterns and both marketplaces price one at least twice the other (a Master Ball sells for 8 to 18 times its Poké Ball), the dearer product is bound to the dearer printing and quoted from Cardmarket’s reverse holo columns, which is how sellers list them. Pairs priced closer than that (Ascended Heroes’ Energy symbol against its ball) have no Cardmarket row. Shadowless printings are bound but not quoted, because Cardmarket’s Shadowless products take 1st Edition copies too and their guide does not describe either. On a localized printing withpricesFrom: "englishTwin",cardmarketIdis the English product where that product is where the localized copies sell (French, German and every language Cardmarket files under one product; Japanese One Piece in the starter decks Cardmarket has no Japanese edition of), andnullwhere Cardmarket sells the language as products of its own and this printing’s has not been identified: the quotes are then the English twin’s, and the twin’s product is on the cardenglishIdnames.- On GraphQL the same array is
printings { key label kind imageFrom cardId images { normal } prices { source finish market } }.
Languages and sealed
- A non-English printing is its own card record with its own image and
printedName, linked to the English printing throughenglishId. Where the marketplaces sell the localized printing as the same product as the English one, which is the case for French, German, Spanish and Italian, thepricesrows on the localized card are the English twin’s and the row says so withpricesFrom: "englishTwin". Where a marketplace lists the localized printing as a product of its own, the row carries that product’s quotes and sayspricesFrom: "own": Japanese One Piece on Cardmarket, whose(Japanese),(Non-English)and(Asia Region Legal)expansions are the Japanese edition. A Japanese printing Cardmarket does not list staysenglishTwin. The other way round, an English One Piece row for a promo no marketplace sells in English (a Japan-only event or magazine promo whose English card exists only in the catalogue) carries its Japanese twin’s Cardmarket quotes and sayspricesFrom: "localizedTwin", withpricesTwin: { id, language: "ja" }naming whose they are and in what language: a figure for Japanese copies, never presented as an English one. It is set only where neither marketplace lists the English printing and exactly one other-language printing of the same set and number has quotes of its own. The per-language figures live inlanguagePrices, below. Cards endpoints return English rows unless?language=asks for a language orall, and?name=matches the printed name as well as the English one:/v1/cards?game=pokemon&language=fr&name=dracaufeu. - Some localized printings are named by their source without a scan: about 2,500 older French Pokémon cards (the Diamond & Pearl sets and the Sun & Moon promos among them), more in German, Spanish and Italian, and Magic printings Scryfall shows only in English. Others no card list carries at all, but Cardmarket sells copies of in the language on the English card’s own product: 1,048 French Pokémon cards (Prize Packs, World Championship decks, McDonald’s, Jumbo and Battle Academy among them) and 425 French One Piece cards (the gold DON!! cards and parallels). Their record shows the English twin’s picture and says
imageFrom: "englishTwin"; it has nohashesand the recogniser never answers it. Its prices work like any other twin’s. - A set printed on its own numbering is its own set, not a twin: Japanese Pokémon sets are
card_setsrows withlanguage: "ja", their cardspkm_ja_<set>_<number>withpricesFrom: "own", priced from TCGplayer’s Pokemon Japan category in USD and from Cardmarket’sJPexpansions in EUR where those exist. Such a card’senglishIdis a cross-reference, not a price twin: the English printing of the same illustration, found by comparing the two images since the numbering differs, set on 93% of the cards and null where no English printing of the illustration exists.sameArtworklists every English printing of it, and on an English card the Japanese ones. The Japanese card keeps its own quotes; a client wanting the English figure requests that id./v1/sets?language=jalists them; a Japanese set code can repeat an English one, so/v1/sets/sv3?game=pokemon&language=jais the Japanese Ruler of the Black Flame and/v1/sets/sv3?game=pokemonthe English Obsidian Flames. Every card’sset.languagesays which kind of set it sits in. - Sealed product is not priced here. These endpoints cover singles; sealed has a different shape and a different set of buyers.
Lows by language
Cardmarket’s published low is the cheapest copy of the product in any language: a One Piece leader whose English copies start at €41 shows a low of €19 if a Japanese copy is listed at that. The published files carry no language at all, so no filter on prices can fix this. TCGGraph prices each language on its own, and every card carries the result as languagePrices[] — the same rows come back as languages[] on the price endpoint, narrowed with ?language=.
GET /v1/prices/op_st03_013_p3
{
"data": {
"cardId": "op_st03_013_p3",
"prices": [
{ "source": "cardmarket", "finish": "normal", "currency": "EUR", "low": 35.00, "trend": 64.48, ... }
],
"graded": [],
"languages": [
{ "source": "cardmarket", "language": "en", "finish": "normal", "currency": "EUR",
"low": 69.00, "lowNm": 70.00, "market": 84.90, "offers": 20, "truncated": true, "truncatedAbove": 92.50,
"conditions": [
{ "condition": "nm", "low": 70.00, "offers": 15 },
{ "condition": "ex", "low": 69.00, "offers": 5 }
],
"updatedAt": "2026-09-11T19:40:22.920Z" },
{ "source": "cardmarket", "language": "fr", "finish": "normal", "currency": "EUR",
"low": 35.00, "lowNm": 37.00, "market": 45.00, "offers": 30, "truncated": true, "truncatedAbove": 92.50,
"conditions": [
{ "condition": "nm", "low": 37.00, "offers": 28 },
{ "condition": "ex", "low": 35.00, "offers": 2 }
],
"updatedAt": "2026-09-11T19:40:22.920Z" }
],
"priceStatus": { "cardmarket": "priced", "tcgplayer": "priced", "graded": "unlisted" }
},
"meta": { "priced": true, "graded": false, "languages": true }
}lowis the cheapest live article in that language in any condition;lowNmthe cheapest Near Mint or Mint one;marketthe median asking price of the cheapest offers.offersis how many articles were counted. All in EUR, stamped withupdatedAt.finishis the same word the card’spricesrow uses for the same printing: an unmarked listing of a holo rare isfoilhere as its guide quote is, a Reverse Holo listing isreverse-holo. FilterlanguagePricesby thefinishon aprintings[]entry and the rows line up.- The rows are built from the fifty cheapest articles across all languages, so
truncated: truemeans dearer copies may exist beyond them, andtruncatedAbovesays where “beyond” starts: the dearest price that was seen. Nothing at or below it was missed. English is always resolved on its own, so an English row withoffers: 0is a fact, not a gap. - Every other language the game is sold in on Cardmarket (French, German, Italian, Spanish, Portuguese, Japanese and Korean for Pokémon; French, Japanese and Korean for One Piece) is read from the same page where its copies are among the fifty, and from a page filtered to it where they are not, on a weekly lane. A language missing from a covered card’s
languagePriceshad no copy listed on a page that showed every offer; one asked for on its own and not found is a row withoffers: 0. For a figure made only of French copies, read thefrrow:prices[].lowandtrendare Cardmarket’s all-language quote. - Coverage is being built out game by game, most-traded first; a card not yet covered has an empty
languagePricesandmeta.languages: false. That is “not covered yet”, never “nothing for sale”. Covered cards are scheduled on one of four lanes by how actively they trade: every 6 hours for a set under three months old and for dearer cards in its first year, daily for cards that move, every three days for the middle, weekly for bulk under 50 cents in sets over a year old. The lanes are what the crawl aims at, not a promise: when a game or a batch of sets joins, rows can run days behind their lane until the crawl catches up. A card customers ask for is moved to a faster lane on the next nightly pass, and every row carriesupdatedAt, the time it was actually read: trust that over the lane.
{
card(id: "op_st03_013_p3") {
name
prices { source finish low trend }
languagePrices(language: EN) {
low lowNm market offers truncated updatedAt
conditions(condition: NM) { low offers }
}
}
}By condition
Each language row carries conditions[]: the cheapest article per grade, best grade first, with how many were counted. The codes are Cardmarket’s own ladder — mt, nm, ex, gd, lp, pl, po — and the table above maps them onto TCGplayer’s. Only grades with a live article appear, so a row without an nm entry has no Near Mint copy among the articles seen; on a truncated row that may be because they were dearer than truncatedAbove, and a row listing only nm is telling you that no played copy is for sale at or below that price. ?condition=nm on the price endpoint and conditions(condition: NM) in GraphQL narrow the list to one grade without changing the row’s own low, lowNm and market.
Realized sales
Asking prices are what sellers hope for. Cardmarket also publishes what copies actually sold for: its own chart of the average sale price on each day a copy changed hands, and we keep every point of it. This is a different thing from the price guide’s trend, which is smoothed, and from low, which is an ask. Every language and condition together, as Cardmarket publishes it: a page filtered to one language shows the same chart. The chart follows the product’s version switch, though, so on a product sold in two versions the series is the plain version’s, and finish=foil or finish=reverse-holo reads the other’s, from our first read of that view.
GET /v1/prices/op_st03_013_p3/sales?days=90
{
"data": {
"cardId": "op_st03_013_p3",
"source": "cardmarket",
"currency": "EUR",
"sales": [
{ "day": "2026-06-25", "avgSell": 65.00 },
...
{ "day": "2026-07-28", "avgSell": 30.00 },
...
{ "day": "2026-09-05", "avgSell": 69.97 },
{ "day": "2026-09-06", "avgSell": 75.00 },
{ "day": "2026-09-10", "avgSell": 37.99 }
]
},
"meta": {
"days": 90,
"count": 30,
"note": "One point per day copies sold; days without a sale are absent. Every language and condition together."
}
}- One point per day copies sold; a day with no sale has no point, so gaps are quiet days, not missing data.
- Cardmarket’s chart holds about the last 30 days on which a copy sold: roughly a month for a card that sells every day, months or years for one that rarely does. Our series for a card begins that far before its first read and grows with every read after, so it lengthens on its own.
- Cardmarket offers nothing older, but the Wayback Machine kept copies of its product pages from 2018 to 2023, each with the chart as it stood that day. We read those for Pokémon, Yu-Gi-Oh!, Dragon Ball Super and One Piece. English cards with sales before 2024: Yu-Gi-Oh! 4,877, Pokémon 3,619 (Base Set Charizard 520 days), Dragon Ball Super 2,463, One Piece 25. Between the last copy and our first read there is a gap, of years for most cards; the series does not fill it.
- Billed as
history, like the daily price series.daysdefaults to 90 and caps at 7305 (twenty years).finishreads a printing Cardmarket sells as its own product, such asmasterballorstaff, or the other version of a two-version product,foilorreverse-holo.
{
card(id: "op_st03_013_p3") {
name
sales(days: 90) { currency sales { day avgSell } }
}
}Freshness
Both sources are refreshed daily and every quote is stamped with updatedAt, so you never have to work out which half of a card’s pricing is stale. A Cardmarket row read from the product page (basis) moves each time the crawl reads the page, and its updatedAt is that read. New sets and cards that trade are read first; how often any one card comes round depends on the crawl’s capacity, so read the timestamp rather than assuming a schedule. Daily closes are kept for both markets, which means you can chart a European or an American price series without having collected it yourself; the TCGplayer series for the non-Magic games reaches back to February 2024, replayed from the marketplace’s own daily archive rather than from the day we started reading it. A card joined after that archive closed in September 2026 — a Poké Ball or Master Ball pattern, a stamped copy, a linked reprint, a Japanese Pokémon card — carries up to a year instead, from TCGplayer’s own product chart: weekly until 90 days back, every three days until 30, then daily. A product TCGplayer only began to chart recently starts where its chart does. See the REST reference for the history endpoint, or the Cardmarket overview for what the European coverage includes game by game.