# Migrating from pokemontcg.io to pokemontcgapi

> Adapt your card IDs, response parsing, pagination and price reads to `https://api.pokemontcgapi.com/v1`.

Source: https://pokemontcgapi.com/docs/migrate-from-pokemontcg-io

This guide is for an application with an existing Pokémon card integration or saved dataset. The mappings below describe how to handle fields your application may already use. They document the destination API; they are not an inventory of another provider’s current features.

Read at the source before you plan anything. As of 16 September 2026, [the other API’s own documentation](https://docs.pokemontcg.io/) states: “The Pokémon TCG API is deprecated. New account registrations are no longer available. Existing API keys will continue to function through March 1, 2027. Please migrate your application to Scrydex.” That is their announcement, quoted in full, not our reading of their plans. Check the page yourself: it is the only version that stays current.

## Start with a small set of saved cards

Get your own key from [Free API key](https://pokemontcgapi.com/free-api-key), then send it in the `X-Api-Key` header. Keep the key in your backend or another trusted environment.

```bash
# Set PTCG_API_KEY to your own key before running these examples.
curl --fail-with-body --get \
  'https://api.pokemontcgapi.com/v1/cards/base1-4' \
  --header "X-Api-Key: ${PTCG_API_KEY}" \
  --data-urlencode 'include=images,set'
```

This catalog request costs **1 credit**. It requests the image and set relations explicitly. Read the returned card directly from the top-level JSON object, without a `data` wrapper; use its returned `id` as the canonical ID in your new integration.

Test the IDs and fields that your application actually depends on before importing the rest of your collection. Keep unresolved records visible in a review queue.

## Resolve IDs before changing your stored references

Our card object has an `id` and a nullable `legacy_id`. Single-card and batch lookups accept a historical alias when its prefix is not a canonical set code. A canonical prefix binds the lookup to that set. The batch reports unresolved ids in optional `missing` entries with `id` and, only for an existing historical candidate in a different canonical set, `suggested_id`. For example, the documented card `bs-4` has the legacy ID `base1-4`.

Our set object has `code`, `slug` and a nullable `legacy_id`. The set’s `id` equals its `code`. Set lookup accepts these identifiers; for example, `bs`, `base` and `base1` resolve the documented Base Set record.

An alias is a recorded relationship, not a string-rewriting rule. Do not generate new IDs by replacing a prefix. There is no guaranteed mapping for every ID in an existing dataset. If lookup does not resolve a record, compare set, collector number, print region and imagery before linking it manually. A card name alone is insufficient.

Keep the original provider ID alongside the canonical ID returned here. This lets you audit a match without changing what the original record meant.

## Translate the fields your application reads

The left column describes a field in an existing saved record or adapter. Apply a row only when that field is present in your input.

| Existing field or concept | Read from pokemontcgapi | Handling |
| --- | --- | --- |
| Card `id` | `id`, optionally `legacy_id` | Resolve the stored ID, then preserve the returned canonical ID. |
| `name`, `number`, `supertype`, `subtypes`, `types`, `rarity` | Same field names | Keep `number` as a string and preserve missing values. |
| `hp` | `hp` | A nullable integer; do not turn an absent value into zero. |
| `evolvesFrom`, `evolvesTo` | `evolves_from`, `evolves_to` | Read the destination fields explicitly. |
| `regulationMark`, `flavorText` | `regulation_mark`, `flavor_text` | Both can be null. |
| `retreatCost`, `convertedRetreatCost` | `retreat_cost`, `converted_retreat_cost` | Energy-symbol array and nullable integer. |
| `nationalPokedexNumbers` | `national_pokedex_numbers` | Integer array. |
| Attack `convertedEnergyCost` | Attack `converted_cost` inside `attacks` | Attack damage remains a string; do not coerce values containing operators into numbers. |
| A string `artist` | `artist_name`, `artist_slug` | `include=artist` requests a separate object. |
| Nested set identity and name | `set_code`, `set_name`; `set` with `include=set` | Resolve the ID separately from the name. |
| Set `printedTotal`, `ptcgoCode`, `releaseDate`, `updatedAt` | `printed_total`, `ptcgo_code`, `release_date`, `updated_at` on the set | `release_date` is a date; `updated_at` is a timestamp. |
| Set image fields | `logo_url`, `symbol_url` on the set | These fields are nullable; keep an image fallback. |
| `images.small`, `images.large` | `images[]` with `include=images` | Inspect each entry’s `url`, `face`, `size` and `locale`. There is no fixed small/large object mapping. |
| Provider-specific nested price blocks | `prices[]` with `include=prices`, or `data.quotes[]` on the current-price route | Rebuild the price-selection logic around the quote dimensions below. There is no universal one-field rename. |
| `legalities` | No equivalent relation | Do not substitute `regulation_mark` for format legality. `include=legalities` is not supported. |

Relations are omitted unless requested. A missing relation is different from a requested relation containing an empty array. The [card object](https://pokemontcgapi.com/docs/objects/card) and [set object](https://pokemontcgapi.com/docs/objects/set) describe the full destination schema and its nullable fields.

## Replace the pagination loop

Collections return a `{data, meta, links}` envelope and use `limit` and an opaque `cursor`. `meta.limit` records the requested page size; `links.next` sits alongside `meta`, not inside it. The default limit is **50**, with a maximum of **250**. Follow the complete `links.next` URL until it is absent. Read rows from `data` and the current page’s row count from `meta.count`; `meta.has_more` indicates whether another page exists.

A stored `page` or `pageSize` value does not translate into a cursor. Start the destination query again. Card list responses do not provide a total-count equivalent for every query.

```bash
curl --fail-with-body --get \
  'https://api.pokemontcgapi.com/v1/cards' \
  --header "X-Api-Key: ${PTCG_API_KEY}" \
  --data-urlencode 'set=base1' \
  --data-urlencode 'q=name:charizard' \
  --data-urlencode 'include=images' \
  --data-urlencode 'limit=50'
```

This request costs **1 credit**. A set alias in the `set` parameter is resolved against our catalog. Revalidate saved search expressions against [query syntax](https://pokemontcgapi.com/docs/query-syntax); do not assume every expression from an existing client has an equivalent. `orderBy` uses our destination field names, such as `orderBy=-updated_at`.

For the paging protocol, see [Pagination](https://pokemontcgapi.com/docs/pagination). A collection cursor represents a position in an ordering, not a frozen snapshot or a durable synchronization checkpoint.

## Read prices without changing their meaning

```bash
curl --fail-with-body --get \
  'https://api.pokemontcgapi.com/v1/cards/base1-4/prices' \
  --header "X-Api-Key: ${PTCG_API_KEY}"
```

This request costs **2 credits**. The response separates `data.index` from `data.quotes`.

A quote carries `amount`, `currency`, `variant`, `basis`, `locale`, `condition`, `printing`, `grading`, `as_of` and `provenance`. Some dimensions can be null. Choose the dimensions your application requires before displaying or aggregating a value. Our `variant` describes a price measure; `printing` describes a physical printing. They are not interchangeable.

EUR is represented explicitly by `currency: "EUR"`. Our composite value is also available as the nullable `index_eur` field. It is not a renamed lowest offer, last sale or arithmetic average from your previous dataset. Preserve the currency and the observation date; an empty quote array or null index means no available value for that response, not a value of zero.

On card lists and batches, request `include=index` for the composite fields or `include=prices` for quotes. Plain catalog lists omit the priced index fields. Price responses are subject to availability, delay rules and the account’s plan. See [Price methodology](https://pokemontcgapi.com/price-methodology) before defining a valuation rule.

## Add the destination resources your application needs

These are optional parts of the pokemontcgapi model, not claims of exclusivity relative to another service.

**Japanese and Chinese print lines.** Discover sets with `GET /v1/sets?region=JP` or `GET /v1/sets?region=CN`, then read cards through `GET /v1/sets/{code}/cards`. Each plain catalog request costs **1 credit**. Card `print_region` and set `region` identify the print line. `lang` selects a localized name with a fallback when the requested translation is unavailable; it does not turn a Western printing into a Japanese or Chinese card. A nullable `jp_twin_id` links a Japanese counterpart where a pairing is recorded. There is no complete automatic cross-region mapping.

**Sealed products.** `GET /v1/sealed` lists products; `GET /v1/sealed/{id}` reads one. Both plain catalog calls cost **1 credit**. The product’s `id` equals its `sku`. Fields include `name`, `kind`, `set_code`, `image_url` and `languages`. Use the SKU as a product identity, not a card number. `GET /v1/sealed/{id}/prices` costs **2 credits**. A product catalog and its quotes do not provide retailer stock or restock alerts.

**Incremental updates.** `GET /v1/changes` costs **1 credit per call**, including an empty successful response. The feed returns change events, not complete card or price records; fetching affected records costs additional credits.

For an initial replica, read the feed with `since=0&limit=1` and capture `meta.watermark` **before** importing the catalog. Complete the catalog import, then consume changes after that captured watermark so changes made during the import are replayed. If the checkpoint expires during this process, restart the import.

For ongoing synchronization, apply each page’s changes and successfully fetch the required records before committing `meta.next_since` as your next `since` value. Follow `meta.has_more`, handle deletions, and make application of repeated events idempotent. The change feed’s `limit` defaults to **250** and can reach **1,000**. An old checkpoint can return **410 `SYNC_CURSOR_TOO_OLD`**: the retained history can no longer reconstruct the delta. Perform a fresh import with a new baseline. There is no promise here of an unlimited retention window.

## Budget the requests you will actually make

The following costs apply to the routes used in this guide. `ceil` means round up. List pricing uses the requested `limit`, not the number of rows eventually returned; batch pricing uses the requested ID count. Request limits still apply.

| Route | Request shape | Credits |
| --- | --- | --- |
| `GET /v1/cards` | Plain catalog, including images or set metadata | 1 |
| `GET /v1/cards` | `include=index` | `ceil(limit / 50)` |
| `GET /v1/cards` | `include=prices` | `4 × ceil(limit / 25)` |
| `GET /v1/cards/{id}` | Plain catalog, including images or set metadata | 1 |
| `GET /v1/cards/{id}` | `include=prices` | 2 |
| `GET /v1/cards/batch` | Plain catalog | `ceil(requested IDs / 25)` |
| `GET /v1/cards/batch` | `include=index` | `ceil(requested IDs / 50)` |
| `GET /v1/cards/batch` | `include=prices` | `4 × ceil(requested IDs / 25)` |
| `GET /v1/sets` | Catalog | 1 |
| `GET /v1/sets/{code}` | Catalog | 1 |
| `GET /v1/sets/{code}/cards` | Plain catalog | 1 |
| `GET /v1/sets/{code}/cards` | `include=index` | `ceil(limit / 50)` |
| `GET /v1/sets/{code}/cards` | `include=prices` | `4 × ceil(limit / 25)` |
| `GET /v1/cards/{id}/prices` | Current quotes | 2 |
| `GET /v1/sealed` | Plain catalog | 1 |
| `GET /v1/sealed` | `include=index` | `ceil(limit / 50)` |
| `GET /v1/sealed/{id}` | Catalog | 1 |
| `GET /v1/sealed/{id}/prices` | Current quotes | 2 |
| `GET /v1/changes` | A page of events | 1 |
| `GET /v1/billing/plans` | Public plan listing, no key required | 0 |

An include price is the total route cost for that request, not a surcharge added to the plain catalog cost. On cards, asking for both index and prices uses the prices tariff. A catalog batch accepts up to **100 IDs**.

For example, a card list requesting `limit=250` costs **1 credit** for plain catalog data, **5 credits** with `include=index`, or **40 credits** with `include=prices`. A smaller final response does not reduce the cost calculated from the requested limit.

## Plans

Public list prices and allowances:

| Plan | Monthly billing | Annual billing | Credits |
| --- | --- | --- | --- |
| Trial | Free | Not a subscription | 800 once, after email verification |
| Developer | 29 €/month | 290 €/year | 50,000 per month |
| Growth | 79 €/month | 790 €/year | 200,000 per month |
| Professional | 149 €/month | 1,490 €/year | 500,000 per month |

The trial expires **30 days after signup** and has a **400-credit daily cap** after verification. Its allowance does not renew. Before verification, signup limits are reduced. Trial quotes exclude graded observations and observations explicitly labeled with a non-English locale; observations without a locale label can still be returned. Catalog print-region coverage is a separate dimension from those quote permissions.

Developer includes all quote locales; graded quotes require Growth or above. These permissions do not create data for an unquoted card or product. Check [Pricing](https://pokemontcgapi.com/pricing) or the public `GET /v1/billing/plans` response for the current list before budgeting a deployment.

Credit allowance, request rate and concurrency are separate limits. Respect `Retry-After` when returned, cap your concurrency, and read `X-Credits-Cost` to inspect request charges. See [Rate limits](https://pokemontcgapi.com/docs/rate-limits) for the account limits and [Caching and ETags](https://pokemontcgapi.com/docs/caching-etags) for conditional requests.

## Check the adapter before switching traffic

Validate known cards, unresolved IDs, missing images, unquoted cards, the currencies you use and the print regions you support. Compare the meaning of each displayed price, not just its numeric value. Exercise a multi-page import and restart the sync worker from a saved checkpoint. Keep your original identifiers and stored observations until you have verified the new mapping.

Start with [Quickstart](https://pokemontcgapi.com/docs/quickstart), then use the [API reference](https://pokemontcgapi.com/docs/api) for the routes your application needs.
