PkmnPrices7 min read

Migrating from pokemontcg.io to PkmnPrices

pokemontcg.io keys stop working on March 1, 2027. How its requests and responses map to PkmnPrices, the code to move a card lookup and its prices, and the fields you will lose.

The Pokémon TCG API at pokemontcg.io is deprecated. Its documentation says new account registrations are closed and that "existing API keys will continue to function through March 1, 2027." If your app calls api.pokemontcg.io, that is your deadline.

This post is the code-level part of moving: how a pokemontcg.io request maps onto ours, a few lines that rebuild the tcgplayer.prices object your code probably reads, and the card fields that have no equivalent. We run PkmnPrices, so weigh the advice accordingly. If you need full card text and legality data, the comparison post covers Scrydex, which is where pokemontcg.io points. Every pokemontcg.io detail here was checked against its documentation on 26 October 2026.

What changes

pokemontcg.ioPkmnPrices
Base URLhttps://api.pokemontcg.io/v2https://api.pkmnprices.com/v1
Key headerX-Api-Keyx-api-key
Without a keyWorks, with much lower rate limitsNot allowed; a free key is 500 credits a day
Card idA string such as base1-4An integer
Searchq=name:charizard subtypes:megaOne parameter per field: name, set_id, number, rarity and others
Page sizepageSize, up to 250per_page, up to 100
Page infocount, totalCountA pagination object with total and total_pages
Choosing fieldsselect=id,nameNot available; each response has a fixed shape
PricesInside every cardNot in lists; GET /v1/cards/:id returns a prices array

Authentication

Create a key in the dashboard. The free tier needs no card. Header names are not case sensitive, so code that already sends X-Api-Key keeps working once it holds a PkmnPrices key.

bash
# pokemontcg.io
curl -H "X-Api-Key: $PTCG_KEY" \
"https://api.pokemontcg.io/v2/cards?q=name:charizard&pageSize=3"
# PkmnPrices
curl -H "x-api-key: $PKMNPRICES_KEY" \
"https://api.pkmnprices.com/v1/cards?name=charizard&per_page=3"

Searching

pokemontcg.io has a query language in q. We have separate parameters, so each clause of a q becomes its own parameter.

pokemontcg.io qPkmnPrices
name:charizardname=charizard, a substring match
set.id:base1set_id=1, the numeric id from GET /v1/sets?name=Base Set
number:4number=4; leading zeros are ignored
rarity:"Rare Holo"rarity=Rare Holo, spelled exactly as responses spell it
types:fireenergy_type=Fire
supertype:pokemoncard_type=Pokemon
subtypes:stage1stage=Stage 1; no other subtype has a filter
artist:"Mitsuhiro Arita"artist=arita, a case-insensitive substring
hp:120hp=120; exact values only, no ranges
orderBy=-set.releaseDatesort=price_asc or price_desc, and only with a price filter

Finding the same card

A pokemontcg.io id is a set code and a number, base1-4. Ours is an integer with no relation to it, so look the set up by name once and keep the result.

js
const API = "https://api.pkmnprices.com/v1";
const get = async (path, params = {}) => {
const url = new URL(API + path);
for (const [key, value] of Object.entries(params)) {
if (value !== undefined) url.searchParams.set(key, value);
}
const res = await fetch(url, { headers: { "x-api-key": process.env.PKMNPRICES_KEY } });
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
return res.json();
};
// pokemontcg.io id "base1-4" becomes a set name plus a number here.
export const findCard = async (setName, number) => {
const sets = await get("/sets", { name: setName });
const set = sets.data.find((s) => s.name === setName);
if (!set) throw new Error(`no set called ${setName}`);
const cards = await get("/cards", { set_id: set.id, number });
if (cards.data.length === 0) throw new Error(`no card ${number} in ${setName}`);
// The list has no prices; the single-card call does.
return get(`/cards/${cards.data[0].id}`);
};

The card you get back has its prices. The list call in the middle does not, which is why the last line is a second request.

Prices

pokemontcg.io nests prices under the marketplace and the printing: card.tcgplayer.prices.holofoil.market. Ours are rows in a prices array, one per source, condition and printing:

json
{
"source": "tcgplayer",
"currency": "USD",
"condition": "Near Mint",
"variant": "Holofoil",
"market_price": 412.5
}

This rebuilds the object your code already reads, for the three printings most apps use:

js
// card.tcgplayer.prices.holofoil.market, rebuilt from the prices array.
const VARIANT_KEY = {
Normal: "normal",
Holofoil: "holofoil",
"Reverse Holofoil": "reverseHolofoil",
};
export const tcgplayerPrices = (card) => {
const prices = {};
for (const row of card.prices) {
if (row.source !== "tcgplayer" || row.condition !== "Near Mint") continue;
const key = VARIANT_KEY[row.variant];
if (key) prices[key] = { market: row.market_price };
}
return prices;
};

Some fields do not carry over:

  • low, mid, high and directLow are not returned. Each row has one market_price.
  • The Cardmarket averages and trends (trendPrice, avg1, avg7, avg30 and the rest) are not returned. A Cardmarket row's market_price is the lowest trusted current offer for that condition and printing, in EUR, on Pro and above.
  • First edition printings appear as their own variants. Read the variant strings in a real response before you map them.

In return, you get a price for every condition rather than Near Mint only, daily price history for up to a year, the live TCGplayer and Cardmarket listings, and eBay sold comps. The docs cover each.

What you will not find

Our cards carry the fields a price app needs and leave out the full rules text. These have no equivalent:

  • legalities and regulationMark
  • supertype and subtypes (only card_type and stage exist)
  • rules, evolvesFrom, evolvesTo and nationalPokedexNumbers
  • ancientTrait, and abilities and attacks as structured objects: ours are text
  • Small and large images: there is one image_url
  • A set's series, release date and ptcgoCode: a set has id, name, language and card_count

If your app needs those, keep a local copy for them and use the API for prices. The historical JSON in the pokemon-tcg-data repository can still be cloned, though its README calls it legacy and says the project is being deprecated. Scrydex inherited the full card database.

Credits and paging

A credit is one item returned. A page of 100 cards costs 100 credits, and the list has no prices, so each price lookup is one more credit. Pro allows 20,000 credits a day.

An app that mirrors every card does not fit in a day. There are more than 80,000 cards, so listing them all once costs about that many credits: spread it over a few days and keep the ids. For prices on the whole catalog, the Scale plan's catalog export returns every current price as one file, and a download costs no credits.

Moving without a flag day

Keep pokemontcg.io running until March. Get a free key and run a few lookups through the code above next to your existing calls, so you can compare the numbers. Store our card id beside theirs as you go. Then switch the reads and leave the old key in place until you are sure. The plans page lists what each tier includes.

When do pokemontcg.io keys stop working?
Its documentation says existing keys continue to function through March 1, 2027. New account registrations are already closed.
Can I use my pokemontcg.io key with PkmnPrices?
No. Create a PkmnPrices key in the dashboard. The free tier gives 500 credits a day and needs no card.
Does PkmnPrices return card legality or rotation data?
No. Cards carry prices, attributes such as stage, type, HP, weakness and attack text, and one image, but no legalities or regulation marks. Keep a local copy for those.
Is there an SDK?
Yes, official SDKs for JavaScript and Python. The SDKs page in the docs shows both.
PkmnPrices

Written by

PkmnPrices

PkmnPrices is a developer API for Pokémon TCG data: daily TCGPlayer pricing across 80,000+ cards, 800+ sets, and sealed products.