---
title: "/v2/cards — JustTCG API"
description: "Preview of the /v2/cards beta: graded cards (PSA, BGS, CGC) as first-class variants, plus multi-region localized pricing via a per-variant markets array."
url: https://justtcg.com/docs/api/cards-v2
markdown_url: https://justtcg.com/docs/api/cards-v2.md
---

# /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)](https://justtcg.com/docs/api/cards), which is unaffected and remains the recommended endpoint for production use.

`GET https://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](https://justtcg.com/docs/api/games) and [/sets](https://justtcg.com/docs/api/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](https://justtcg.com/docs/api/cards-v2#v2-migration). Wondering when v2 leaves beta, or why a slab has no price yet? See the [FAQ](https://justtcg.com/docs/api/cards-v2#v2-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](https://justtcg.com/docs/api/cards-v2/regions) for how the `markets` array works.

**`regions`** · string · Optional · Default: `NA` · Values: `NA`, `EU`, `UK`, `JP`, `OCE`, `LATAM`, `OTHER`, `…ISO country codes`

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.

Graded cards (PSA, BGS, CGC, and more) are one of the biggest additions in v2 — see [Graded cards](https://justtcg.com/docs/api/cards-v2/graded) for how they fit into the response shape. The params below are how you request them.

### Response

```typescript
// 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
// 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](https://justtcg.com/docs/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](https://justtcg.com/docs/api/cards-v2/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](https://justtcg.com/docs/api/cards-v2/regions) 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](https://justtcg.com/docs/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](https://justtcg.com/partner)

**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](https://justtcg.com/docs/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](https://justtcg.com/docs/api/cards-v2/graded) · [Variant object (v2)](https://justtcg.com/docs/schema/variant#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](https://justtcg.com/docs/thin-markets)

**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](https://justtcg.com/partner)

**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](https://justtcg.com/docs/changelog) · [Migrating from v1](https://justtcg.com/docs/api/cards-v2#v2-migration)

**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](https://justtcg.com/docs/api/cards-v2/regions)

**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](https://justtcg.com/docs/api/cards-v2/batch) · [Changelog](https://justtcg.com/docs/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](https://justtcg.com/docs/schema/variant#v2-periods) · [Changelog](https://justtcg.com/docs/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](https://justtcg.com/docs/api/cards-v2) · [Changelog](https://justtcg.com/docs/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](https://justtcg.com/docs/api/cards-v2#v2-cost)
