Collection tracking
A collection tracker is a pricing problem wearing a catalogue costume
Build a collection tracker across eight games with per-printing identity, both markets priced, and a valuation your users can actually act on.
The problem
Where this project usually goes wrong
The catalogue half of a collection tracker looks like the hard part and is not. Users add cards, you store ids, you render images. The part that decides whether people keep using it is the total at the top of the screen.
That total is where most trackers quietly fall apart. A user owns a specific printing — this set, this collector number, this finish — and if your data model collapses those into one card record, you show the same value for a bulk reverse holo as for the version that is worth two hundred times more. Users spot it immediately and stop trusting every other number you display.
The second failure is currency. A European user shown a dollar figure converted at today's rate is being told a number that does not exist in their market, because European and American prices diverge for reasons that have nothing to do with the exchange rate.
What it needs
The fields that decide it
- Per-printing identity
- Every finish, language and promo treatment is its own row with its own price, so a user's specific copy is the thing being valued.
- prices[] by source
- European and American quotes on the same object, each in its native currency, so you show the user the market they are actually in.
- listType: buylist
- What the collection would fetch if sold, which is the honest companion to what it would cost to rebuild.
- updatedAt on every quote
- Lets you show how fresh a valuation is rather than implying every number is live.
- Bulk export
- Seed the whole catalogue once instead of paginating it, so search and autocomplete are local and instant.
In practice
The two queries that matter most
Written for apps where people catalogue what they own. Both work the same way for every game we carry.
Value a shelf of cards in one round trip
{
cards(filter: { ids: ["mtg_f6555d1f-d4c", "pkm_a91c2f30-77b"] }) {
nodes {
id
name
set { name code }
collectorNumber
prices {
source
listType
currency
market
updatedAt
}
}
}
}One request for the whole shelf, with both markets and both sides of each. Looping per card is the single most common reason a tracker feels slow and burns credits.
Seed the catalogue, then sync only what moved
# Once, at setup
curl -X POST "https://api.tcggraph.com/v1/bulk/exports" \
-d game=pokemon -H "Authorization: Bearer $TCGGRAPH_KEY"
# Then daily, only what changed
curl -G "https://api.tcggraph.com/v1/cards" \
-d game=pokemon \
-d updatedSince=2026-09-07 \
-H "Authorization: Bearer $TCGGRAPH_KEY"Seeding by paginating /v1/cards costs orders of magnitude more credits and takes far longer. Export once, then track updatedAt.
Avoid these
Three mistakes that are expensive to undo
Each of these is cheap to get right at the start and painful to retrofit once you have user data shaped the wrong way.
Storing a card name instead of a printing id
Names are not unique across sets, reprints or languages, and a user who owns the expensive printing will not accept being shown the cheap one's price. Store the printing id; render the name.
Valuing everything at retail
A collection marked at retail is a number the owner can never realise. Show both, or at minimum label clearly which one you are showing.
Converting one market's price into another currency
An FX conversion of an American price is not a European price. Ask for the market your user is in and show it natively.
Questions
Collection tracker, answered
Also built on this
Other projects, same catalogue
Ship your card app this weekend
$19 a month for 25,000 credits, no sales call and no contract. Pick a plan and your first query runs in under a minute.