Where a price comes from
Three independent price paths, what each one covers, and how to tell how stale a number is.
There is no single price in this system. Three paths run independently, from different vendors, on different schedules, covering different things. Most confusion about a price is really confusion about which path produced it.
The two paths
| Path | Covers | Vendor | Lands in |
|---|---|---|---|
| Card headline | Every card, one representative number | Scryfall | cards.price_usd |
| Printing prices | Every printing and finish, with history | MTGJSON | printing_prices_current and the daily / weekly series |
A fourth thing sits on top of the second.
card_prices rolls the printing prices back up
to the card, taking the cheapest current TCGplayer retail price across a card's
normal-finish printings. It is calculated here, from data already imported.
Why a card has two headline prices
cards.price_usd comes from Scryfall. card_prices.cheapest_usd comes from
TCGplayer via MTGJSON, rolled up. Different vendors and different paths, so they
routinely differ by a few cents and occasionally by more.
Which one you see depends on where you are looking. Search, deck pages, precon pages and the AI read the rolled-up cheapest. The deck builder and collection read the Scryfall headline. A card showing one number in a search result and another elsewhere is showing you two answers to slightly different questions.
The distinction that matters when reading them:
- Scryfall's headline is a representative price for the card.
- The rolled-up cheapest is the cheapest normal-finish printing, which for a card with a dozen printings is a meaningfully different number.
Why history is TCGplayer only
printing_prices_current carries several vendors: TCGplayer, Card Kingdom,
Cardmarket, Cardhoarder and Manapool, because MTGJSON's feed carries them.
The daily and weekly series carry TCGplayer alone. A chart needs one consistent vendor over time. Mixing vendors would produce a line that moves when the cheapest vendor changes, which looks like a price movement and is not one. Anything you see plotted over time is TCGplayer retail, in USD.
Cardmarket prices are in EUR. The currency is recorded per row, which matters if
you read printing_prices_current directly.
Sealed products are not priced
The catalogue is real: 4,011 products, seeded from MTGJSON by pnpm seed:sealed.
The prices are not. No job writes sealed_products.price_usd, and the values
still in that column were written on 2026-03-26 by a vendor since removed.
MTGJSON's price feed is keyed by card and carries no sealed identifiers, and TCGplayer's pricing API is closed to new developers, so there is no source to switch to. The collection view shows each sealed price with the date it was captured, or "unpriced" where there is none.
How stale is it
Every price column has a companion timestamp, and the timestamp is the answer:
cards.prices_updated_at. When the Scryfall headline was last refreshed.card_prices.updated_at. When the rolled-up cheapest was last rebuilt.printing_prices_current.price_date. The day that quote is from.sealed_products.prices_updated_at. When that price was captured. Nothing refreshes it.
Read the timestamp, not the schedule. The card path is designed to refresh daily and the printing path runs with the MTGJSON ingest, but a job that has not run is indistinguishable from one that has, except by its timestamp. The gap between intent and fact is exactly why these columns exist, and it is not hypothetical: the card headline path has gone weeks without a refresh while every surface kept serving its numbers without complaint.
Nothing here is a live quote. Every price you see was imported earlier, and no surface calls a vendor at request time. Treat every number as "as of" its timestamp. Where the exact figure matters, the buy links go to the vendor's own page.
What refreshes what
The data model records, per column, the job that owns it. If a price looks wrong, that field names the thing to re-run. The schedule tells you what was intended; the owning job is what actually writes.