Documentation

/v2/cards

Beta

Preview of the next cards API: graded cards and multi-region localized pricing.

Looking for the stable API?This is the v2 beta. Every existing integration should stay on /cards (v1), which is unaffected and remains the recommended endpoint for production use.
GEThttps://api.justtcg.com/v2/cards
Beta:/v2/cards is in beta. The shape below is finalized but may still change before general availability. For now, v2 covers the /cards endpoint only, so /games and /sets stay on v1. Your existing v1 integration keeps working exactly as before, so you can adopt v2 one call at a time, at your own pace. New to v2? See migrating from v1. Wondering when v2 leaves beta, or why a slab has no price yet? See the FAQ.
card_id
uuid | slug
Optional
Look up one card directly. Accepts either a UUID or a legacy v1 slug.
variant_id
uuid | slug
Optional
Look up one variant directly. This is the fastest lookup. Accepts either a UUID or a legacy slug.
tcgplayer_id
string
Optional
Look up one card by its TCGplayer product ID, same as v1's tcgplayerId.
tcgplayer_sku_id
string
Optional
Look up one variant by its TCGplayer SKU ID, same as v1's tcgplayerSkuId.
gtin
string
Optional
Look up sealed product by its retail barcode (GTIN): a 12-digit UPC-A, a 13-digit EAN-13 or JAN, an 8-digit EAN-8, or a 14-digit GTIN-14. Spaces and hyphens are ignored, and a UPC-A with or without a leading zero is the same code. A code with an invalid check digit returns an invalid-parameter problem response; a valid code we don't recognize returns 404. One barcode can match more than one product, so data may hold several cards. Has the lowest precedence of the identifiers and cannot be combined with graded.

Prices are quoted per region instead of converted between currencies — see Localized pricing for how the markets array works.

regions
string
Optional
A comma-separated list of region codes, listed in priority order (not case sensitive). The first region is your primary market. Currently only NA returns data; the other codes are reserved for regions rolling out later. Each region beyond the first adds to the request cost.
Default: NA
NAEUUKJPOCELATAMOTHER…ISO country codes

Graded cards (PSA, BGS, CGC, and more) are one of the biggest additions in v2 — see Graded cards for how they fit into the response shape. The params below are how you request them.

Response

typescript — request.ts
// BETA: the justtcg-js SDK gains /v2 support after v2 ships. Until then, call
// the endpoint directly. Note: 'regions' defaults to NA and 'markets' replaces
// the flat price fields.
const params = new URLSearchParams({
  game: "pokemon",
  regions: "eu,na",   // optional (defaults to NA); first region is the primary market
  graded: "include",  // raw + graded variants (premium tier)
});

const res = await fetch(`https://api.justtcg.com/v2/cards?${params}`, {
  headers: { "x-api-key": process.env.JUSTTCG_API_KEY! },
});
const { data } = await res.json();

// price now lives per-region under markets[]
const eu = data[0].variants[0].markets.find((m) => m.region === "EU");
console.log(eu?.price, eu?.currency);
json — response · 200 OK (BETA)
response · 200 OK (BETA)
// GET /v2/cards?game=pokemon&regions=eu,na&graded=include&include=periods.7d,price_history.7d
// 200 OK
// RateLimit: limit=500000, remaining=499958, reset=...
// Link: <https://api.justtcg.com/v2/cards?...&cursor=eyJvIjoyMH0>; rel="next"
{
  "data": [
    {
      "id": "9b2e4d1a-1111-5v5v-aaaa-000000000000",   // card UUID (native)
      "slug": "pokemon-base-set-charizard-holo-rare",   // legacy v1 id
      "name": "Charizard",
      "game": { "id": "pokemon", "name": "Pokemon" },
      "set": { "id": "base-set-pokemon", "name": "Base Set" },
      "number": "4",
      "rarity": "Rare Holo",
      "external_ids": { "tcgplayer": "42445", "scryfall": null, "mtgjson": null },
      "variants": [
        {
          "id": "d41f-raw-uuid",
          "slug": "pokemon-base-set-charizard-holo-rare_near-mint_holofoil",
          "type": "raw",
          "condition": "Near Mint",
          "printing": "Holofoil",
          "language": null,
          "grading": null,
          "markets": [
            {
              "region": "EU",              // markets[0] = primary market (drives sort/filter)
              "currency": "EUR",
              "price": 142.30,             // observed EU price; null if none observed
              "price_status": "current",
              "last_price": null,
              "updated_at": 1765635970,
              "change_24h_pct": -1.8,
              "periods": {
                "7d": { "change_pct": -3.1, "avg": 148.0, "min": 140.0, "max": 151.2, "changes_count": 10, "sales_count": 12 }
              },
              "price_history": [
                { "t": 1765550000, "p": 150.0 },
                { "t": 1765636400, "p": 142.3 }
              ]
            },
            {
              "region": "NA",             // requested explicitly as the USD fallback reference
              "currency": "USD",
              "price": 155.00,
              "price_status": "current",
              "last_price": null,
              "updated_at": 1765635970,
              "change_24h_pct": -0.9,
              "periods": {
                "7d": { "change_pct": -2.4, "avg": 158.0, "min": 151.0, "max": 162.0, "changes_count": 11, "sales_count": 14 }
              },
              "price_history": [
                { "t": 1765550000, "p": 160.0 },
                { "t": 1765636400, "p": 155.0 }
              ]
            }
          ]
        },
        {
          "id": "d41f-psa10-uuid",
          "slug": "d41f-psa10-uuid",
          "type": "graded",
          "condition": null,
          "printing": "Holofoil",
          "language": null,
          "grading": {
            "company": "PSA", "grade": 10, "grade_label": null,
            "qualifier": null, "canonical": "PSA 10"
          },
          "markets": [
            { "region": "EU", "currency": "EUR", "price": null,    "price_status": "unpriced", "last_price": null, "updated_at": 1765630000, "change_24h_pct": null, "periods": {}, "price_history": [] },
            { "region": "NA", "currency": "USD", "price": 4570.00, "price_status": "current", "last_price": null, "updated_at": 1765630000, "change_24h_pct": 0.4,  "periods": { "7d": { "change_pct": 1.2, "avg": 4480.0, "changes_count": 2, "sales_count": 3 } }, "price_history": [] }
          ]
        }
      ]
    }
  ]
}

Two time signalsBeta

  • price_history is the { t, p } series of observed market prices over time. It is exactly what v1 exposed, so your existing charts keep working, and it is now scoped to each market entry.
  • periods holds aggregate statistics (average, min and max, standard deviation, trend, and more), now grouped by time window. The fields line up one-to-one with v1's flat stats and carry the same values, so anything you computed in v1 continues to match.

Request costBeta

Most requests cost the same as v1. Two optional features add to that cost, and they are easy to spot below. If you use neither, you pay the base rate.

Not charged during the beta:While /v2/cards is in beta, neither surcharge is applied. Every v2 request, including graded=include and extra regions, costs the same as the equivalent v1 call. The schedule below is what takes effect at general availability, and it will be announced in the changelog before it switches on.
Base rate

One region, raw OR graded variants only

The same cost as the equivalent v1 call. This is what you pay by default.

Two features add a surcharge

Both raw & graded+1 Surcharge
graded=include

Returning raw and graded variants together adds a surcharge. Asking for just one kind (graded=exclude or graded=only) stays at normal cost.

Extra regions+1 Surcharge
regions=na,eu,…

Each region beyond the first is a separate price lookup, so it adds cost. This applies as more regions come online.

Surcharges stack: use both features and both apply.

Migrating from v1Beta

v1 does not change. v2 is a new, additional path you can opt into. The defaults already reproduce v1 pricing:regions falls back to NA and graded defaults to exclude, so a plain request returns the same raw NA pricing as v1. The identifier rename is covered in full on the Identifiers page.

What changes for an existing integrationThe call-outs that matter when you adopt /v2/cards
  • id is now the UUID. The old slug moves to a field called slug. This is the single biggest rename.
  • regions defaults to NA. That matches v1's US pricing, and NA is the only region live today.
  • price & price_history now live inside markets[]. With regions=NA, markets[0] is the drop-in replacement.
  • There is no more offset pagination. Use the cursor from the Link header instead.
  • There is no standalone us_price field. Request NA and read that market entry.
  • The flat stat fields move into periods. They are regrouped by time window under markets[].periods, with the same values as v1.
  • printing now holds the print type only. The " - <Language>" suffix has been removed, so read language separately.
  • Absent is not the same as null. v2 leaves out fields you did not request instead of sending them as null. The one exception is a market's price.
  • Errors now use application/problem+json. Update any parser that keys on { error, code }.

Frequently asked questionsBeta

The questions we are asked most often about the v2 beta: why it exists, when it leaves beta, and why graded and regional coverage is still filling in.

Why is /v2/cards in beta, and who is it for right now?

v2 opened in beta earlier than a typical release because its first users are the stores in the JustTCG Partner Program. Graded prices in v2 are built from recorded sales, and a store can only report a graded sale once its own systems can address graded cards the way v2 does: as first-class variants with stable identifiers. Opening the beta early gives those stores a finished contract to integrate against while the data behind it is still filling in. Developers are welcome to build on v2 today, with the understanding that coverage is still growing and the beta label reflects the data more than the API.

See: JustTCG Partner Program

When will v2 leave beta?

There is no fixed date. The request and response shape is essentially final, so what gates general availability is data coverage and quality, not the API. Coverage grows as more partner stores integrate and report sales, and we would rather reach GA with a dataset that holds up than with a date on a calendar. The status change will be announced in the changelog. Existing v1 integrations are unaffected either way.

See: Changelog

Why does a graded card have no price, and why is graded coverage incomplete?

A graded variant exists for every card, grading company and grade, but its price stays null until a sale of that exact slab has been recorded. Graded prices are built from real sales, not from estimates or multipliers on the raw price, so coverage is deepest for slabs that trade often and thinnest for rare ones. An unpriced variant is not an error: in browse and search responses it carries price: null and price_status: "unpriced", while a direct lookup of a variant that has never been priced returns a 404 problem response. Coverage fills in as partner stores report sales, and your integration needs no change when it does.

See: Graded cards · Variant object (v2)

Why do some graded prices carry an old updated_at?

updated_at is when that slab's price was last observed. Graded prices do not expire the way high-value raw prices do, because a slab that trades a few times a year is normal rather than a sign of a stale market. Treat updated_at as part of the price: if your use case needs freshness, weight or discount older observations in your own logic.

See: Thin markets and price expiry

How can I get coverage for the cards I care about sooner?

Coverage follows reported sales. If you run a store, or build for one, joining the JustTCG Partner Program is the most direct way to improve it: sales reported by a partner feed graded and regional prices for the region they were sold in, and partners get API access in return. If you are a developer without a store, the best move is to build against the v2 shape now. Nothing in your integration changes when a slab or a region gets its first price.

See: JustTCG Partner Program

Will the response shape change before general availability?

The shape is finalized and we treat it as stable. The beta label keeps room for a correction if real-world data exposes a problem, and any change that could break a client is announced in the changelog before it takes effect, with what to update. v1 does not change at all, so you can adopt v2 one call at a time and fall back if you need to.

See: Changelog · Migrating from v1

When do EU, UK, JP and other regions come online?

Each region switches on once it has enough locally observed sales to stand on its own. v2 does not convert US prices into other currencies, so a region is not enabled until its local data meets our coverage threshold. Today NA is the only region with data. The regions parameter and the markets array are already shaped for the rest, so a region arriving changes nothing in your integration. Partner stores outside North America directly accelerate their own region.

See: Localized pricing

Can I fetch many graded cards in one request?

Yes, if you name each slab by variant_id. POST /v2/cards returns graded variants in the v2 shape, priced as slabs. Batch does not accept graded, grading_company or grade, so to find which grades a card has, use a direct GET lookup first.

See: v2 batch · Changelog

Does v2 show how many times a card has sold?

Yes. Every markets[] entry carries sales_count inside periods: the number of sales recorded for that variant in that market over each window (7d, 30d, 90d, 1y and all_time), for raw and graded variants alike. It counts sales, not units, so a sale of several copies counts once, and refunded or voided sales are left out. A window appears only when it holds at least one sale, so read a missing window as zero. Next to it, changes_count is the number of times the sale price changed in the window. Counts are aggregates: individual sale records, such as each sale's price and date, are not part of the API. periods are on by default, and include=periods.30d narrows the response to one window.

See: periods fields · Changelog

Can I look up sealed product by its barcode?

Yes. Pass the code printed under the barcode as gtin, for example gtin=820650850714. It accepts a 12-digit UPC-A, a 13-digit EAN-13 or JAN, an 8-digit EAN-8 and a 14-digit GTIN-14, and ignores spaces and hyphens. If your app already sends whatever the user scans or types to q, a 12 to 14 digit barcode there is matched the same way and falls back to a normal name search when nothing carries it. Barcodes cover sealed product such as booster boxes, Elite Trainer Boxes and bundles, and coverage is growing. One barcode can belong to more than one listing, so a lookup can return several cards.

See: gtin parameter · Changelog

Do graded or multi-region requests cost more during the beta?

No. While /v2/cards is in beta, the surcharges for graded=include and for extra regions are not applied: every v2 request costs the same as the equivalent v1 call. The Request cost section on this page describes the schedule that takes effect at general availability, so you can plan for it now. Surcharges will not switch on silently; the change is announced in the changelog ahead of time.

See: Request cost