The official JavaScript/TypeScript SDK for the JustTCG API. Access real-time and historical pricing data for the world's most popular Trading Card Games.
- ✅ Modern & Type-Safe: Written entirely in TypeScript for a superior developer experience with static typing and autocomplete.
- 🔐 Versioned API Access: Clean, explicit access to API versions via
client.v1andclient.v2, ensuring your integrations are stable and future-proof. - 🌍 v2 (public beta): Localized pricing across regions, graded cards, cursor pagination with
iterate(), and atoV1Cardsadapter so you can migrate without rewriting your types. See Using v2. - 🧼 Clean Data Models: Raw API responses are automatically transformed into clean, intuitive JavaScript objects (e.g., prices in dollars, camelCase properties).
- 🚨 Robust Error Handling: Predictable, typed errors allow you to gracefully handle API issues like invalid keys or rate limits.
- 🚀 Zero Dependencies: A lightweight package with no production dependencies for fast and secure installation.
- 🤖 Node.js First: Optimized for server-side environments, with support for environment variable authentication out of the box.
npm install justtcg-jsGet your API key from your JustTCG Dashboard. For security, we recommend storing your API key in an environment variable named JUSTTCG_API_KEY.
import { JustTCG } from 'justtcg-js';
async function findTopCards() {
try {
// The client automatically looks for the JUSTTCG_API_KEY environment variable
const client = new JustTCG();
// Or, you can provide options directly in the constructor
// const client = new JustTCG({
// apiKey: 'your_api_key_here',
// debug: true // Enable request logging
// });
console.log(`Searching for the most valuable cards in Disney Lorcana: The First Chapter...`);
const response = await client.v1.cards.get({
game: 'Disney Lorcana',
set: 'the-first-chapter-disney-lorcana',
orderBy: 'price',
order: 'desc',
limit: 10,
});
console.log('\n--- Top 10 Most Valuable Cards ---');
response.data.forEach((card, index) => {
// Find the most expensive variant for display (e.g., the Foil version)
const topVariant = card.variants.sort((a, b) => (b.price ?? 0) - (a.price ?? 0))[0];
const price = topVariant?.price?.toFixed(2) ?? 'N/A';
const printing = topVariant?.printing ? `(${topVariant.printing})` : '';
console.log(`${index + 1}. ${card.name} ${printing} - $${price}`);
});
console.log(`\nAPI requests remaining today: ${response.usage.apiDailyRequestsRemaining}`);
} catch (error) {
console.error('An error occurred:', (error as Error).message);
process.exit(1);
}
}
findTopCards();All successful method calls from the client return a consistent JustTCGApiResponse object. It's crucial to understand its structure.
interface JustTCGApiResponse<T> {
/** The main data payload from the API. The type of T depends on the method called. */
data: T;
/** Pagination metadata, ONLY present on paginated endpoints like `cards.get()` and `sets.list()`. */
pagination?: {
total: number;
limit: number;
offset: number;
hasMore: boolean;
};
/** API usage metadata, included in every successful response. */
usage: {
apiRequestLimit: number;
apiDailyLimit: number;
apiRateLimit: number;
apiRequestsUsed: number;
apiDailyRequestsUsed: number;
apiRequestsRemaining: number;
apiDailyRequestsRemaining: number;
apiPlan: string;
};
/** If the API returns an error (e.g., validation), this field will contain the message. */
error?: string;
/** An error code corresponding to the error message. */
code?: string;
}The client is organized by API version, resource, and method. The structure is always:
client.[version].[resource].[method](params).
Fetches a list of all supported Trading Card Games.
-
Parameters: None.
-
Returns:
Promise<JustTCGApiResponse<Game[]>>
Response data Object (Game):
{
id: string; // The unique identifier for the game (e.g., 'pokemon')
name: string; // The full name of the game (e.g., 'Pokemon')
cards_count: number; // Total number of cards in the game
sets_count: number; // Total number of sets in the game
sealed_count: number; // Number of sealed products in the game
last_updated: number; // Last updated timestamp (Unix seconds)
game_value_usd: number; // Total value of the game's cards (USD)
game_value_change_7d_pct: number; // Percentage change in game value over 7 days
game_value_change_30d_pct: number; // Percentage change in game value over 30 days
game_value_change_90d_pct: number; // Percentage change in game value over 90 days
cards_pos_7d_count: number; // Number of cards with positive price change in 7d
cards_neg_7d_count: number; // Number of cards with negative price change in 7d
sealed_cards_pos_7d_count: number; // Number of sealed products with positive price change in 7d
sealed_cards_neg_7d_count: number; // Number of sealed products with negative price change in 7d
cards_pos_30d_count: number; // Number of cards with positive price change in 30d
cards_neg_30d_count: number; // Number of cards with negative price change in 30d
sealed_cards_pos_30d_count: number; // Number of sealed products with positive price change in 30d
sealed_cards_neg_30d_count: number; // Number of sealed products with negative price change in 30d
cards_pos_90d_count: number; // Number of cards with positive price change in 90d
cards_neg_90d_count: number; // Number of cards with negative price change in 90d
sealed_cards_pos_90d_count: number; // Number of sealed products with positive price change in 90d
sealed_cards_neg_90d_count: number; // Number of sealed products with negative price change in 90d
}Fetches a list of sets, which must be filtered by game.
-
Parameters:
Parameter Type Required Description game string Yes The name of the game to filter sets by (e.g., 'Pokemon'). -
Returns:
Promise<JustTCGApiResponse<Set[]>>(This response includes thepaginationobject).
Response data Object (Set):
{
id: string; // The unique identifier for the set
name: string; // The name of the set (e.g., 'Base Set')
gameId: string; // The ID of the game this set belongs to
game: string; // The name of the game this set belongs to
count: number; // The number of cards in the set
variants_count: number; // The total number of variants in the set
sealed_count: number; // The number of sealed products in the set
release_date: string; // The release date in ISO 8601 format
set_value_usd: number; // Total value of the set's cards (USD)
set_value_change_7d_pct: number; // Percentage change in set value over 7 days
set_value_change_30d_pct: number; // Percentage change in set value over 30 days
set_value_change_90d_pct: number; // Percentage change in set value over 90 days
}A powerful and flexible method to browse, filter, and retrieve a paginated list of cards.
| Parameter | Type | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| tcgplayerId | string | A TCGplayer product ID to look up. | ||||||||||
| tcgplayerSkuId | string | The TCGplayer SKU ID for a specific variant. | ||||||||||
| cardId | string | A JustTCG card ID or UUID to look up. | ||||||||||
| variantId | string | A JustTCG variant ID or UUID to look up. | ||||||||||
| scryfallId | string | The Scryfall ID for the card (if applicable). | ||||||||||
| mtgjsonId | string | The MTGJSON ID for the card (if applicable). | ||||||||||
| query | string | A general search query for the card name. | ||||||||||
| game | string | The name of the game to filter by (e.g., 'Pokemon'). | ||||||||||
| set | string | The id of the set to filter by (e.g., 'the-first-chapter-disney-lorcana'). | ||||||||||
| number | string | The number of the card within the set (e.g., '015'). | ||||||||||
| updated_after | number | Unix timestamp (seconds) to filter results updated after this time. | ||||||||||
| condition | string[] | An array of conditions to filter by. Supports fully spelled names or abbreviations. Full names: "Sealed", "Near Mint", "Lightly Played", "Moderately Played", "Heavily Played", "Damaged". Abbreviations: "S", "NM", "LP", "MP", "HP", "DMG". | ||||||||||
| printing | string[] | An array of print types to filter by (e.g., ['Foil', '1st Edition']). | ||||||||||
| include_price_history | boolean | Option to include price history in the response for matching variants. | ||||||||||
| priceHistoryDuration | '7d' | '30d' | '90d' | '180d' | '1y' | Specify which timeframe to include in the priceHistory array. This parameter is ignored if include_price_history is set to false. | ||||||||||
| include_statistics | '7d' | '30d' | '90d' | '1y' | 'allTime' | Specify which timeframe statistics to include in the response. Defaults to all timeframes. You can provide a comma-separated list (e.g., 7d,30d,1y) to include multiple statistics. | ||||||||||
| include_null_prices | boolean | Option to include cards that currently have null prices. Defaults to 'false'. | ||||||||||
| min_price | number | Filters results to include only cards where the current market price is greater than or equal to the provided value. | ||||||||||
| orderBy | 'price' | '24h' | '7d' | '30d' | '90d' | The field to sort the results by. Default is 'price'. | ||||||||||
| order | 'asc' | 'desc' | The sort order. Default is 'desc'. | ||||||||||
| limit | number | The maximum number of results to return. Default is 20.
|
||||||||||
| offset | number | The number of results to skip for pagination. |
- Returns:
Promise<JustTCGApiResponse<Card[]>>(This response includes thepaginationobject).
Retrieves multiple specific cards and their variants in a single, efficient request. This is ideal for updating inventory prices.
- Parameters: An array of
BatchLookupItemobjects.
| Parameter | Type | Max Object Length | Description | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| items | BatchLookupItem[] |
|
An array of lookup objects. |
BatchLookupItem Object:
You can mix and match different identifier types in a single batch request. The lookup item can define any combination of identifiers and options.
{
tcgplayerId?: string; // A TCGplayer product ID.
tcgplayerSkuId?: string; // The TCGplayer SKU ID for a specific variant.
cardId?: string; // A JustTCG card ID or UUID.
variantId?: string; // A JustTCG variant ID or UUID.
scryfallId?: string; // The Scryfall ID for the card.
mtgjsonId?: string; // The MTGJSON ID for the card.
printing?: string[]; // Optional: Filter by specific print types for this item.
condition?: string[]; // Optional: Filter by specific conditions for this item. Accepts full names and abbreviations.
updated_after?: number; // Optional: Only return items updated after this Unix timestamp (seconds).
include_price_history?: boolean; // Optional: Include price history for matched variants.
include_statistics?: ('7d'|'30d'|'90d'|'1y'|'allTime')[]; // Optional: Specific timeframes for statistics.
}- Returns:
Promise<JustTCGApiResponse<Card[]>>(This response does not include thepaginationobject).
The get() and getByBatch() methods return an array of Card objects. Each card contains an array of its Variant objects.
Card Object:
{
/** The unique slug identifier for the card. Will be renamed to `slug` in v2. */
id: string;
/** The UUID of the card. Recommended for stable identification. */
uuid: string;
/** The name of the card. */
name: string;
/** The game this card belongs to. */
game: string;
/** The set ID this card belongs to. */
set: string;
/** The set name this card belongs to (may be omitted for some responses). */
set_name?: string;
/** The card number within the set. */
number: string | null;
/** The rarity of the card. */
rarity: string | null;
/** The TCGPlayer ID for the card. */
tcgplayerId: string | null;
/** The Scryfall ID for the card, if applicable. */
scryfallId: string | null;
/** The MTGJSON ID for the card, if applicable. */
mtgjsonId: string | null;
/** Additional details about the card. */
details?: string | null;
/** The different variants of the card. */
variants: Variant[];
}Variant Object: (Contains detailed pricing)
{
/** The unique slug identifier for this variant. Will be renamed to `slug` in v2. */
id: string;
/** The UUID of this variant. Recommended for stable identification. */
uuid: string;
/** The condition of the card variant (e.g., Near Mint). */
condition: string;
/** The printing type of the card variant (e.g., Foil, 1st Edition). */
printing: string;
/** The language of the card variant, if applicable. */
language: string | null;
/** The TCGPlayer SKU of the specific variant, if available. */
tcgplayerSkuId?: string;
/** The current price of the card variant in dollars. */
price: number;
/** The last time the price was updated, as an epoch timestamp in seconds. */
lastUpdated: number; // Epoch seconds
/** The percentage change in price over the last 24 hours. */
priceChange24hr?: number | null; // Percentage
// --- 7d stats ---
/** The percentage change in price over the last 7 days. */
priceChange7d?: number | null; // Percentage
/** The average price over the last 7 days. */
avgPrice?: number | null; // Dollars
/** The price history entries over the last 7 days. */
priceHistory?: PriceHistoryEntry[] | null;
minPrice7d?: number | null; // Dollars
maxPrice7d?: number | null; // Dollars
stddevPopPrice7d?: number | null;
covPrice7d?: number | null;
iqrPrice7d?: number | null;
trendSlope7d?: number | null;
priceChangesCount7d?: number | null;
// --- 30d stats ---
priceChange30d?: number | null; // Percentage
avgPrice30d?: number | null; // Dollars
minPrice30d?: number | null; // Dollars
maxPrice30d?: number | null; // Dollars
stddevPopPrice30d?: number | null;
covPrice30d?: number | null;
iqrPrice30d?: number | null;
trendSlope30d?: number | null;
priceChangesCount30d?: number | null;
priceRelativeTo30dRange?: number | null;
// --- 90d stats ---
priceChange90d?: number | null; // Percentage
avgPrice90d?: number | null; // Dollars
minPrice90d?: number | null; // Dollars
maxPrice90d?: number | null; // Dollars
stddevPopPrice90d?: number | null;
covPrice90d?: number | null;
iqrPrice90d?: number | null;
trendSlope90d?: number | null;
priceChangesCount90d?: number | null;
priceRelativeTo90dRange?: number | null;
// --- 1y stats ---
minPrice1y?: number | null;
maxPrice1y?: number | null;
// --- All-time stats ---
minPriceAllTime?: number | null;
minPriceAllTimeDate?: string | null;
maxPriceAllTime?: number | null;
maxPriceAllTimeDate?: string | null;
}PriceHistoryEntry Object:
{
/** Epoch timestamp in seconds. */
t: number;
/** Price in dollars. */
p: number;
}v2 lives alongside v1 on the same client. Reaching for client.v2 never changes how client.v1 behaves — every v1 type, id, and response shape is untouched. Adopt v2 at your own pace, endpoint by endpoint.
import { JustTCG } from 'justtcg-js';
const client = new JustTCG();
// Search — returns the same { data, pagination, usage } envelope you know from v1
const { data } = await client.v2.cards.search('charizard', { game: 'pokemon', limit: 5 });
for (const card of data) {
// markets[0] is the primary market — the drop-in replacement for v1's flat `price`
console.log(card.name, card.variants[0]?.markets[0]?.price);
}| Area | v1 | v2 |
|---|---|---|
| Card id | id is a slug; uuid is the UUID |
id is the UUID; slug holds the old v1 id |
| Pricing | flat price, avgPrice30d, … on the variant |
a markets[] array, one entry per region; stats grouped under markets[].periods |
| Localization | US/NA only | request regions: ['UK', 'US'] — prices are the real local prices, never currency-converted |
| Graded cards | not returned | ordinary variants with type: 'graded' and a grading object |
| Pagination | offset + total |
opaque cursor + Link header; walk it with iterate() |
| Usage | _metadata in the body |
RateLimit-* response headers, surfaced as usage |
| Errors | error/code on the response |
thrown typed exceptions (NotFoundError, RateLimitError, …) |
Nested fields also regroup: game becomes { id, name }, set becomes { id, name }, and the third-party ids move under external_ids (external_ids.tcgplayer, external_ids.scryfall, …).
All methods return { data, pagination?, usage }. pagination is present on list methods (get, search) and absent on the others.
// Browse / filter — with no params, this browses; with q/number it searches
await client.v2.cards.get({ game: 'pokemon', regions: ['US'], limit: 20 });
// Search by name
await client.v2.cards.search('pikachu', { game: 'pokemon' });
// Direct lookup — accepts a v2 UUID or a legacy v1 slug, and unwraps the single card.
// This is the only lookup that accepts graded: 'include'.
await client.v2.cards.retrieve('907005b3-b7bd-5eac-aed9-d20454b7fe8a');
await client.v2.cards.retrieveVariant('e72a67fe-922e-5662-bcfc-e8cb509f8220');
// Batch — snake_case body keys (a v1 payload needs its keys renamed, not sent as-is).
// `regions` applies to every item and is sent once.
await client.v2.cards.getByBatch([{ card_id: '…' }, { tcgplayer_id: '42' }], { regions: ['US'] });iterate() walks the entire result set one card at a time, fetching pages lazily — breaking out of the loop stops the requests, so it's safe to point at a large set. Use iteratePages() when you need the page boundaries or want to watch usage as you go.
for await (const card of client.v2.cards.iterate({ game: 'pokemon', limit: 100 })) {
console.log(card.name);
}
for await (const page of client.v2.cards.iteratePages({ game: 'pokemon' })) {
console.log(page.data.length, 'cards,', page.usage.remaining, 'requests left');
}Request regions in priority order; markets come back in the same order, and markets[0] is the market that min_price and order_by apply to. A region with no local data has price: null — the SDK never falls back to a different currency.
const { data } = await client.v2.cards.search('charizard', {
game: 'pokemon',
regions: ['UK', 'US'],
});
for (const market of data[0].variants[0].markets) {
console.log(market.region, market.price, market.currency); // 'UK' 410.5 'GBP'
}Omitting regions yields a single US market (USD), reproducing v1's pricing. Extra regions and graded: 'include' carry a cost surcharge; exact figures are set at launch.
If your codebase is already typed against the v1 Card, switch the fetch to v2 and run the results through toV1Cards — it reshapes v2 back into the exact v1 shape, so nothing downstream changes.
import { JustTCG, toV1Cards } from 'justtcg-js';
import type { Card } from 'justtcg-js';
const { data } = await client.v2.cards.get({ game: 'pokemon' });
const legacy: Card[] = toV1Cards(data); // flat price, avgPrice, game as a string — all v1 names
// Choose which region flattens into the price fields (defaults to the primary market):
const ukPriced = toV1Cards(data, { region: 'UK' });Two things deliberately don't round-trip: graded variants are dropped unless you pass { includeGraded: true } (v1 was raw-only), and printing loses the " - <Language>" suffix v1 appended for non-English cards (v2 carries language separately). Use toV1Card for a single card and toV1Variant for a single variant.
Note: External-id GET parameters (
tcgplayerId,scryfallId, …) are pending on the v2getendpoint. Until then, use them viagetByBatchor stay onclient.v1for those lookups.
The SDK surfaces errors in two primary ways: thrown exceptions for critical SDK issues and an error property on the response object for API issues.
v2 note: v2 never uses the
errorproperty — it always throws a typed exception on failure. The classes are exported (JustTCGErrorand its subclassesAuthenticationError,ValidationError,RegionNotAvailableError,NotFoundError,RateLimitError,ApiError) so you can branch withinstanceof. Each carries.status,.code, and the raw.problembody;RateLimitErroradds.retryAfter,RegionNotAvailableErroradds.available.import { NotFoundError, RateLimitError } from 'justtcg-js'; try { await client.v2.cards.retrieve(cardId); } catch (error) { if (error instanceof NotFoundError) { /* no such card */ } else if (error instanceof RateLimitError) { console.log(`retry after ${error.retryAfter}s`); } else throw error; }
Critical errors that happen at the SDK level will be thrown as a JavaScript Error. These must be wrapped in a try...catch block.
Common Causes:
- Authentication Failure (401): The API key is missing.
- Invalid Parameter Value: Providing an unknown value for a parameter like
orderBy.
Example:
try {
const client = new JustTCG();
await client.v1.games.list();
} catch (error) {
// This block will catch the error
console.error((error as Error).message); // e.g., "Authentication error: API key is missing."
}If a request is syntactically valid but fails API-level validation (e.g., an invalid lookup parameter), the API will often return a normal response object where the data is empty and the error and code properties are populated.
Common Causes:
- Authentication Failure (401/403): The provided API key is invalid or disabled.
- Not Found (404): The requested endpoint does not exist.
- Rate Limit Exceeded (429): Your application is making too many requests.
- Missing Required Parameters: Calling
client.v1.sets.list()without the requiredgameparameter. - Invalid Lookup Parameter: Providing an unknown value for a parameter like
cardId.
Example:
const client = new JustTCG();
// Missing the required 'game' parameter
const response = await client.v1.sets.list();
if (response.error) {
// This block will execute
console.log(response.error); // "Required query parameter \"game\" is missing"
console.log(response.code); // "INVALID_REQUEST"
} else {
// This data will be empty
console.log(response.data);
}You can find practical, runnable examples in the /examples directory of this repository.
To run an example, first ensure you have set your JUSTTCG_API_KEY environment variable.
# Example: Find the most valuable Lorcana cards from The First Chapter
export JUSTTCG_API_KEY="YOUR_API_KEY_HERE"
npx ts-node examples/find-most-valuable-lorcana-cards.tsFor the v2 API (public beta):
npx ts-node examples/v2-quickstart.ts # search, direct lookup, localized pricing
npx ts-node examples/v2-iterate-and-graded.ts # cursor iteration and graded cards
npx ts-node examples/v2-migrate-from-v1.ts # toV1Cards adapter and typed errorsThis SDK is licensed under the MIT License.