Skip to content
pokemontcgapi.com

Graded card price API

PSA, BGS and CGC graded Pokémon card prices over an API

A PSA 10 and a PSA 8 of the same card are different objects with different markets. The API prices them as separate rows, each naming the grading company, the grade, the source and whether the figure is a guide median or an asking price.

Companies
PSA, BGS, CGC, SGC, ACE, TAG
Shape
One row per company and grade, grading: { company, score }
Filter
variant=MEDIAN_GRADED on any price route
Plan
Growth and above

A single "market price" describes neither a raw near-mint copy nor a slabbed gem-mint one. So graded figures never get folded into the raw price: they are rows of their own, with grading.score filled in and grading.company whenever the source names the grader, and the composite EUR index deliberately excludes them. On an old, valuable card the graded rows are most of the response.

Two sources feed them, and they do not measure the same thing: one publishes guide medians, the other what sellers are asking today. Which is which, and why an asking median runs above a sold one, is on the price methodology page; here it is enough to know that basis tells them apart on every row, and that your UI should too.

One call, and what comes back

Only the graded rows of one card, with grading filled in, as a Growth key sees them. Below that plan the same request returns no rows and meta.withheld says graded.

GET/v1/cards/{id}/prices2 credits · from Trialfull reference
request.tsrequest.py
curl -s -G "https://api.pokemontcgapi.com/v1/cards/base1-4/prices" \
  --data-urlencode "variant=MEDIAN_GRADED" \
  -H "X-Api-Key: $PTCG_API_KEY"
const url = new URL("https://api.pokemontcgapi.com/v1/cards/base1-4/prices");
url.searchParams.set("variant", "MEDIAN_GRADED");

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();
import os, httpx

res = httpx.get(
    "https://api.pokemontcgapi.com/v1/cards/base1-4/prices",
    params={"variant": "MEDIAN_GRADED"},
    headers={"X-Api-Key": os.environ["PTCG_API_KEY"]},
)
res.raise_for_status()
payload = res.json()
response.json
{
  "data": {
    "card_id": "base1-4",
    "index": {
      "eur": 556.23,
      "as_of": "2026-09-02",
      "sample_n": 16,
      "by_locale": [
        {
          "locale": "en",
          "printing": null,
          "eur": 556.23,
          "as_of": "2026-09-02",
          "sample_n": 16
        },
        {
          "locale": "fr",
          "printing": null,
          "eur": 900.58,
          "as_of": "2026-09-02",
          "sample_n": 3
        },
        {
          "locale": "de",
          "printing": null,
          "eur": 399.95,
          "as_of": "2026-09-02",
          "sample_n": 5
        },
        {
          "locale": "es",
          "printing": null,
          "eur": 1313.14,
          "as_of": "2026-09-02",
          "sample_n": 8
        },
        {
          "locale": "it",
          "printing": null,
          "eur": 400.64,
          "as_of": "2026-09-02",
          "sample_n": 11
        }
      ]
    },
    "quotes": [
      {
        "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"
      }
    ]
  },
  "meta": {
    "quotes": 5,
    "delayed_hours": 0
  }
}

Why it starts at the Growth plan

Graded quotes, the complete price history and the movers feed are what a serious valuation or analytics product is built on, and they come together with Growth (€79/month, 200,000 credits a month). On the trial and on Developer the raw-card rows are complete and the graded ones are named as withheld, so you can build the whole integration first and switch the plan when the graded view ships.

Reading a slab into your app

Filter with ?variant=MEDIAN_GRADED for the guide medians, or take every row and group by grading.company and grading.score; a null company is a grade band that applies to any grader. Scores are strings, not numbers, because half grades exist. The valid companies are listed by GET /v1/reference under grading_companies, so a dropdown never offers a company the data does not know.

Centering, before the slab exists

A card that has not been graded yet can still be measured: the centering route reads the borders from a photo and, once a person confirms the lines, returns the centering-only grade ceiling for PSA, BGS, CGC and SGC. It is a ceiling from centering alone, not a prediction of the final grade, and it starts on Growth.

What it is not

Read this before you build on it. Every line here is a support ticket somebody else already opened.

Asking is not sold
Some graded rows are what sellers want today for that company and grade, not completed sales; the row says so in basis. Mixing them with guide medians overstates a collection.
Not every card, not every grade
Graded rows exist where the sources publish them: deep on vintage and chase cards, sparse or absent on bulk. An empty result on a Growth key means no data, and meta.withheld will be absent to confirm it.
USD only
Both graded sources publish in dollars. Nothing is converted, and the EUR index leaves graded rows out.

Questions people ask before signing up

Can I get PSA 10 prices for Pokémon cards from an API?
Yes. Price rows carry grading.company and grading.score, so a PSA 10 is its own row, separate from PSA 9 and from the raw card. Graded rows are served from the Growth plan. Pricing
Which grading companies are covered?
PSA, BGS, CGC, SGC, ACE and TAG are the values the API knows; which of them has rows for a given card depends on what the sources publish. GET /v1/reference lists them under grading_companies.
Are graded prices based on sold listings?
It depends on the row, and the row says which: basis GUIDE marks a median built on sales history, basis ASKING marks what sellers are currently asking. The two are never merged, and the methodology page explains each source. Price methodology
Can I test graded prices on the trial?
The trial returns the raw-card rows and tells you graded rows were withheld, so you can build and test the parsing. The graded rows themselves need Growth.

Keep reading