How to Get Bulk Card Price Data for Your App
Learn how to efficiently fetch thousands of card prices using TCG API's bulk endpoints. Build price databases, inventory tools, and collection trackers.
If you’re building a price database, inventory management tool, or collection tracker, you need bulk data — not one card at a time. Fetching 50,000+ prices individually would take thousands of API calls and hours of time.
TCG API’s bulk endpoints solve this. Here’s how to efficiently fetch card data at scale.
The Problem with Single-Card Fetching
Say you want prices for every Pokemon card. With individual requests:
200+ sets × ~150 cards per set = 30,000+ API callsAt 100 requests/day on the free tier, that’s 300 days. Even on the Pro plan (10K/day), it’s 3 days of constant API calls. Not practical.
Bulk Endpoints to the Rescue
TCG API’s bulk endpoints let you fetch entire sets or games worth of data in a few requests. Available on Pro ($49.99/mo) and Business ($99.99/mo) plans.
Bulk Card Prices by Set
Fetch all card prices for an entire set in one request:
curl "https://api.tcgapi.dev/v1/sets/{set_slug}/cards/prices" \ -H "Authorization: Bearer YOUR_API_KEY"Response:
{ "data": [ { "card_id": 12345, "name": "Charizard ex", "prices": { "Normal": { "market_price": 12.45, "low_price": 10.99, "change_24h": 0.35, "change_7d": -0.80 }, "Holofoil": { "market_price": 45.99, "low_price": 42.00, "change_24h": 1.20, "change_7d": 3.50 } } }, // ... hundreds more cards ], "meta": { "total": 198, "set": "Obsidian Flames" }}One request = every card in the set with per-printing prices.
Bulk Cards for a Full Game
Fetch paginated card data for an entire game:
# Page through all Pokemon cardscurl "https://api.tcgapi.dev/v1/games/pokemon/cards?per_page=100&page=1" \ -H "Authorization: Bearer YOUR_API_KEY"With 100 cards per page, you can fetch all of Pokemon (~30K cards) in ~300 requests.
Efficient Data Pipeline Strategy
Here’s the approach we recommend for building a local price database:
Initial Load
const API_KEY = 'your-api-key';const BASE = 'https://api.tcgapi.dev/v1';
async function fetchAllSets(game) { let page = 1; let allSets = [];
while (true) { const res = await fetch( `${BASE}/games/${game}/sets?per_page=100&page=${page}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } } ); const data = await res.json(); allSets.push(...data.data);
if (!data.meta.has_more) break; page++; }
return allSets;}
async function fetchSetPrices(setSlug) { const res = await fetch( `${BASE}/sets/${setSlug}/cards/prices`, { headers: { 'Authorization': `Bearer ${API_KEY}` } } ); return (await res.json()).data;}
// Build the databaseasync function initialLoad(game) { const sets = await fetchAllSets(game); console.log(`Found ${sets.length} sets for ${game}`);
const allPrices = []; for (const set of sets) { const prices = await fetchSetPrices(set.slug); allPrices.push(...prices); console.log(` ${set.name}: ${prices.length} cards`);
// Respect rate limits await new Promise(resolve => setTimeout(resolve, 200)); }
return allPrices;}Daily Updates with Price Movers
Once you have the initial data, use the price movers endpoint to get only changed prices:
async function dailyUpdate(game) { // Get cards with price changes in the last 24h const res = await fetch( `${BASE}/games/${game}/prices/movers?period=24h&per_page=100`, { headers: { 'Authorization': `Bearer ${API_KEY}` } } ); const movers = await res.json();
const updates = [ ...(movers.data.gainers || []), ...(movers.data.losers || []) ];
console.log(`${updates.length} price changes today`);
// Update only changed cards in your database for (const card of updates) { await updateLocalDatabase(card.id, card); }}This approach means your daily updates use minimal API calls — only fetching what actually changed.
Request Budget Planning
Here’s how many requests different use cases typically need:
| Use Case | Initial Load | Daily Update | Recommended Plan |
|---|---|---|---|
| Track 1 game (e.g., Pokemon) | ~300 requests | ~10-50 | Starter ($19.99/mo) |
| Track 3 games | ~800 requests | ~30-150 | Pro ($49.99/mo) |
| Track all 89+ games | ~5,000 requests | ~200-500 | Business ($99.99/mo) |
| Single card tracking | 1 per card | 1 per card | Free or Hobby |
Best Practices
1. Cache Aggressively
Prices update at most daily (for top-tier games). No need to re-fetch more than once per day:
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
function isCacheValid(lastFetched) { return Date.now() - lastFetched < CACHE_TTL;}2. Respect Rate Limits
Check the x-ratelimit-remaining header in every response:
async function fetchWithRateLimit(url) { const res = await fetch(url, { headers: { 'Authorization': `Bearer ${API_KEY}` } });
const remaining = parseInt(res.headers.get('x-ratelimit-remaining') || '0'); if (remaining < 10) { console.warn(`Rate limit warning: ${remaining} requests remaining`); }
return res.json();}3. Use Pagination Efficiently
Always use the maximum per_page value (100) to minimize total requests:
// Good: 300 requests for 30K cardsconst url = `${BASE}/games/pokemon/cards?per_page=100&page=${page}`;
// Bad: 3,000 requests for the same dataconst url = `${BASE}/games/pokemon/cards?per_page=10&page=${page}`;4. Store Per-Printing Data
Don’t average Normal and Foil prices — they’re completely different markets:
// Good: store each printing separatelydb.insert({ card_id: card.id, printing: 'Normal', market_price: card.prices.Normal.market_price, low_price: card.prices.Normal.low_price});
db.insert({ card_id: card.id, printing: 'Holofoil', market_price: card.prices.Holofoil.market_price, low_price: card.prices.Holofoil.low_price});Common Patterns
Price Alert System
async function checkAlerts(watchlist) { for (const item of watchlist) { const res = await fetch(`${BASE}/cards/${item.cardId}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } }); const card = await res.json();
const price = card.data.prices?.Normal?.market_price; if (price && price <= item.targetPrice) { notify(`${card.data.name} dropped to $${price} (target: $${item.targetPrice})`); } }}Collection Value Calculator
async function calculateCollectionValue(cardIds) { let totalValue = 0;
// Batch into groups of 100 for bulk fetch for (let i = 0; i < cardIds.length; i += 100) { const batch = cardIds.slice(i, i + 100); const ids = batch.join(',');
const res = await fetch(`${BASE}/cards/bulk?ids=${ids}`, { headers: { 'Authorization': `Bearer ${API_KEY}` } }); const data = await res.json();
for (const card of data.data) { totalValue += card.prices?.Normal?.market_price || 0; } }
return totalValue;}Getting Started
- Sign up for free — start with 100 req/day
- Prototype with the free tier — build your data pipeline
- Upgrade when ready — Pro plan ($49.99/mo) unlocks bulk endpoints
- Check the API docs — full endpoint reference with examples
The bulk endpoints are the most cost-effective way to build a comprehensive card price database. One Pro subscription replaces what would take thousands of dollars in manual data collection.
Building something cool with bulk data? Share it on Discord — we love seeing what developers create.
Ready to get started?
Free tier includes 100 requests per day. No credit card required.