Skip to content
pokemontcgapi.com
Documentation

Search cards

Query 52,337 cards with Lucene syntax, keyset pagination, field projection and price sorting.

GET/v1/cards1 creditfree plan

Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=600

Objects returned

The main read path. q accepts a Lucene-style query grammar — field terms, phrases, booleans, wildcards and ranges — documented in full in query syntax.

Results are ordered by id unless you say otherwise, and every sort ends with id as a tiebreaker. That is not a detail: without it, two cards sharing a sort key have undefined relative order between queries, and paging silently duplicates and drops rows.

Every row carries index_eur and last_price_at, so a price-sorted list is one request with no include and no second round trip. The example below sorts the whole catalogue by our composite index.

Query parameters

Query parameters
NameTypeDefaultDescription
qstringLucene query. Omit it to list everything. Full grammar in query syntax.
setstringComma-separated set codes, slugs or alternate ids. A shortcut for q=set.id:(a OR b) that does not need quoting.
orderBystringidUp to 4 keys, comma-separated, - for descending: id, name, number, rarity, hp, index_eur, last_price_at, updated_at, created_at, release_date, set, artist. A fifth key is QUERY_TOO_COMPLEX; an unsortable name is INVALID_PARAMETER. id is appended as tiebreaker whether you ask for it or not.
selectstringComma-separated card fields to return. id is always included. camelCase and snake_case both resolve, so nationalPokedexNumbers works and comes back as national_pokedex_numbers. An unknown name is INVALID_SELECT_FIELD, with the full list in details.valid_fields.
includestringComma-separated relations to expand: prices, translations, images, set, artist. Each one is an extra join, so ask only for what you render. include=prices is how you get the per-source price breakdown. legalities is not an accepted value and is rejected with 400 INVALID_INCLUDE.
langstringLocale for the card name: en, ja, fr, de, es, it — the six with translation rows in the catalogue. A card with no translation in the requested locale falls back to English rather than to null.
limitinteger50Rows per page, 1 to 250. Above 250 you get LIMIT_EXCEEDED — follow links.next instead of raising it.
cursorstringOpaque keyset cursor from links.next. Never construct one; it carries the sort order it was issued for and is rejected if orderBy changes mid-run.

Headers

Headers
NameTypeDefaultDescription
X-Api-Key requiredstringYour API key. Authorization: Bearer <key> is accepted as an alias.
If-None-MatchstringSend back the ETag you stored. The API returns a strong ETag on every catalogue response, and a 304 is a header exchange: no body, no database row read.

Example request

curl -s -G "https://api.pokemontcgapi.com/v1/cards" \
  --data-urlencode "q=name:charizard" \
  --data-urlencode "orderBy=-index_eur" \
  --data-urlencode "limit=2" \
  --data-urlencode "select=id,name,number,rarity,set_code,set_name,release_date,artist_name,index_eur,last_price_at" \
  -H "X-Api-Key: $PTCG_API_KEY"

Example response

200 OK · Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=600

response.json
{
  "data": [
    {
      "id": "nde-107",
      "name": "Shining Charizard",
      "number": "107",
      "rarity": "Rare Shining",
      "index_eur": 5894.78,
      "last_price_at": "2026-08-26T19:03:20.955Z",
      "set_code": "nde",
      "set_name": "Neo Destiny",
      "release_date": "2002-02-28",
      "artist_name": "Hironobu Yoshida"
    },
    {
      "id": "e5-089",
      "name": "Charizard",
      "number": "089",
      "rarity": "Secret Rare",
      "index_eur": 5371.99,
      "last_price_at": "2026-08-26T19:03:20.955Z",
      "set_code": "e5",
      "set_name": "Mysterious Mountains",
      "release_date": "2002-10-04",
      "artist_name": null
    }
  ],
  "meta": { "limit": 2, "count": 2, "has_more": true },
  "links": {
    "next": "https://api.pokemontcgapi.com/v1/cards?q=name%3Acharizard&orderBy=-index_eur&limit=2&cursor=eyJrIjpbNTM3MS45OV0sImlkIjoiZTUtMDg5Iiwicy..."
  }
}

Errors

Every error body carries error.code, error.message and error.request_id. Switch on the code, never on the message.

Errors returned by this endpoint
StatusCodeWhen
401MISSING_API_KEYNo X-Api-Key header and no bearer token on a route that requires one.
401INVALID_API_KEYThe key does not match any account.
400INVALID_QUERYThe q parameter does not parse, or names a field that does not exist.
400QUERY_TOO_COMPLEXA structural limit was exceeded — the query has too many clauses or nests too deep, or orderBy carries more than 4 keys.
400QUERY_UNSUPPORTEDThe syntax is valid but cannot run: a leading wildcard, or a range on a field that is neither numeric nor a date.
400INVALID_CURSORThe cursor is malformed, or was issued for a different orderBy than the one on this request.
400INVALID_SELECT_FIELDA name in select is not a card field.
400INVALID_INCLUDEA name in include is not a known relation.
400INVALID_PARAMETERA query parameter has the wrong type or an unsupported value — a non-integer limit, an unknown lang, an unknown region, a sort key that is not sortable.
400LIMIT_EXCEEDEDlimit is above 250, or a batch request carries more than 100 ids.

The full taxonomy, with what to do about each code, is on the errors page.

Unknown parameters are ignored

A query parameter this endpoint does not know is dropped silently — it is not an error and it does not change the result. A typo in a filter name therefore returns a full, unfiltered page rather than a 400, so check the parameter names in the table above before concluding that a filter does nothing.

Paging a full result set

Follow links.next until it is absent. Do not rebuild the URL yourself and do not change orderBy between pages — the cursor carries the sort order it was issued for, and a mismatch is rejected with INVALID_CURSOR rather than quietly skipping rows.

page-everything.ts
let next: string | null =
  "https://api.pokemontcgapi.com/v1/cards?set=bs&orderBy=number&limit=250";

while (next) {
  const res = await fetch(next, { headers: { "X-Api-Key": key } });
  const page = await res.json();
  for (const card of page.data) upsert(card);
  next = page.links?.next ?? null;
}

What the catalogue holds

This endpoint searches identity, printing and price: name, number, rarity, HP, set, series, region, artist and the composite index. Rules text — attacks, abilities, subtypes, flavour text — is declared in the schema and carries no rows today, so a query that filters on it matches nothing. The card object states the coverage of every field with the date it was measured.

view this page as markdown