v1 · 800 free credits, no card · commercial use included
The Pokémon TCG API
Japanese, international and Chinese sets as first-class rows. Every price with its source. A card identified from a photo.
Cards, sets and sealed products with names in eight locales and the illustrator on every row. One REST endpoint, cursor pagination that never drops a row, and a key from one form in about a second.
Prices in euro and in US dollars, each with its marketplace, basis, sample size and date. A trial key from one call, no card; paid plans from 29 € a month. Coming from pokemontcg.io, which no longer accepts new sign-ups? There is a migration guide.
- sets
- 651
- sets
- cards
- 58,340
- cards
- sealed products
- 2,091
- sealed products
measured live from /v1/status
curl -s "https://api.pokemontcgapi.com/v1/cards/base1-4?select=id,legacy_id,name,set_name,release_date,rarity,artist_name,index_eur" \
-H "X-Api-Key: $PTCG_API_KEY"{
"id": "bs-4",
"legacy_id": "base1-4",
"name": "Charizard",
"rarity": "Rare Holo",
"index_eur": 561.84,
"set_name": "Base",
"release_date": "1999-01-09",
"artist_name": "Mitsuhiro Arita"
}- cards
- 58,340
- Every printing across three print regions, under one id scheme you can build by hand.
- sets
- 651
- From the 1996 Japanese Expansion Pack to the block on sale now, each with release date, series, totals and print region.
- sealed products
- 2,091
- Booster boxes, elite trainer boxes, tins, blisters and collections, with names in several locales and their own price rows.
- card-name languages
- 8
- en · fr · de · ja · it · es · pt · zh
Read from https://api.pokemontcgapi.com/v1/status when this page was last generated, at most an hour ago.
print regions
Japanese sets are not a translation layer.
Most catalogues carry the international releases and bolt the rest on as alternate names. Here every print line is its own set list, because that is how the cards are actually printed: different boundaries, different numbering, different release dates.
| print region | sets | what that means |
|---|---|---|
| JP · Japan | 380 | Japanese print runs as their own sets, from the 1996 Expansion Pack onward — different boundaries, different printed totals, their own promo lines. |
| WEST · International | 176 | The English-led Western releases, with the ptcgo_code and the printed total on every row. |
| CN · Simplified Chinese | 96 | The Simplified Chinese line, carried as first-class sets rather than as a footnote. |
Measured with GET /v1/sets?region=<code>&limit=1, reading meta.total_count, on 2026-09-18.
Eight languages on the cardboard
These are the languages the cards are printed in. A German printing and a French one are two different objects with two different prices, not one row with a translated label — which is exactly why the quotes carry a locale of their own.
- JapaneseIts own set list, months ahead
- English (US)The international print line
- English (UK)Same cards, its own market
- ItalianPriced in EUR
- FrenchPriced in EUR
- GermanPriced in EUR
- SpanishPriced in EUR
- ChineseSimplified, its own set list
GET /v1/sets/sv8/cards?limit=1&lang=ja
{
"data": [
{
"id": "sv8-001",
"name": "タマタマ",
"number": "001",
"set_name": "Super Electric Breaker"
}
]
}And 8 languages the API answers in
A different list, and the difference matters. ?lang=ja does not add a field, it replaces the one you already read: name comes back Japanese on the same row, and a card with no translation falls back to English rather than to null. The 8 locales are en · fr · de · ja · it · es · pt · zh.
Printed in, answered in
The eight above are print runs you can buy and price. The 8 here are labels on our response. They overlap but they are not the same set: we answer in Italian for cards that were never printed in Italian, and we price Chinese printings that we do not answer in.the card object
One row per printing, with the coordinates that let you join it to anything.
This is a real response, trimmed only of the fields that repeat. It is a catalogue and pricing record: identity, provenance, imagery, marketplace ids and a price index. The gameplay fields are modelled and currently empty, and they are printed here empty rather than hidden.
{
"id": "bs-4",
"legacy_id": "base1-4",
"name": "Charizard",
"number": "4",
"number_sort": 4,
"supertype": "Pokémon",
"hp": 120,
"rarity": "Rare Holo",
"set_code": "bs",
"set_name": "Base",
"set_total": 134,
"ptcgo_code": "BS",
"series": "Base",
"release_date": "1999-01-09",
"print_region": "WEST",
"artist_name": "Mitsuhiro Arita",
"artist_slug": "mitsuhiro-arita",
"index_eur": 561.84,
"last_price_at": "2026-08-26T19:03:20.955Z",
"tcgplayer_id": 42382,
"cardmarket_id": 273699,
"row_version": 6,
"updated_at": "2026-08-26T17:47:39.900Z",
"images": [
{
"face": "FRONT",
"size": "NORMAL",
"url": "https://img.pokemontcgapi.com/media/cards/BASE/4-normal.webp?e=1791763200&s=Qm3vZk1yWq8dP2sL0aXt9c",
"image_source": "rarebit-media"
}
],
"translations": [
{ "locale": "en", "name": "Charizard" },
{ "locale": "fr", "name": "Dracaufeu" },
{ "locale": "de", "name": "Glurak" }
],
"attacks": null,
"abilities": null,
"weaknesses": null,
"subtypes": [],
"types": []
}id · legacy_idThe id is the printed coordinate — set code, dash, collector number — so you can build it from a card in your hand. legacy_id is an alternate string id in the same shape, and both resolve on the same route, so a catalogue that already stored one keeps working.
print_region · set_code · seriesWhich print line a card belongs to, which set it sits in, and which era that set belongs to. This is what makes a Japanese printing a first-class row rather than a variant of an English one.
translations · ?lang=Card names in 8 languages (en · fr · de · ja · it · es · pt · zh), as a list or applied in place: ask for ?lang=ja and the name field itself comes back Japanese. A missing translation falls back to English rather than to null.
index_eur · last_price_atA derived euro index and the moment it was last recomputed, on every single card and — with include=index, one credit per 50 rows — on the rows of a list, so a list still carries a comparable number without a second request per card.
artist_name · artist_slugEvery illustrator, deduplicated across 30 years of printings and reachable as its own resource, so "everything Mitsuhiro Arita drew" is one query and not a scrape.
tcgplayer_id · cardmarket_idThe marketplace ids we matched each card against, so you can join to a database you already have instead of matching on names.
imagesOne front image per card, served from our own host. Width and height are modelled on the object but are not populated today, so reserve the box from the 5:7 trading-card ratio rather than from the response.
row_version · updated_atA monotonic version and a timestamp on every row, which is what lets a mirror re-sync by comparing versions instead of refetching the catalogue.
Gameplay text is English, and uneven
attacks, abilities, weaknesses, resistances, subtypes, retreat_cost, flavor_text and retreat_cost carry rows since 3 September 2026 — but in English only, so they land on the 20,725 Western printings and not on the Japanese and Chinese ones, which are the larger half of the catalogue. Counted whole rather than sampled: attacks on 33% of all cards and 83% of the Western ones, subtypes on 38%, abilities on 8%. legalities is still empty for every card, so a deck checker is still not something this API can carry — and we would rather you find that out here than after the integration.
prices
Every price says where it came from.
A number on its own cannot be defended when a user asks about it. Every observation carries its source, what kind of figure it is, which printing and grade it applies to, how many observations are behind it, and the day it is for.
/v1/cards/base1-4?include=pricesGET /v1/cards/base1-4?include=prices
{
"id": "bs-4",
"index_eur": 579.53,
"prices": [
{
"source": "TCGPLAYER",
"variant": "MARKET",
"basis": "GUIDE",
"amount": 800.43,
"currency": "USD",
"printing": "HOLOFOIL",
"grading": null,
"as_of": "2026-07-30",
"sample_n": null,
"provenance": "TCGplayer"
},
{
"source": "CARDMARKET",
"variant": "LOW",
"basis": "ASKING",
"amount": 599.90,
"currency": "EUR",
"condition": "NEAR_MINT",
"grading": null,
"as_of": "2026-09-01",
"sample_n": null,
"provenance": "Cardmarket"
},
{
"source": "PRICECHARTING",
"variant": "MEDIAN_GRADED",
"basis": "GUIDE",
"amount": 20061.40,
"currency": "USD",
"grading": { "company": "PSA", "score": "10" },
"as_of": "2026-08-24",
"sample_n": null,
"provenance": "PriceCharting"
},
{
"source": "PTCG_INDEX",
"variant": "INDEX",
"basis": "DERIVED",
"amount": 579.53,
"currency": "EUR",
"locale": "en",
"grading": null,
"as_of": "2026-09-02",
"sample_n": 6,
"provenance": "pokemontcgapi composite index"
}
]
}sourceWhere the price formed — TCGPLAYER, CARDMARKET, CARDTRADER, EBAY, PRICECHARTING, COMMUNITY, PTCG_INDEX — as a stable enum you can filter on, so you can lead with the market your customer is actually in.
basisWhat kind of number it is. GUIDE is a published figure, DERIVED is computed by us. A price with no basis is a rumour with two decimal places.
variantMARKET, LOW, an average window, a graded median, or our INDEX.
printing · gradingHolofoil against reverse, raw against a PSA 10 — never mixed into one number.
sample_nHow many observations are behind it, where the source tells us. Null means it did not.
as_ofThe day the observation is for. Every source is served after a delay, so this is never today.
provenanceThe attribution string to print next to the number in your own UI, so your users see it too.
The euro index is ours
PTCG_INDEX is a composite we compute, in euros, with basis: DERIVED and sample_n on every observation — a market signal, not a quote you can transact on. It is also on every card as index_eur, and on list rows with include=index, so a list still carries a comparable number.the contract
The parts you only notice when they are missing.
Caching that costs you nothing, paging that stays correct at depth, and errors that name what went wrong. Everything on this list was verified against the running service.
A 304 costs nothing
Every collection carries a strong ETag. Send it back as If-None-Match and an unchanged result answers 304 with no body: no database work on our side, no parsing on yours. Re-syncing often is the behaviour we want to reward, not bill for.
docs →Pagination that never repeats or drops a row
Paging is keyset-based and every sort ends with the id as a tiebreaker, at any depth. Follow links.next until it is gone; the cursor carries a signature of the sort order, so changing orderBy mid-walk is rejected rather than quietly skipping rows.
docs →Stable error codes
Machine-readable SCREAMING_SNAKE codes that do not change once published, a message that names the offending value, a details object that lists what would have been valid, and a request_id you can quote at us. A client mistake is never answered with a 5xx.
docs →Ask for the fields you use
select trims the response on the wire and include opts into relations — prices, images, translations, set, artist — so a list view moves a fraction of the bytes a detail view does, and a wrong field name is a 400 that tells you the right ones.
docs →Ids you can build by hand
Set code, dash, collector number. You can construct an id from the card in front of you, and the alternate legacy id resolves on the same route, so migrating a catalogue you already have does not start with a matching problem.
docs →Callable from a browser
CORS is enabled, so front-end code can call the API directly. Keep secret keys out of bundles: a public key type with catalogue-only scope and a referrer allow-list is what belongs in client code.
docs →a real response head
HTTP/2 200
content-type: application/json; charset=utf-8
etag: W/"JR4-4C3IFIC3FTB9bDiwsTpaJsg-gzip"
cache-control: public, max-age=60, s-maxage=300, stale-while-revalidate=600
ratelimit-limit: 5
ratelimit-remaining: 9
ratelimit-reset: 1
x-credits-cost: 1
x-quota-limit: 80
x-quota-remaining: 79
x-quota-reset: 2026-10-01T00:00:00Z
x-request-id: 923508b19a52fb733c27778f87366bc6An etag on every collection and a x-request-id on every response, which is the one thing support can act on. Every metered response also carries RateLimit-Limit, RateLimit-Remaining, X-Quota-Remaining and X-Credits-Cost, so a client can back off before a 429 rather than after — verified against the running service on 10 September 2026.
a real error
{
"error": {
"code": "INVALID_SELECT_FIELD",
"message": "Unknown field \"hitpoints\" in \"select\". See details.valid_fields for the full list.",
"details": {
"field": "hitpoints",
"valid_fields": ["id", "legacy_id", "name", "number", "hp", "…"]
},
"request_id": "abe7c040-a7c1-4e58-88ca-92e8ff24a1de"
}
}The details object lists what would have been valid, so a mistyped field is one round trip to fix instead of a trip to the documentation.
pricing
Start with the trial. Build on Developer.
Every plan reaches the whole catalogue; the trial covers 1,000 distinct cards and Developer 30,000 a month. Developer covers current prices and 30 days of history; Growth lifts the card limit and adds graded quotes, the complete price series, the movers feed and ongoing card recognition.
| plan | monthly | credits | req / second | price history | graded · movers · vision |
|---|---|---|---|---|---|
| Trial | Free | 800 once | 5 | 7 days | No |
| Developer | 29 €/mo | 50,000 / mo | 30 | 30 days | No |
| Growth | 79 €/mo | 200,000 / mo | 80 | Complete | Yes |
| Professional | 149 €/mo | 500,000 / mo | 160 | Complete | Yes |
| Enterprise | Talk to us | Custom | 400 | Complete | Yes |
what people build
Walkthroughs, not a feature list.
Each one is a real build against real endpoints, with the traps named.
prices
Build a Pokémon card price tracker
Watch a list of cards, store a daily price series with its provenance, and alert when a card moves more than a threshold.
bots
Build a Pokémon TCG Discord bot
A /card slash command that answers in under a second: search, disambiguate, and reply with an embed carrying the art, the rules text and a price.
apps
Build a Pokémon card collection app
Mirror the catalogue once, keep it current with a change feed, and value a binder without hammering the API on every screen.
questions
Before you integrate.
- Is there a free Pokémon TCG API?
- There is a free trial: 800 credits, granted once, valid for 30 days, no credit card, and every catalogue endpoint open — every set, one front image per card and today's price, up to 1,000 distinct cards over the trial (Developer reads 30,000 a month, Growth has no limit). Graded quotes and the complete price series start at Growth; verified accounts can try 5 photo recognitions during the trial, with ongoing access on Growth. It is deliberately a trial rather than a permanent free tier: the credits do not renew, and when they run out or the 30 days end the account waits for a plan. Commercial use is allowed either way. Free API key
- Does it include Japanese cards?
- Yes, and they are the largest part of the catalogue: 379 Japanese sets against 176 international ones, going back to the 1996 Expansion Pack. Japanese print runs are modelled as their own sets rather than as translations, because that is what they are — different boundaries, different numbering, different release dates. Filter them with region=JP, and ask for Japanese card names with lang=ja. Coverage by region
- Are there Pokémon card prices in EUR?
- Yes. Every card carries index_eur, a composite index in euros with the number of observations behind it and the day it was computed — on a single card always, on a list with include=index — and the full price list on a card returns each observation with its source, basis, printing, grading and as_of date. Marketplace figures are carried in their original currency and are never silently converted. Price methodology
- Which languages does it cover?
- Card names come in 8 languages — en · fr · de · ja · it · es · pt · zh — either as a translations list or applied in place with ?lang=. There are no Korean card names in the catalogue today, and Simplified Chinese names cover only a fraction of the cards; the schema reserves Korean, and the coverage page states exactly what is present, measured from the API rather than asserted. Coverage
- Is this an official Pokémon API?
- No, and nothing here pretends otherwise. This is an independent project that indexes public card data. Card names, artwork and set data belong to their owners; the attribution page lists every source behind the catalogue and how each one is credited. Attribution
Point your code at it.
A free key in one form, every catalogue endpoint, and a quickstart that gets a real response back in about three minutes.