Skip to content
Prepaid, no subscription trap. Top up a balance and we draw the monthly fee from it.
tcggraph

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[]
{
  "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&region=EU&minPrice=50&sort=-price

The 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.

CardmarketTCGGraphNotes
trendtrendCardmarket's own trend price. We also copy it to market.
avgavg30Their all-time average is not comparable across reprints; we serve the 30-day window instead.
avg1avg1Mean of yesterday's sales.
avg7avg7Mean of the last seven days.
avg30avg30Mean of the last thirty days.
lowlowCheapest live article in any condition.
lowFoillowServed on the foil finish row rather than as a separate field.
idProductexternalIds.cardmarketIdKept 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.

TCGplayerClosest CardmarketNotes
NM — Near MintNMThe default both marketplaces quote.
LP — Lightly PlayedEXCardmarket's Excellent is the closest grade.
MP — Moderately PlayedGDGood.
HP — Heavily PlayedLPLight Played, despite the initials.
DMG — DamagedPL / POPlayed 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.

an unpriced card
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 }
}
StatusMeaning
pricedA Cardmarket product is identified and quoted. The quote is on prices[].
unquotedThe product is identified but Cardmarket publishes no usable quote for it yet, which is typical of a new release before its first sales.
ambiguousCardmarket 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.
collapsedEvery 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.
unlistedThe expansion this printing belongs to is on Cardmarket but no product there carries this card's name and number.
set_unlistedCardmarket has no expansion for this set, or none we could identify with confidence, so nothing in it can be priced.
game_unlistedCardmarket 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.

StatusMeaning
pricedA TCGplayer product is identified and quoted. The quote is on prices[].
unquotedThe 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.
ambiguousTCGplayer 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.
unlistedThe set is on TCGplayer but no product there carries this card's number.
set_unlistedTCGplayer 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.

graded[]
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.

gradergradeMeaning
rawungradedAn ungraded copy.
any7Graded 7 by any grading company.
any8Graded 8 by any grading company.
any9Graded 9 by any grading company.
any9.5Graded 9.5 by any grading company.
psa10PSA 10.
bgs10BGS 10.
cgc10CGC 10.
sgc10SGC 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.

StatusMeaning
pricedThis printing is identified in the graded index and quoted. The quotes are on gradedPrices[].
unquotedThe printing is identified in the graded index but no grade has enough recorded sales to quote yet, which is typical of a new release.
ambiguousThe graded index lists several products under this name and number in this set and nothing distinguishes which is this printing. We do not guess.
collapsedThis 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.
unlistedThe set is in the graded index but no product there carries this card's name and number.
set_unlistedThe graded index has no entry for this set, or none we could identify with confidence, so nothing in it can be quoted.
game_unlistedThe 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.

finishlabelkindseen on
normalNormalsurfaceall
foilHolofoilsurfaceall
etchedEtched foilsurfaceMagic
reverse-holoReverse holosurfacePokémon
cosmos-holoCosmos holosurfacePokémon
cracked-iceCracked Ice holosurfacePokémon
pokeballPoké Ball patternpatternPokémon
masterballMaster Ball patternpatternPokémon
energy-symbolEnergy symbol patternpatternPokémon
duskballDusk Ball patternpatternPokémon
loveballLove Ball patternpatternPokémon
friendballFriend Ball patternpatternPokémon
quickballQuick Ball patternpatternPokémon
team-rocketTeam Rocket patternpatternPokémon
staffStaff stampstampPokémon
prereleasePrerelease stampstampPokémon
leagueLeague stampstampPokémon
pokemon-centerPokémon Center stampstampPokémon
1st-edition1st EditioneditionPokémon, Yu-Gi-Oh!
1st-edition-foil1st Edition holofoileditionPokémon
shadowlessShadowlesseditionPokémon
limitedLimited EditioneditionYu-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.

GET /v1/cards/pkm_sv8pt5_1
{
  "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", ... }
  ]
}
  • key is the finish value and the handle to key a user’s selection on. It does not change when a marketplace product is re-resolved; only externalIds would. A reprint sold on its own is keyed by its card id, below.
  • images is 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. imageFrom says which: printing or card. 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, each kind: "stamp"; Prize Pack, Battle Academy and League & Championship copies and Cosmos or Cracked Ice holos are listed the same way. For these, key is the reprint’s card id (a stamp is not one of the closed finish values), externalIds and prices are the reprint’s, and its prices may span finishes, each quote naming its own. The reprint is a card of its own too, with baseCardId pointing back.
  • cardId is the card prices are kept on: the id to ask /v1/prices/{cardId}, its /history and its /sales for. The card’s own id for its own finishes (the English twin’s under pricesFrom: "englishTwin", the pricesTwin id under "localizedTwin"), the reprint’s for a reprint.
  • externalIds are 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, null where 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 with pricesFrom: "englishTwin", cardmarketId is 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), and null where 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 card englishId names.
  • 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 through englishId. 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, the prices rows on the localized card are the English twin’s and the row says so with pricesFrom: "englishTwin". Where a marketplace lists the localized printing as a product of its own, the row carries that product’s quotes and says pricesFrom: "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 stays englishTwin. 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 says pricesFrom: "localizedTwin", with pricesTwin: { 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 in languagePrices, below. Cards endpoints return English rows unless ?language= asks for a language or all, 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 no hashes and 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_sets rows with language: "ja", their cards pkm_ja_<set>_<number> with pricesFrom: "own", priced from TCGplayer’s Pokemon Japan category in USD and from Cardmarket’s JP expansions in EUR where those exist. Such a card’s englishId is 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. sameArtwork lists 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=ja lists them; a Japanese set code can repeat an English one, so /v1/sets/sv3?game=pokemon&language=ja is the Japanese Ruler of the Black Flame and /v1/sets/sv3?game=pokemon the English Obsidian Flames. Every card’s set.language says 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=.

languages[]
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 }
}
  • low is the cheapest live article in that language in any condition; lowNm the cheapest Near Mint or Mint one; market the median asking price of the cheapest offers. offers is how many articles were counted. All in EUR, stamped with updatedAt.
  • finish is the same word the card’s prices row uses for the same printing: an unmarked listing of a holo rare is foil here as its guide quote is, a Reverse Holo listing is reverse-holo. Filter languagePrices by the finish on a printings[] entry and the rows line up.
  • The rows are built from the fifty cheapest articles across all languages, so truncated: true means dearer copies may exist beyond them, and truncatedAbove says 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 with offers: 0 is 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 languagePrices had no copy listed on a page that showed every offer; one asked for on its own and not found is a row with offers: 0. For a figure made only of French copies, read the fr row: prices[].low and trend are Cardmarket’s all-language quote.
  • Coverage is being built out game by game, most-traded first; a card not yet covered has an empty languagePrices and meta.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 carries updatedAt, 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.

sales
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. days defaults to 90 and caps at 7305 (twenty years). finish reads a printing Cardmarket sells as its own product, such as masterball or staff, or the other version of a two-version product, foil or reverse-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.