Skip to content
pokemontcgapi.com

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"
200 OK
{
  "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.

Sets by print region
print regionsetswhat that means
JP · Japan380Japanese print runs as their own sets, from the 1996 Expansion Pack onward — different boundaries, different printed totals, their own promo lines.
WEST · International176The English-led Western releases, with the ptcgo_code and the printed total on every row.
CN · Simplified Chinese96The 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
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.

GET /v1/cards/base1-4
{
  "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_id

The 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 · series

Which 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_at

A 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_slug

Every 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_id

The marketplace ids we matched each card against, so you can join to a database you already have instead of matching on names.

images

One 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_at

A 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.

get/v1/cards/base1-4?include=prices
200 OK
GET /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"
    }
  ]
}
source

Where 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.

basis

What 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.

variant

MARKET, LOW, an average window, a graded median, or our INDEX.

printing · grading

Holofoil against reverse, raw against a PSA 10 — never mixed into one number.

sample_n

How many observations are behind it, where the source tells us. Null means it did not.

as_of

The day the observation is for. Every source is served after a delay, so this is never today.

provenance

The 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

curl -D -
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: 923508b19a52fb733c27778f87366bc6

An 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

400 Bad Request
{
  "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.

Plans
planmonthlycreditsreq / secondprice historygraded · movers · vision
TrialFree800 once57 daysNo
Developer29 €/mo50,000 / mo3030 daysNo
Growth79 €/mo200,000 / mo80CompleteYes
Professional149 €/mo500,000 / mo160CompleteYes
EnterpriseTalk to usCustom400CompleteYes

what people build

Walkthroughs, not a feature list.

Each one is a real build against real endpoints, with the traps named.

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.