Build a Pokemon Price Tracker in 15 Minutes
Step-by-step tutorial to build a Pokemon card price tracker using TCG API and JavaScript. Track prices, monitor changes, and find deals.
Want to track Pokemon card prices programmatically? In this tutorial, you’ll build a price tracker that monitors your favorite cards and alerts you to price changes — all in about 15 minutes.
We’ll use TCG API for the pricing data and plain JavaScript (Node.js). No frameworks, no build tools, no complexity.
What You’ll Build
A Node.js script that:
- Searches for Pokemon cards by name
- Fetches current market prices (Normal and Foil)
- Tracks 24h, 7d, and 30d price changes
- Saves a watchlist to a JSON file
- Flags cards with significant price movements
Prerequisites
- Node.js 18+ installed
- A free TCG API key (sign up here)
Step 1: Get Your API Key
- Create an account at tcgapi.dev/signup
- Log in to your dashboard
- Create a new API key
- Copy the key — you’ll need it in a moment
The free tier gives you 100 requests per day, which is plenty for tracking a personal collection.
Step 2: Search for Cards
Let’s start by searching for a card. Create a file called tracker.js:
const API_KEY = 'your-api-key-here';const BASE_URL = 'https://api.tcgapi.dev/v1';
async function searchCards(query, game = 'pokemon') { const url = `${BASE_URL}/search?q=${encodeURIComponent(query)}&game=${game}&per_page=5`; const response = await fetch(url, { headers: { 'X-API-Key': API_KEY } }); const data = await response.json(); return data.data;}
// Try it outconst results = await searchCards('charizard ex');// Search returns a flat row per card — `printing` and `market_price` live on the cardresults.forEach(card => { console.log(`${card.name} (${card.set_name})`); console.log(` ${card.printing}: $${card.market_price ?? 'N/A'}`);});Run it:
node tracker.jsYou should see something like:
Charizard ex (Obsidian Flames) Normal: $12.45 Foil: $45.99Charizard ex (Paldea Evolved) Normal: $8.75 Foil: $32.50Step 3: Build the Watchlist
Now let’s add a watchlist system that tracks cards you care about:
const fs = require('fs');
const WATCHLIST_FILE = './watchlist.json';
function loadWatchlist() { try { return JSON.parse(fs.readFileSync(WATCHLIST_FILE, 'utf8')); } catch { return { cards: [], lastUpdated: null }; }}
function saveWatchlist(watchlist) { watchlist.lastUpdated = new Date().toISOString(); fs.writeFileSync(WATCHLIST_FILE, JSON.stringify(watchlist, null, 2));}
async function addToWatchlist(cardId) { // Card row and prices are two calls: /cards/{id} has no prices field. const [cardRes, priceRes] = await Promise.all([ fetch(`${BASE_URL}/cards/${cardId}`, { headers: { 'X-API-Key': API_KEY } }), fetch(`${BASE_URL}/cards/${cardId}/prices`, { headers: { 'X-API-Key': API_KEY } }) ]); const card = await cardRes.json(); const prices = (await priceRes.json()).data;
const watchlist = loadWatchlist(); const existing = watchlist.cards.findIndex(c => c.id === cardId);
const entry = { id: card.data.id, name: card.data.name, set: card.data.set_name, prices, addedAt: new Date().toISOString() };
if (existing >= 0) { watchlist.cards[existing] = entry; } else { watchlist.cards.push(entry); }
saveWatchlist(watchlist); console.log(`Added: ${entry.name} (${entry.set})`);}Step 4: Price Change Detection
The killer feature — find cards that are moving:
// top-movers returns a flat array — ask for gainers or losers via `direction`.// `price_change` is the percentage move over the requested period.async function fetchMovers(game, direction) { const url = `${BASE_URL}/prices/top-movers?game=${game}&direction=${direction}&period=24h&limit=5`; const response = await fetch(url, { headers: { 'X-API-Key': API_KEY } }); return (await response.json()).data;}
async function checkPriceMovers(game = 'pokemon') { console.log('\n📈 Biggest Pokemon Price Movers (24h)\n');
for (const [label, direction] of [['TOP GAINERS', 'up'], ['TOP LOSERS', 'down']]) { const cards = await fetchMovers(game, direction); if (!cards.length) continue;
console.log(`${label}:`); cards.forEach(card => { const pct = `${card.price_change >= 0 ? '+' : ''}${card.price_change.toFixed(1)}%`; console.log(` ${card.name} — $${card.market_price.toFixed(2)} (${pct})`); }); console.log(''); }}Step 5: Update Watchlist Prices
Refresh prices for all tracked cards:
async function updateWatchlist() { const watchlist = loadWatchlist();
console.log(`\nUpdating ${watchlist.cards.length} tracked cards...\n`);
for (const card of watchlist.cards) { // /cards/{id}/prices returns the printing rows directly as `data` const response = await fetch(`${BASE_URL}/cards/${card.id}/prices`, { headers: { 'X-API-Key': API_KEY } }); const newPrices = (await response.json()).data;
// Compare prices for (const row of newPrices) { const printing = row.printing; const oldPrice = (card.prices || []).find(p => p.printing === printing)?.market_price; const newPrice = row.market_price;
if (oldPrice && newPrice) { const diff = newPrice - oldPrice; const pct = ((diff / oldPrice) * 100).toFixed(1);
if (Math.abs(diff) > 0.50) { const arrow = diff > 0 ? '📈' : '📉'; console.log(`${arrow} ${card.name} (${printing}): $${oldPrice.toFixed(2)} → $${newPrice.toFixed(2)} (${pct}%)`); } } }
card.prices = newPrices; }
saveWatchlist(watchlist); console.log('\nWatchlist updated!');}Step 6: Put It All Together
Add a simple CLI to your script:
const command = process.argv[2];
switch (command) { case 'search': const query = process.argv.slice(3).join(' '); const results = await searchCards(query); results.forEach(card => { console.log(`[${card.id}] ${card.name} (${card.set_name})`); console.log(` ${card.printing}: $${card.market_price ?? 'N/A'}`); }); break;
case 'add': await addToWatchlist(process.argv[3]); break;
case 'update': await updateWatchlist(); break;
case 'movers': await checkPriceMovers(); break;
default: console.log('Usage:'); console.log(' node tracker.js search <card name>'); console.log(' node tracker.js add <card-id>'); console.log(' node tracker.js update'); console.log(' node tracker.js movers');}Now you can:
# Search for cardsnode tracker.js search "pikachu vmax"
# Add a card to your watchlist by IDnode tracker.js add 12345
# Update all watchlist pricesnode tracker.js update
# Check today's biggest moversnode tracker.js moversNext Steps
From here you could:
- Add a cron job to run
updatedaily and log price changes - Send Discord notifications when a tracked card drops below a target price
- Build a web dashboard with a simple HTML page that reads the watchlist JSON
- Track sealed products (booster boxes, ETBs) — they use the same API endpoints
- Expand to other games — just change the
gameparameter (trymagic,yugioh,one-piece-card-game, or any of the 54 supported games)
Full API Reference
- Search Cards — Full-text card search with filters
- Card Data — Individual card details and prices
- Price Movers — Top gainers and losers
- Games List — All supported games
- Rate Limits — Stay within your daily quota
The free tier (100 req/day) is enough to track ~50 cards with daily updates. Need more? Check out paid plans starting at $9.99/mo.
Questions? Join us on Discord — we’re happy to help debug your tracker.
Ready to get started?
Free tier includes 100 requests per day. No credit card required.