Skip to content
· 8 min read

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.

tutorialbulk dataapideveloper tools

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 calls

At 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:

Terminal window
curl "https://api.tcgapi.dev/v1/sets/{set_id}/prices" \
-H "X-API-Key: YOUR_API_KEY"

Response:

{
"data": [
{
"card_id": 12345,
"name": "Charizard ex",
"number": "125",
"printing": "Normal",
"market_price": 12.45,
"low_price": 10.99,
"price_change_24h": 2.89,
"price_change_7d": -6.04
},
{
"card_id": 12345,
"name": "Charizard ex",
"number": "125",
"printing": "Holofoil",
"market_price": 45.99,
"low_price": 42.00,
"price_change_24h": 2.68,
"price_change_7d": 8.24
},
// ... one row per card per printing
],
"meta": {
"total": 198,
"set": "Obsidian Flames"
}
}

One request = every card in the set with per-printing prices.

Bulk Cards for a Full Game

There is no single game-wide card endpoint — you walk the game’s sets and page through each one:

Terminal window
# 1. List every set for the game
curl "https://api.tcgapi.dev/v1/games/pokemon/sets?per_page=100" \
-H "X-API-Key: YOUR_API_KEY"
# 2. Page through the cards in a set, using its numeric id
curl "https://api.tcgapi.dev/v1/sets/1234/cards?per_page=100&page=1" \
-H "X-API-Key: YOUR_API_KEY"

Note that set endpoints take the numeric set id, not the slug. With 100 cards per page, all of Pokemon (~30K cards) lands in ~300 requests plus a couple of set-listing calls.

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: { 'X-API-Key': API_KEY } }
);
const data = await res.json();
allSets.push(...data.data);
if (!data.meta.has_more) break;
page++;
}
return allSets;
}
async function fetchSetPrices(setId) {
const res = await fetch(
`${BASE}/sets/${setId}/prices`,
{ headers: { 'X-API-Key': API_KEY } }
);
return (await res.json()).data;
}
// Build the database
async 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.id);
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}/prices/top-movers?game=${game}&direction=up&period=24h&limit=50`,
{ headers: { 'X-API-Key': API_KEY } }
);
const movers = await res.json();
// top-movers returns a flat array in the requested `direction`
const updates = movers.data || [];
console.log(`${updates.length} price changes today`);
// Update only changed cards in your database
for (const card of updates) {
await updateLocalDatabase(card.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 CaseInitial LoadDaily UpdateRecommended Plan
Track 1 game (e.g., Pokemon)~300 requests~10-50Starter ($19.99/mo)
Track 3 games~800 requests~30-150Pro ($49.99/mo)
Track all 54 games~5,000 requests~200-500Business ($99.99/mo)
Single card tracking1 per card1 per cardFree 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: { 'X-API-Key': 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: 100 cards per request
const url = `${BASE}/sets/${setId}/cards?per_page=100&page=${page}`;
// Bad: 10x the requests for the same data
const url = `${BASE}/sets/${setId}/cards?per_page=10&page=${page}`;

4. Store Per-Printing Data

Don’t average Normal and Foil prices — they’re completely different markets:

// Good: /sets/{id}/prices already gives one row per card per printing
for (const row of setPrices) {
db.insert({
card_id: row.card_id,
printing: row.printing, // 'Normal', 'Foil', 'Holofoil', ...
market_price: row.market_price,
low_price: row.low_price
});
}

Common Patterns

Price Alert System

async function checkAlerts(watchlist) {
for (const item of watchlist) {
const res = await fetch(`${BASE}/cards/${item.cardId}/prices`, {
headers: { 'X-API-Key': API_KEY }
});
const printings = (await res.json()).data;
const normal = printings.find(p => p.printing === 'Normal');
const price = normal?.market_price;
if (price && price <= item.targetPrice) {
notify(`${normal.card_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}/bulk/cards?ids=${ids}`, {
headers: { 'X-API-Key': API_KEY }
});
const data = await res.json();
for (const card of data.data) {
const normal = (card.prices || []).find(p => p.printing === 'Normal');
totalValue += normal?.market_price || 0;
}
}
return totalValue;
}

Getting Started

  1. Sign up for free — start with 100 req/day
  2. Prototype with the free tier — build your data pipeline
  3. Upgrade when ready — Pro plan ($49.99/mo) unlocks bulk endpoints
  4. 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.