Best Pokemon Card APIs in 2026

13 July 2026 | 15 min read

Pokemon card prices move fast. A Charizard ex sitting at $40 on Monday can hit $65 by Friday after a strong tournament showing. If you're building a price tracker, a collection manager, or a flip-finder, you need a Pokémon card API that keeps up.

The good news is that several Pokémon card APIs exist in 2026 that cover the TCG well. The bad news is that each one has a ceiling. When you hit it, the only path forward is scraping the marketplaces directly. This guide covers the most useful APIs, explains where each one falls short, and shows how to scrape PriceCharting for the data no API exposes.

Pokemon cards with price data overlay

Quick answer (TL;DR)

For most Pokémon card pricing workflows, the free APIs are enough. pokemontcg.io covers TCGPlayer and Cardmarket averages. pokemonpricetracker.com adds graded values and PSA population data. tcgapi.net is the only option that bundles PriceCharting prices and eBay comparable sales in a single call.

Live seller listings, marketplace-specific sold-price views, and fuller vintage pricing history often require either a specialized API, licensed data access, or carefully handled scraping. That part is not as simple as it sounds: PriceCharting sits behind Cloudflare and returns a JavaScript challenge to plain HTTP requests. A standard requests call always returns 403. ScrapingBee handles the access layer (headless browser rendering, residential proxy rotation, and structured data extraction) so your code can focus on parsing the data.

Every Pokemon Card API Worth Knowing in 2026

Before writing a line of scraping code, you should check out what the free APIs already cover. Here's a quick summary of the most commonly used APIs:

APIPricesFree tierBest forStrengthsBiggest limitation
pokemontcg.ioTCGPlayer + Cardmarket1,000/day (no key), 20,000/day (free key)Card metadata and standard market pricingMost widely used; flexible query syntax; comprehensive English set coveragePrice data syncs on a schedule and can lag after rapid market moves
tcgdex.devTCGPlayer + Cardmarket (embedded per card)Generous, no published hard limitInternational and non-English set coverage14 languages; REST + GraphQL; 1/7/30-day price trends; no auth requiredCardmarket prices update daily only; best suited for international coverage rather than US-only projects
ScrydexRaw + graded prices, trends, historyNone, starts at $29/mo (~160 req/day)Multi-TCG platforms needing graded data and price historyPopulation reports, historical trends, covers 6 TCGs from one APINo free tier; $29/mo yields only ~160 requests per day
pokewallet.ioTCGPlayer + Cardmarket (real-time)100/hour, 1,000/dayDevelopers wanting a simpler REST alternativeReal-time prices; no credit card required for free tierREST only; 100 requests per hour caps any automated workflow
pokemonpricetracker.comTCGPlayer + PSA/BGS/CGC grading100 credits/dayGraded card tracking and PSA population analysisPSA/BGS/CGC/SGC population reports; gem rates; CSV export; Japanese variantsPopulation and export endpoints require a paid plan
pokemon-api.comTCGPlayer, Cardmarket, eBay graded100 calls/dayMulti-market price aggregation across multiple TCGsCardMarket EUR + eBay USD graded + TCGPlayer USD in one response; covers 6 TCGs100 calls/day is very low for automated workflows
tcgapi.netTCGPlayer + PriceCharting + eBay comps1,000 req/dayeBay sold prices and PriceCharting data without scrapingActual transaction prices via /v1/comps; PriceCharting + TCGPlayer aggregated in one callFree tier (1,000 req/day) is far lower than pokemontcg.io's 20,000/day with a free key; smaller community

1. pokemontcg.io

pokemontcg.io homepage

Despite being merged into Scrydex, pokemontcg.io is the most widely used starting point. The query syntax is flexible: q=name:Charizard filters by name, but you can combine fields like q=name:Charizard set.id:base1 rarity:"Rare Holo" to narrow to a specific printing.

Each card response includes the full set metadata, format legality, and a nested tcgplayer object with a prices dict keyed by printing type (holofoil, reverseHolofoil, normal) and then by price point (low, mid, high, market, directLow). You can use market for fair-value comparisons. Without an API key, you get 1,000 requests a day and 30 per minute; a free key raises that to 20,000 a day by default.

2. tcgdex.dev

tcgdex.dev homepage

tcgdex.dev supports 14 languages and covers international sets that pokemontcg.io handles less thoroughly, including Japanese, French, German, Italian, and Portuguese releases. It offers both REST and GraphQL endpoints, and card objects include HP, types, attacks, weaknesses, stages, retreat cost, and illustrator credits.

It also now ships pricing data embedded directly in every card response under a pricing field pulling TCGPlayer (updated hourly) and Cardmarket (updated daily), with 1/7/30-day trend averages included. If your project needs non-English set coverage for card metadata, this is the most complete free option.

3. Scrydex

Scrydex homepage

Scrydex is built by the same team behind pokemontcg.io, positioned as a paid successor rather than a replacement. Scrydex adds market history, graded prices, and population reports on top of card data, and covers Pokémon, Lorcana, Magic: The Gathering, Gundam, One Piece, and Riftbound from a single API.

There's no free tier: plans start at $29/month for 5,000 credits (roughly 160 requests a day), scaling to $99/month, $399/month, and custom Enterprise pricing, all published at scrydex.com/pricing.

4. pokewallet.io

pokewallet.io homepage

pokewallet.io hits TCGPlayer and markets itself as a developer-friendly alternative to tcgdex and pokemontcg.io. It's REST-only; there's no GraphQL endpoint despite what you might expect from a newer entrant. Its free tier includes 100 requests per hour and 1,000 per day, no credit card required.

5. pokemonpricetracker.com

pokemonpricetracker.com homepage

pokemonpricetracker.com is the deepest option for graded card data. The /api/v2/population endpoint returns PSA, BGS, CGC, and SGC population reports including gem rates (the percentage of submitted copies grading at PSA 10 or BGS 10). The /api/v2/cards endpoint bundles grading stats alongside TCGPlayer prices. The /api/v2/export endpoint lets you download card data in bulk as CSV. Japanese variants and promos are included. The free tier gives 100 credits a day at 60 requests a minute, and the $9.99/month API tier steps up to 20,000 credits a day, which is enough for regular automated pulls of card and price data, just not population or export.

6. pokemon-api.com

pokemon-api.com homepage

pokemon-api.com aggregates CardMarket EUR, eBay USD graded, and TCGPlayer USD prices in a single card response, which reduces the number of API calls needed if you're tracking multiple markets. It covers five TCGs alongside Pokémon: Lorcana, Star Wars, One Piece, LoL Riftbound, and Magic: The Gathering. The free tier allows 100 requests per day and 30 requests per minute, which seems quite decent for hobbyists.

7. tcgapi.net

tcgapi.net homepage

tcgapi.net has two endpoints that no other API matches. The /v1/comps endpoint returns recent eBay comparable sales (actual transaction prices, not just listings). The /v1/value/ endpoint aggregates TCGPlayer and PriceCharting prices in a single call. It also ships with Shopify and eBay integrations for shop owners who want to automate listing prices. The free tier gives 1,000 requests per day.

Pulling card data from the Pokémon TCG API

pokemontcg.io is the most practical starting point. Sign up for a free API key, and you get 1,000 requests per day.

import requests

# you can add your API key below if you have one
# API_KEY = "your_api_key_here"
# HEADERS = {"X-Api-Key": API_KEY}
HEADERS = {}

def get_card(name: str) -> list[dict]:
    resp = requests.get(
        "https://api.pokemontcg.io/v2/cards",
        headers=HEADERS,
        params={"q": f"name:{name}", "pageSize": 10},
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()["data"]

for card in get_card("Charizard"):
    tcgplayer = card.get("tcgplayer", {})
    prices = tcgplayer.get("prices", {})
    updated = tcgplayer.get("updatedAt", "unknown")
    print(f"{card['name']} ({card['set']['name']}) — prices last synced: {updated}")
    print(prices)

Here's what the output would look like:

Pokemon TCG API result

The updatedAt field matters. It tells you when the prices were last synced from TCGPlayer. For a card that spiked two days ago, that timestamp might already be stale.

Where even the best Pokemon card APIs fall short

Every API in that table has at least one of these limitations:

  • Price syncs run on a schedule. A card that jumps after a tournament result won't reflect the new price until the next update cycle.
  • APIs surface market averages. Individual seller listings with condition grades and actual asking prices aren't available through any API.
  • Graded card premiums are either absent or locked behind paid tiers.
  • Sold prices (what a card actually transacted for) are not exposed by any free API. Listed prices and real transaction prices can differ by 20 to 40 percent on high-demand cards.
  • PriceCharting data for vintage cards and sealed product is only partially accessible via tcgapi.net.

For any of those cases, you need to go to the source.

Scraping PriceCharting for Vintage and Graded Prices

PriceCharting is a common reference for loose, graded, and sealed product pricing on vintage Pokémon cards. Its pages are relatively easy to parse once loaded, but plain HTTP requests can still hit Cloudflare challenges, so scraping usually requires browser rendering.

Why a plain request fails

import requests

HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; price-tracker/1.0)"}
resp = requests.get(
    "https://www.pricecharting.com/game/pokemon-base-set/charizard-4",
    headers=HEADERS,
    timeout=10,
)
print(resp.status_code)
print(resp.headers.get("Cf-Mitigated"))

This returns 403 with Cf-Mitigated: challenge, and the body is Cloudflare's "Just a moment..." interstitial. Swapping in a full desktop Chrome header set produces the same result because Cloudflare is running a JavaScript challenge. requests never executes JavaScript, so no header combination gets past it.

Getting through with ScrapingBee

ScrapingBee renders the page in a real headless browser (render_js, on by default) and can route the request through its stealth proxy pool (stealth_proxy=True) for sites that check browser fingerprints beyond just headers. A short js_scenario wait gives the challenge time to resolve before the HTML is captured:

from scrapingbee import ScrapingBeeClient

client = ScrapingBeeClient(api_key="YOUR_API_KEY")

def fetch_pricecharting_page(url: str) -> str | None:
    js_scenario = {"instructions": [{"wait": 2000}]}
    response = client.get(
        url,
        params={
            "js_scenario": js_scenario,
            "stealth_proxy": True,
            "country_code": "us",
        },
    )
    if not response.ok:
        print(f"Request failed: {response.status_code}")
        return None
    return response.text

html = fetch_pricecharting_page(
    "https://www.pricecharting.com/game/pokemon-base-set/charizard-4"
)

stealth_proxy costs more credits than a standard request and only works with JS rendering enabled. Reserve it for pages like this one.

Three ways to pull the actual prices

Once you're past the challenge, the price field IDs are worth learning before you write a single selector. PriceCharting repurposed its old video-game price-guide naming for cards, so the IDs don't match what you'd expect. used_price is Ungraded, complete_price is Grade 7, new_price is Grade 8, graded_price is Grade 9, box_only_price is Grade 9.5, and manual_only_price is PSA 10.

1. Raw HTML plus BeautifulSoup, if you want full control over parsing:

from bs4 import BeautifulSoup

def parse_prices(html: str) -> dict:
    soup = BeautifulSoup(html, "html.parser")

    def extract(field_id: str) -> str | None:
        el = soup.select_one(f"#{field_id} .price")
        return el.text.strip() if el else None

    return {
        "ungraded": extract("used_price"),
        "grade_7": extract("complete_price"),
        "grade_8": extract("new_price"),
        "grade_9": extract("graded_price"),
        "grade_9_5": extract("box_only_price"),
        "psa_10": extract("manual_only_price"),
    }

print(parse_prices(html))

Here's what the result could look like:

Price extraction output result

2. ScrapingBee's built-in extract_rules, which returns structured JSON directly from the same API call with no BeautifulSoup step needed:

response = client.get(
    "https://www.pricecharting.com/game/pokemon-base-set/charizard-4",
    params={
        "js_scenario": {"instructions": [{"wait": 2000}]},
        "stealth_proxy": True,
        "extract_rules": {
            "ungraded": "#used_price .price",
            "grade_7": "#complete_price .price",
            "grade_8": "#new_price .price",
            "grade_9": "#graded_price .price",
            "grade_9_5": "#box_only_price .price",
            "psa_10": "#manual_only_price .price",
        },
    },
)
print(response.json())

The output will be exactly the same as the previous one, except without needing to use BeautifulSoup or any HTML parser at all:

ScrapingBee's extract rules result

3. AI extraction, which lets you describe the data you want in plain English. This approach is resilient to layout changes, and PriceCharting has already reworked its field naming once:

response = client.get(
    "https://www.pricecharting.com/game/pokemon-base-set/charizard-4",
    params={
        "js_scenario": {"instructions": [{"wait": 2000}]},
        "stealth_proxy": True,
        "ai_selector": "#price_data",
        "ai_extract_rules": {
            "prices": {
                "description": "the price for each grade or condition shown in the price table",
                "type": "list",
                "output": {
                    "grade": "the grade or condition label, e.g. Ungraded, Grade 9, PSA 10",
                    "price": "the price for that grade",
                },
            }
        },
    },
)
print(response.json())

Here's what the output would look like:

ScrapingBee AI extraction output

AI extraction costs 5 extra credits per request on top of the standard call. For high-volume routine pulls, the CSS selectors above are faster and cheaper.

Scraping an entire set

Set listings live at pricecharting.com/console/{set-slug}, for example /console/pokemon-base-set. The card table ID is games_table (with an underscore):

def get_set_slugs(set_slug: str) -> list[str]:
    url = f"https://www.pricecharting.com/console/{set_slug}"
    html = fetch_pricecharting_page(url)
    soup = BeautifulSoup(html, "html.parser")

    slugs = []
    for a in soup.select("table#games_table td.title a"):
        href = a.get("href", "")
        if "/game/" in href:
            slugs.append(href.split("/game/")[-1])
    return slugs

def scrape_set_prices(set_slug: str) -> list[dict]:
    results = []
    for slug in get_set_slugs(set_slug):
        card_html = fetch_pricecharting_page(f"https://www.pricecharting.com/game/{slug}")
        results.append({"card": slug, **parse_prices(card_html)})
    return results

Here's what the output for this script (limited to just the first three cards) would look like:

Set price scraping results

ScrapingBee's proxy pool handles blocking prevention. A small delay between requests keeps you within your plan's concurrency limit, which is the only job sleep needs to do here.

Scraping responsibly

PriceCharting's robots.txt is permissive, it only disallows a handful of specific paths (/stripe-connect, /publish-offer, /buy), not the card and set pages this guide scrapes. The real restriction is in their Terms of Service instead: PriceCharting claims ownership of "Price Data" (current and historical pricing, trends, and related info from their site, API, or downloads) and restricts how it can be used once you have it. Personal research and internal use are fine. Referencing their prices on an external site is allowed if you cite PriceCharting and link back to them. But building any product, app, or system that exposes their Price Data to third parties, customers, clients, or the public requires either a paid "Legendary" subscription for internal business use or express written permission for anything public-facing.

Practically: this guide's approach is fine for a personal tracker or research project you're not redistributing. If you're building something you intend to ship to other users, check PriceCharting's official API first, since that's the licensed path for exactly that use case, rather than relying on scraped data for a public product. Either way, keep request rates low and cache results locally so you're not re-fetching data you already have.

Putting Together A Basic Price Aggregator

The practical pattern is to query the API first and only scrape when the API data is stale or missing.

import requests
from datetime import datetime, timedelta
from bs4 import BeautifulSoup
from scrapingbee import ScrapingBeeClient

POKEMONTCG_API_KEY = "YOUR_POKEMONTCG_API_KEY"
# Optional: raises daily limit from 1k to 20k
SCRAPINGBEE_API_KEY = "YOUR_SCRAPINGBEE_API_KEY"

tcg_headers = {"X-Api-Key": POKEMONTCG_API_KEY} if POKEMONTCG_API_KEY else {}
sb_client = ScrapingBeeClient(api_key=SCRAPINGBEE_API_KEY)


def get_card(name: str) -> list[dict]:
    resp = requests.get(
        "https://api.pokemontcg.io/v2/cards",
        headers=tcg_headers,
        params={"q": f"name:{name}", "pageSize": 10},
        timeout=10,
    )
    resp.raise_for_status()
    return resp.json()["data"]


def fetch_pricecharting_page(url: str) -> str | None:
    response = sb_client.get(
        url,
        params={
            "js_scenario": {"instructions": [{"wait": 2000}]},
            "stealth_proxy": True,
            "country_code": "us",
        },
    )
    if not response.ok:
        print(f"Request failed: {response.status_code}")
        return None
    return response.text


def parse_prices(html: str) -> dict:
    soup = BeautifulSoup(html, "html.parser")

    def extract(field_id: str) -> str | None:
        el = soup.select_one(f"#{field_id} .price")
        return el.text.strip() if el else None

    return {
        "ungraded": extract("used_price"),
        "grade_7": extract("complete_price"),
        "grade_8": extract("new_price"),
        "grade_9": extract("graded_price"),
        "grade_9_5": extract("box_only_price"),
        "psa_10": extract("manual_only_price"),
    }


def get_pricecharting_price(pc_slug: str) -> dict:
    html = fetch_pricecharting_page(f"https://www.pricecharting.com/game/{pc_slug}")
    if html is None:
        return {}
    return parse_prices(html)


def is_fresh(date_str: str, max_age_days: int = 1) -> bool:
    try:
        updated = datetime.strptime(date_str, "%Y/%m/%d")
        return datetime.now() - updated < timedelta(days=max_age_days)
    except (ValueError, TypeError):
        return False


def get_best_price(card_name: str, pc_slug: str) -> dict:
    for card in get_card(card_name):
        tcgplayer_data = card.get("tcgplayer", {})
        prices = tcgplayer_data.get("prices", {})
        updated = tcgplayer_data.get("updatedAt", "")
        if prices and is_fresh(updated):
            return {"source": "pokemontcg-api", "prices": prices}

    # API prices stale or missing — check PriceCharting for vintage or graded data
    pc = get_pricecharting_price(pc_slug)
    if pc.get("ungraded"):
        return {"source": "pricecharting", "prices": pc}

    return {}


print(get_best_price("Charizard", "pokemon-base-set/charizard-4"))

If the API turns out to have stale price information, the script will automatically scrape PriceCharting for the latest prices:

Aggregator run results

From here, you can do a number of things, like:

  1. store results in SQLite with a timestamp column to build a price history
  2. schedule daily pulls with APScheduler or a cron job
  3. trigger alerts when a card crosses a price threshold, etc.

When to Use an API vs. When to Scrape

The APIs work well for card metadata, ballpark pricing, and anything where a same-day sync is close enough. pokemontcg.io covers most projects. pokemonpricetracker.com is the right upgrade if graded card data matters to your use case. tcgapi.net is the only way to get PriceCharting data without scraping it yourself.

Scraping becomes necessary for live marketplace listings, recent sold prices, and condition-level breakdowns by individual sellers. PriceCharting is straightforward to parse with BeautifulSoup after you retrieve the rendered HTML. TCGPlayer requires a headless browser and residential proxies to pass Cloudflare, which ScrapingBee handles transparently.

The combination covers the full picture: reliable baseline data from the APIs, and current market reality from the scraped sources.

What's Next?

The APIs in this guide cover the majority of card metadata and pricing workflows. pokemontcg.io handles most use cases for free. When you need graded card data, pokemonpricetracker.com or tcgapi.net close most of the gaps. When you need live marketplace data or vintage card history that no API exposes, ScrapingBee handles the Cloudflare layer so you can focus on parsing the data rather than fighting the access problem.

If you want to try the PriceCharting scraper from this guide, ScrapingBee's free trial includes 1,000 credits, enough to pull prices across a full vintage set.

FAQ

Is there a free Pokemon card API?

Yes. pokemontcg.io gives you 1,000 requests per day without an API key, or 20,000 per day with a free key. tcgdex.dev requires no authentication and has no published rate limit. Several pricing-focused APIs offer free tiers, though Scrydex is the exception — plans start at $29/month with no free option.

What is the best Pokemon card API for prices?

For TCGPlayer and Cardmarket averages, pokemontcg.io covers most use cases. For graded card values and PSA population data, pokemonpricetracker.com goes deeper. tcgapi.net is the only API that bundles PriceCharting and eBay comp prices alongside TCGPlayer. For live individual seller listings with condition breakdowns, scraping the marketplace directly is the only available path.

Does the Pokemon TCG API include card images?

Yes. pokemontcg.io includes image URLs in each card object. TCGdex and Scrydex return images as well.

Can I get sold listing prices from a Pokemon card API?

For eBay, yes. tcgapi.net's /v1/comps endpoint returns recent eBay comparable sales with actual transaction prices, available on its free tier. For TCGPlayer sold prices, scraping the recent sales tab directly is the only available path (no API currently exposes it).

image description
Kumar Harsh

Kumar Harsh is an indie software developer and DevRel enthusiast. He is a spirited writer who puts together content around popular web technologies like serverless and JavaScript.