Quickstart
Get a key, make your first request, and page the whole catalogue or a single set.
Get a key, make a request, then choose the paging example that matches what you need. The only prerequisite is something that can send an HTTP header.
1. Get a key
For scripts only. Creates a key without a password, so this account cannot sign in to the dashboard until the email is verified and a password is set from /account/recover; a person wanting the dashboard should use /free-api-key (POST /v1/accounts/register) instead.
Generate the Idempotency-Key once per signup and keep it with the request body:
IDEM=$(uuidgen)curl -s -X POST "https://api.pokemontcgapi.com/v1/accounts/free" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEM" \
-d '{"email":"[email protected]"}'Lost the response? Repeat the exact same request (same Idempotency-Key, same body byte for byte, same network: same public IPv4 or the same IPv6 /64) within 24 hours and the response comes back, if stored, secret included; it is the original response, so a key rotated or revoked since then is not revived. A new Idempotency-Key for the same email returns 409 ACCOUNT_EXISTS; the same key with a different body returns 409 IDEMPOTENCY_CONFLICT.
- We store only a hash of the key; the signup response is kept for 24 hours so the same request can be replayed. Save
data.key.secretnow. - Put it in your environment as
PTCG_API_KEY. - The trial starts at 80 credits with a 40 credit daily cap. Confirming the address we email you raises it to 800 credits and a 400 credit daily cap, and the trial ends 30 days after signup. Credits already spent stay spent.
If replay is unavailable, sign in and rotate the key, or recover access with an already verified email to get a new secret.
If you would rather click, the same key is at pokemontcgapi.com/free-api-key.
Secret keys are server-side
The prefix tells you where a key may live. ptcg_live_ is a secret key: it belongs on a server, never in a browser bundle. ptcg_pub_ is a public key: catalogue-only scope and a referrer allow-list, made for client-side code. See authentication.
2. Make a request
Search is the endpoint you will use most. This asks for the two most recent Charizard holo rares:
curl -s -G "https://api.pokemontcgapi.com/v1/cards" \
--data-urlencode 'q=name:charizard rarity:"rare holo"' \
--data-urlencode "orderBy=-release_date" \
--data-urlencode "limit=2" \
-H "X-Api-Key: $PTCG_API_KEY"Prefer a client? The same call is one line in the SDKs: npm install @pokemontcgapi/sdk, pip install pokemontcgapi, go get github.com/pokemontcgapi/sdk-go. They wrap every data route with the same pagination, error classes and retries; the raw calls above show the contract they sit on.
Collections always come back in the same envelope:
{
"data": [ /* rows */ ],
"meta": { "limit": 2, "count": 2, "has_more": true },
"links": { "next": "https://api.pokemontcgapi.com/v1/cards?...&cursor=eyJrIjpb..." }
}Which plan for which use case
Photo scanning app: Growth. Ongoing photo recognition starts on Growth. Card centering from a photo, with person-confirmed lines. After email confirmation, the trial includes 5 recognitions, 5 centering measure and 5 confirm calls, with separate counters. 79 € per month, 200,000 credits per month, no distinct-card limit.
Price website or bot: Developer. Developer covers catalogue lookups and current prices. Choose Growth for graded quotes, complete price history or movers. 29 € per month, 50,000 credits per month, 30,000 distinct cards per month.
Full catalogue import: Growth. Developer covers 30,000 distinct cards per month. Growth has no distinct-card limit; request credit limits still apply. 79 € per month, 200,000 credits per month, no distinct-card limit.
Try the API: Free trial. Free trial, no credit card: 800 credits granted once after email verification (80 before), valid for 30 days from signup, up to 400 credits per day after verification and 1,000 distinct cards over the trial. Credits do not renew. Trial quotes exclude graded observations and observations explicitly labelled with a non-English locale; observations without a locale label can still be returned. Verified accounts can try 5 photo recognitions.
3. Page the whole catalogue
Use the flat card list for an initial import of all cards, even if your shop groups them by set. Keep each card’s set_code locally. Paging the flat list fills pages across set boundaries and uses fewer requests than starting a separate card loop for every set.
Follow links.next until it disappears. Use orderBy=id for the import, keep it unchanged, and follow each returned URL verbatim. There is no page parameter. A cursor is a position, not a frozen snapshot; for a replica that stays current, capture the change feed watermark before importing as described in incremental updates.
const key = process.env.PTCG_API_KEY ?? "";
let next: string | null = "https://api.pokemontcgapi.com/v1/cards?limit=250&orderBy=id";
const all: unknown[] = [];
while (next) {
const res = await fetch(next, { headers: { "X-Api-Key": key } });
if (!res.ok) throw new Error((await res.json()).error.code);
const page = await res.json();
all.push(...page.data);
next = page.links?.next ?? null;
}
console.log(all.length);Measured on 2026-09-16: 57,641 cards, fetched in 231 pages of up to 250 cards. Paging the same cards separately across 651 sets takes 678 card-list requests. At one credit per 50 rows requested (5 for a full page of 250), the flat list costs 1,155 credits against 3,390 for the per-set walk at the same page size: it saves 2,235 credits (65.9%) on the initial card import. This comparison excludes set discovery, other metadata requests, retries and subsequent changes.
A full import is a Growth job: the trial covers 1,000 distinct cards and Developer 30,000 a month, so the loop above stops with 403 CARD_ALLOWANCE_EXCEEDED after a few pages on those plans (see rate limits). Add include=translations to the initial URL when you need translated names; it keeps the plain catalogue credit cost. include=index and include=prices have different credit costs, so the comparison above does not describe a price import. In a production importer, save each page to your database instead of keeping the whole catalogue in memory.
Import one print region
Regional import measurements below are from 2026-09-16, with limit=250, no priced includes and set discovery excluded.
Use the same loop with q=set.region:JP for Japanese printings or q=set.region:CN for Simplified Chinese printings. There is no need to discover and page every set in that region first. lang=ja selects a name translation; it does not filter the print region.
https://api.pokemontcgapi.com/v1/cards?limit=250&orderBy=id&q=set.region%3AJP
https://api.pokemontcgapi.com/v1/cards?limit=250&orderBy=id&q=set.region%3ACN| Print region | Cards | Flat-list requests / credits | Requests / credits if paged by set |
|---|---|---|---|
| JP | 24,722 | 99 / 495 | 387 / 1,935 at most |
| CN | 12,003 | 49 / 245 | 105 / 525 at most |
Both regional lists were paged to completion on 2026-09-16. Every returned card belonged to the requested region, with no duplicate ids, and the complete id lists matched the database selection by set region.
4. Page a whole set
Use this endpoint when you need one set, such as a shop importing only Obsidian Flames or refreshing a selected set. For all cards or all cards in a print region, use the flat-list examples above. The paging protocol is the same: follow links.next unchanged until it disappears.
const key = process.env.PTCG_API_KEY ?? "";
let next: string | null = "https://api.pokemontcgapi.com/v1/sets/obf/cards?limit=250";
const all: unknown[] = [];
while (next) {
const res = await fetch(next, { headers: { "X-Api-Key": key } });
if (!res.ok) throw new Error((await res.json()).error.code);
const page = await res.json();
all.push(...page.data);
next = page.links?.next ?? null;
}
console.log(all.length);Measured on 2026-09-16: obf returns 230 cards in one request.
What to read next
The query syntax page is the one that pays for itself: most of what people ask support is already expressible in q. After that, caching and ETags — a 304 costs no credits, so a well-behaved client is a cheap client.
What the trial gives you
Five requests per second, 800 credits granted once, a daily cap of 400, and 30 days from signup to use them. The credits do not renew: they exist to answer "does this API have what I need", and most calls spend one of them. The trial also covers up to 1,000 distinct cards: enough to build against, not to copy the catalogue. Every response carries the end date in X-Trial-Expires-At, and GET /v1/me reports it as trial_expires_at; after that date credited routes answer 403 TRIAL_EXPIRED until you subscribe, while /v1/me and the billing routes keep answering. Rate limits has the cost of the calls that spend more than one credit.