# Get a card

> Fetch one card by our id or its alternate `{set}-{number}` id, with prices, images and translations on request.

- **Endpoint**: `GET /v1/cards/{id}`
- **Cost**: 1 credit
- **Minimum plan**: free
- **Cache-Control**: `public, max-age=60, s-maxage=300, stale-while-revalidate=600`

Source: https://pokemontcgapi.com/docs/api/cards/get

Returns the card object directly, not wrapped in a collection envelope. A missing id is `404 CARD_NOT_FOUND`, which is a statement about the catalogue — cache it, do not poll it.

**This is the price endpoint.** `include=prices` expands the full per-source breakdown for the card, and the card row itself already carries `index_eur` and `last_price_at` whether you ask for the relation or not. The response below is a real one, trimmed at the fifteenth price row.

## Path parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `id` **required** | string | — | Our public id (`bs-4`) or the alternate `legacy_id`, a string of the same `{set}-{number}` form (`base1-4`). Both resolve to the same row, so ids you already have stored need no translation table. |

## Query parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `select` | string | — | Comma-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`. |
| `include` | string | — | Comma-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`. |
| `lang` | string | — | Locale 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. |

## Headers

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `X-Api-Key` **required** | string | — | Your API key. `Authorization: Bearer <key>` is accepted as an alias. |
| `If-None-Match` | string | — | Send 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**

```bash
curl -s -G "https://api.pokemontcgapi.com/v1/cards/base1-4" \
  --data-urlencode "include=prices" \
  -H "X-Api-Key: $PTCG_API_KEY"
```

**TypeScript**

`request.ts`

```ts
const url = new URL("https://api.pokemontcgapi.com/v1/cards/base1-4");
url.searchParams.set("include", "prices");

const res = await fetch(url, {
  headers: { "X-Api-Key": process.env.PTCG_API_KEY ?? "" },
});

if (!res.ok) {
  const { error } = await res.json();
  throw new Error(`${error.code}: ${error.message} (${error.request_id})`);
}

const { data, meta } = await res.json();
```

**Python**

`request.py`

```python
import os, httpx

res = httpx.get(
    "https://api.pokemontcgapi.com/v1/cards/base1-4",
    params={"include": "prices"},
    headers={"X-Api-Key": os.environ["PTCG_API_KEY"]},
)
res.raise_for_status()
payload = res.json()
```

## Example response

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

`response.json`

```json
{
  "id": "bs-4",
  "legacy_id": "base1-4",
  "name": "Charizard",
  "number": "4",
  "number_sort": 4,
  "supertype": "Pokémon",
  "hp": 120,
  "rarity": "Rare Holo",
  "tcgplayer_id": 42382,
  "cardmarket_id": 273699,
  "index_eur": 561.84,
  "last_price_at": "2026-08-26T19:03:20.955Z",
  "row_version": 6,
  "updated_at": "2026-08-26T17:47:39.900Z",
  "set_code": "bs",
  "set_name": "Base",
  "set_total": 134,
  "ptcgo_code": "BS",
  "release_date": "1999-01-09",
  "print_region": "WEST",
  "series": "Base",
  "artist_name": "Mitsuhiro Arita",
  "artist_slug": "mitsuhiro-arita",
  "prices": [
    {
      "source": "CARDMARKET",
      "variant": "LOW",
      "basis": "ASKING",
      "amount": 599.9,
      "currency": "EUR",
      "locale": "en",
      "condition": "NEAR_MINT",
      "printing": null,
      "grading": null,
      "as_of": "2026-09-01",
      "sample_n": null,
      "provenance": "Cardmarket"
    },
    {
      "source": "CARDTRADER",
      "variant": "LOW",
      "basis": "ASKING",
      "amount": 1250.64,
      "currency": "EUR",
      "locale": "en",
      "condition": "NEAR_MINT",
      "printing": null,
      "grading": null,
      "as_of": "2026-09-01",
      "sample_n": 4,
      "provenance": "CardTrader"
    },
    {
      "source": "TCGPLAYER",
      "variant": "LOW",
      "basis": "GUIDE",
      "amount": 510,
      "currency": "USD",
      "locale": "en",
      "condition": null,
      "printing": "HOLOFOIL",
      "grading": null,
      "as_of": "2026-07-30",
      "sample_n": null,
      "provenance": "TCGplayer"
    },
    {
      "source": "EBAY",
      "variant": "MEDIAN_GRADED",
      "basis": "ASKING",
      "amount": 400,
      "currency": "USD",
      "locale": "en",
      "condition": null,
      "printing": null,
      "grading": {
        "company": "PSA",
        "score": "1"
      },
      "as_of": "2026-08-26",
      "sample_n": 7,
      "provenance": "eBay"
    },
    {
      "source": "PRICECHARTING",
      "variant": "MEDIAN_GRADED",
      "basis": "GUIDE",
      "amount": 20061.4,
      "currency": "USD",
      "locale": "en",
      "condition": null,
      "printing": null,
      "grading": {
        "company": "PSA",
        "score": "10"
      },
      "as_of": "2026-08-24",
      "sample_n": null,
      "provenance": "PriceCharting"
    },
    {
      "source": "PTCG_INDEX",
      "variant": "INDEX",
      "basis": "DERIVED",
      "amount": 556.23,
      "currency": "EUR",
      "locale": "en",
      "condition": null,
      "printing": null,
      "grading": null,
      "as_of": "2026-09-02",
      "sample_n": 16,
      "provenance": "pokemontcgapi composite index"
    }
  ]
}
```

## Errors

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

| Status | Code | When |
| --- | --- | --- |
| 401 | `MISSING_API_KEY` | No `X-Api-Key` header and no bearer token on a route that requires one. |
| 401 | `INVALID_API_KEY` | The key does not match any account. |
| 404 | `CARD_NOT_FOUND` | No card with that id. Both our id (`bs-4`) and the alternate `{set}-{number}` id (`base1-4`) resolve here. |
| 400 | `INVALID_SELECT_FIELD` | A name in `select` is not a card field. |
| 400 | `INVALID_INCLUDE` | A name in `include` is not a known relation. |
| 400 | `INVALID_PARAMETER` | A 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. |

The full taxonomy, with what to do about each code, is on the [errors](https://pokemontcgapi.com/docs/errors) page.

## Reading the price rows

One row per live series. `source` says who observed it, `basis` says what kind of number it is (`SOLD` a completed sale, `ASKING` a listing, `GUIDE` a published guide value, `DERIVED` ours), and `currency` is the currency the row was observed in — a single card returns USD and EUR rows side by side, and they are not converted for you.

`MEDIAN_GRADED` rows carry `grading` with the company and the score, so a PSA 10 and a CGC 10 stay separate numbers. Every row carries `provenance` and `as_of`; display the provenance wherever you display the price. The sources, their delays and how the composite is built are on [price methodology](https://pokemontcgapi.com/price-methodology).

## The other relations

`include` takes any comma-separated subset of six names. What each one costs you is one join:

| Relation | Shape | Populated today |
| --- | --- | --- |
| `prices` | Array of [price](https://pokemontcgapi.com/docs/objects/price) rows | Yes — a dozen or more rows on a well-covered card |
| `images` | Array of [image](https://pokemontcgapi.com/docs/objects/image) rows | Yes — one `FRONT` / `NORMAL` rendition per card |
| `set` | One [set](https://pokemontcgapi.com/docs/objects/set) object | Yes |
| `artist` | One [artist](https://pokemontcgapi.com/docs/objects/artist) object | Yes, where the card is attributed |
| `translations` | Array of `{ locale, name }` | Yes — `en`, `ja`, `fr`, `de`, `es`, `it` |
| `legalities` | — | Not an accepted value: 400 `INVALID_INCLUDE`. Format legality is not in the catalogue, and an empty array would look like an answer |

`include-images-set-artist.json`

```json
{
  "images": [
    {
      "face": "FRONT",
      "size": "NORMAL",
      "locale": null,
      "url": "https://media.rarebit.app/cards/BASE/4-normal.webp",
      "image_source": "rarebit-media",
      "width": null,
      "height": null
    }
  ],
  "set": {
    "id": "bs",
    "code": "bs",
    "slug": "base",
    "legacy_id": "base1",
    "name": "Base",
    "series": "Base",
    "region": "WEST",
    "release_date": "1999-01-09",
    "total": 134,
    "printed_total": 102,
    "ptcgo_code": "BS",
    "symbol_url": null,
    "logo_url": "https://media.rarebit.app/sets/BASE/logo-normal.webp",
    "updated_at": "2026-08-26T17:47:39.900Z"
  },
  "artist": { "slug": "mitsuhiro-arita", "name": "Mitsuhiro Arita", "card_count": 760 }
}
```

`translations` returns an array of `{ locale, name }`, one entry per locale that has a name for this card — never every locale, and never a null-filled row for the ones that do not. A card with no entry for the locale you asked for in `lang` comes back with its English name.

> **Image dimensions are null**
>
> The API returns one rendition per card and does not measure it on ingest, so `width` and `height` come back null. Reserve layout space with a fixed aspect ratio rather than reading those two fields.
