Discover/PSA Card API
live

PSA Card APIpsacard.com

Access PSA certification details, population reports, and price guide data for graded trading cards and collectibles via a structured REST API.

Endpoints
5
Updated
2mo ago

What is the PSA Card API?

This API provides 5 endpoints covering PSA-graded collectible data, including certification lookups, population report searches, and price guide queries. The get_cert_details endpoint returns card name, grade, images, and population counts for any PSA certification number, while search_population_report and get_population_report_by_set expose grading distribution data across all grade levels (1–10) for every tracked card set.

Try it
PSA Certification Number (e.g., '12345678')
api.parse.bot/scraper/311daf8c-242f-4c68-af70-b50617fd1d13/<endpoint>
Ready to send
Fill in the parameters and hit sign in to send to see live response data here.
Call it over HTTPgrab a free API key at signup
curl -X GET 'https://api.parse.bot/scraper/311daf8c-242f-4c68-af70-b50617fd1d13/get_cert_details?cert_number=12345678' \
  -H 'X-API-Key: $PARSE_API_KEY'
Python SDK · recommended

Typed, relational, agent-ready

A generated client with real types, enums, and the links between objects — the structure a flat JSON response can't carry. Autocompletes in your editor and reads cleanly to coding agents.

  • Fully typed · autocompletes
  • Objects link to objects
  • Typed errors & pagination

Typed Python client. Set up the SDK in your uv project, then pull this API’s typed client:

uv add parse-sdk
uv run parse init
uv run parse add --marketplace psacard-com-api

uv run parse add --marketplace pulls a pinned snapshot of this canonical API — it won’t change underneath you. To customize it, subscribe and swap to your own copy.

"""
PSA Card API Client
Get your API key from: https://parse.bot/settings
"""

import os
import requests
from typing import Optional, Dict, Any, List


class ParseClient:
    """Client for interacting with the PSA Card API via Parse Bot."""
    
    def __init__(self, api_key: Optional[str] = None):
        """
        Initialize the Parse API client.
        
        Args:
            api_key: API key from https://parse.bot/settings
                    Falls back to PARSE_API_KEY environment variable
        """
        self.base_url = "https://api.parse.bot"
        self.scraper_id = "311daf8c-242f-4c68-af70-b50617fd1d13"
        self.api_key = api_key or os.getenv("PARSE_API_KEY")
        
        if not self.api_key:
            raise ValueError("API key required. Pass api_key or set PARSE_API_KEY env var")
    
    def _call(self, endpoint: str, method: str = "POST", **params) -> Dict[str, Any]:
        """
        Make an API call to the Parse Bot scraper.
        
        Args:
            endpoint: API endpoint name
            method: HTTP method (GET or POST)
            **params: Query/body parameters
            
        Returns:
            Response JSON as dictionary
        """
        url = f"{self.base_url}/scraper/{self.scraper_id}/{endpoint}"
        headers = {
            "X-API-Key": self.api_key,
            "Content-Type": "application/json"
        }
        
        if method.upper() == "GET":
            response = requests.get(url, headers=headers, params=params)
        else:
            response = requests.post(url, headers=headers, json=params)
        
        response.raise_for_status()
        return response.json()
    
    def get_cert_details(self, cert_number: str) -> Dict[str, Any]:
        """
        Retrieve details for a specific PSA certification number.
        
        Args:
            cert_number: PSA Certification Number (e.g., 69225215)
            
        Returns:
            Dictionary containing card_title, grade, population, images, etc.
        """
        return self._call("get_cert_details", method="GET", cert_number=cert_number)
    
    def search_population_report(
        self, 
        query: str, 
        category_id: int = 0
    ) -> Dict[str, Any]:
        """
        Search the PSA Population Report by keyword.
        
        Args:
            query: Search keyword (e.g., set name or player name)
            category_id: Optional Category ID filter (e.g., 156940 for TCG)
            
        Returns:
            Dictionary with success flag and data array of matching sets
        """
        return self._call(
            "search_population_report",
            method="GET",
            query=query,
            category_id=category_id
        )
    
    def get_population_report_by_set(
        self,
        set_id: str,
        category_id: str,
        start: int = 0,
        length: int = 300,
        search: str = ""
    ) -> Dict[str, Any]:
        """
        Retrieve the full population report for a specific card set.
        
        Args:
            set_id: Heading ID for the set (e.g., 57801)
            category_id: Category ID (e.g., 156940)
            start: Pagination offset
            length: Number of records to return
            search: Search filter within the set
            
        Returns:
            Dictionary with population data for all cards in the set
        """
        return self._call(
            "get_population_report_by_set",
            method="GET",
            set_id=set_id,
            category_id=category_id,
            start=start,
            length=length,
            search=search
        )
    
    def search_price_guide(
        self,
        query: str,
        category_id: int = 0
    ) -> Dict[str, Any]:
        """
        Search the PSA Price Guide by keyword.
        
        Args:
            query: Search keyword
            category_id: Optional Category ID filter
            
        Returns:
            Dictionary with success flag and data array of matching items
        """
        return self._call(
            "search_price_guide",
            method="GET",
            query=query,
            category_id=category_id
        )
    
    def browse_population_categories(self) -> Dict[str, Any]:
        """
        Browse available population report categories.
        
        Returns:
            Dictionary with categories array containing name, url, and slug
        """
        return self._call("browse_population_categories", method="GET")


if __name__ == "__main__":
    client = ParseClient()
    
    print("=" * 80)
    print("PSA Card API - Practical Workflow: Pokemon Card Population Analysis")
    print("=" * 80)
    
    # Step 1: Browse categories to understand the data structure
    print("\n[Step 1] Browsing available collectible categories...")
    categories_resp = client.browse_population_categories()
    categories = categories_resp.get("categories", [])
    print(f"✓ Found {len(categories)} categories")
    
    tcg_category = next((c for c in categories if c.get("slug") == "tcg-cards"), None)
    if tcg_category:
        tcg_id = tcg_category["category_id"]
        print(f"  → Using TCG category ID: {tcg_id}")
    
    # Step 2: Search for a specific card set
    print("\n[Step 2] Searching for 'Pokemon Base Set' in population report...")
    search_resp = client.search_population_report(
        query="Pokemon Base Set",
        category_id=156940
    )
    
    if search_resp.get("success") and search_resp.get("data"):
        sets = search_resp["data"]
        print(f"✓ Found {search_resp['recordsTotal']} matching results")
        
        # Filter for the exact Base Set
        base_set = next(
            (s for s in sets if "base" in s["SetName"].lower() and "1999" in s["YearIssued"]),
            sets[0]
        )
        
        set_id = str(base_set["HeadingID"])
        set_name = base_set["SetName"]
        year = base_set["YearIssued"]
        category_id = str(base_set["SportCategoryID"])
        
        print(f"  → Selected: {set_name} ({year})")
        print(f"  → Set ID: {set_id}, Category ID: {category_id}")
        
        # Step 3: Get population data for this set
        print(f"\n[Step 3] Retrieving population data for '{set_name}'...")
        pop_resp = client.get_population_report_by_set(
            set_id=set_id,
            category_id=category_id,
            start=0,
            length=50
        )
        
        if pop_resp.get("data"):
            cards = pop_resp["data"]
            total_cards = pop_resp.get("recordsTotal", 0)
            print(f"✓ Retrieved {len(cards)} cards (total in set: {total_cards})")
            
            # Step 4: Analyze population statistics
            print(f"\n[Step 4] Analyzing top 5 most graded cards by population...")
            print("-" * 80)
            
            # Sort by total population
            top_cards = sorted(
                cards,
                key=lambda x: int(x.get("Total", 0)),
                reverse=True
            )[:5]
            
            for idx, card in enumerate(top_cards, 1):
                subject = card.get("SubjectName", "Unknown")
                card_num = card.get("CardNumber", "?")
                total = int(card.get("Total", 0))
                grade_10 = int(card.get("Grade10", 0))
                grade_9 = int(card.get("Grade9", 0))
                grade_8 = int(card.get("Grade8", 0))
                
                gem_mint_pct = (grade_10 / total * 100) if total > 0 else 0
                mint_pct = (grade_9 / total * 100) if total > 0 else 0
                
                print(f"\n{idx}. {subject} (Card #{card_num})")
                print(f"   Total Graded: {total:,}")
                print(f"   Grade 10 (Gem Mint): {grade_10:,} ({gem_mint_pct:.1f}%)")
                print(f"   Grade 9 (Mint):     {grade_9:,} ({mint_pct:.1f}%)")
                print(f"   Grade 8 (VF-NM):    {grade_8:,}")
            
            # Step 5: Search price guide for this set
            print(f"\n[Step 5] Searching PSA Price Guide for '{set_name}'...")
            price_resp = client.search_price_guide(
                query=set_name,
                category_id=156940
            )
            
            if price_resp.get("success") and price_resp.get("data"):
                price_entries = price_resp["data"]
                print(f"✓ Found {price_resp['recordsTotal']} price guide entries")
                
                # Show top matching price entries
                print("\nTop 3 matching price guide entries:")
                for idx, entry in enumerate(price_entries[:3], 1):
                    entry_name = entry.get("SetName", "Unknown")
                    entry_category = entry.get("CategoryName", "N/A")
                    entry_url = entry.get("url", "N/A")
                    print(f"  {idx}. {entry_name}")
                    print(f"     Category: {entry_category}")
            
            # Step 6: Find a specific high-value card and get cert details
            print(f"\n[Step 6] Example: Getting certification details...")
            print("-" * 80)
            
            # For demonstration, use a known cert number (from API spec example)
            example_cert = "69225215"
            print(f"Retrieving details for certification #{example_cert}...")
            
            try:
                cert_resp = client.get_cert_details(cert_number=example_cert)
                
                if cert_resp.get("status") == "success":
                    cert_data = cert_resp.get("data", {})
                    cert_num = cert_data.get("cert_number", "Unknown")
                    card_title = cert_data.get("card_title", "Unknown")
                    grade = cert_data.get("grade", "Unknown")
                    population = cert_data.get("population", "N/A")
                    
                    print(f"✓ Cert #{cert_num}")
                    print(f"  Card: {card_title}")
                    print(f"  Grade: {grade}")
                    print(f"  Population: {population}")
                    
                    images = cert_data.get("images", [])
                    if images:
                        print(f"  Images: {len(images)} available")
                else:
                    print("  (Example cert details would appear here)")
            except Exception as e:
                print(f"  (Demo mode - cert lookup may be rate-limited or require direct lookup)")
    
    print("\n" + "=" * 80)
    print("Workflow complete! You now have:")
    print("  • Population statistics for the set")
    print("  • Grade distribution analysis")
    print("  • Price guide references")
    print("  • Individual certification details")
    print("=" * 80)
All endpoints · 5 totalmissing one? ·

Retrieve details for a specific PSA certification number. Returns card name, grade, population data, and images. Note: This endpoint may be subject to Cloudflare challenges.

Input
ParamTypeDescription
cert_numberrequiredstringPSA Certification Number (e.g., '12345678')
Response
{
  "type": "object",
  "fields": {
    "year": "string",
    "brand": "string",
    "grade": "string",
    "images": "array",
    "subject": "string",
    "card_title": "string",
    "population": "string",
    "cert_number": "string"
  },
  "sample": {
    "data": {
      "url": "https://www.psacard.com/cert/69225215/psa",
      "cert_number": "69225215"
    },
    "status": "success"
  }
}

About the PSA Card API

Certification Lookups and Population Data

The get_cert_details endpoint accepts a PSA certification number (e.g., '12345678') and returns structured data including year, brand, subject, card_title, grade, population, cert_number, and any associated images. This is the primary way to verify a single graded card's details. Note that this endpoint may encounter Cloudflare challenges, which can affect response availability.

Population Report Search and Set-Level Data

search_population_report takes a query string — such as a set name like '1999 Pokemon Game' or a player name like 'Mickey Mantle' — and returns an array of matching card sets. Each result includes HeadingID, SetName, YearIssued, SportCategoryID, and CategoryName. The HeadingID and SportCategoryID values feed directly into get_population_report_by_set, which returns per-card grading counts (Grade1 through Grade10) alongside SubjectName, CardNumber, SpecID, and Variety. Pagination is supported via start and length parameters, and an optional search string filters results within a set.

Price Guide and Category Browsing

search_price_guide accepts the same query and optional category_id inputs as the population search, returning matching entries with SMRSetID, SetName, SportCategoryID, CategoryName, and SetNameSEO. For both price guide and population searches, setting category_id to 0 searches across all categories — non-zero values may return empty results. browse_population_categories requires no inputs and returns the full list of collectible categories, each with name, url, slug, and category_id, which can be used to understand available scopes across the other endpoints.

Reliability & maintenance

The PSA Card API is a managed, monitored endpoint for psacard.com — not a raw scraper you maintain. Every endpoint is automatically health-checked on a schedule, and when psacard.com changes and a check fails, the API is automatically queued for repair and re-verified. It is built to keep working as the site underneath it changes.

This isn't an official psacard.com API — it's an independent, maintained REST wrapper over public data. Where the source has no official API (or only a limited one), Parse gives you a stable contract over a source that never promised one, and keeps it current. Need a new endpoint or field? You can revise it yourself in plain English and the agent rebuilds it against the live site in minutes — contributing the change back to the shared API is free.

Will this API break when the source site changes?+
It's built not to. Every endpoint is health-checked on a schedule with automated test probes. When the source site changes and a check fails, the API is automatically queued for repair and re-verified — that's the self-healing layer. Each API page shows when its endpoints were last verified. And because marketplace APIs are shared, any fix reaches everyone using it.
Is this an official API from the source site?+
No — Parse APIs are independent, managed REST wrappers over publicly available data. That is the point: where a site has no official API (or only a limited one), Parse gives you a maintained, monitored endpoint for that data and keeps it working as the site changes — so you get a stable contract over a source that never promised one.
Can I fix or extend this API myself if I need a new endpoint or field?+
Yes — and you don't have to wait on us. This API was generated by the Parse agent, which stays attached. Describe the change in plain English ("add an endpoint that returns reviews", "fix the price field") in the revise box on the API page or via the revise_api MCP tool, and the agent rebuilds it against the live site in minutes. Contributing the change back to the public API is free.
What happens if I call an endpoint that has an issue?+
Errors are machine-readable: a bad call returns a clean status with the list of available endpoints and a repair hint, so an agent (or you) can recover or trigger a fix instead of failing silently. Confirmed failures feed the automatic repair queue.
Common use cases
  • Verify authenticity and grade of a PSA-certified card using its certification number via get_cert_details
  • Build a collection tracker that pulls grade-level population counts for specific card sets using get_population_report_by_set
  • Compare population scarcity across PSA 9 and PSA 10 grades to inform buying and selling decisions
  • Search price guide entries for a specific player or set to surface SMRSetID links for pricing research
  • Enumerate all collectible categories via browse_population_categories to build a structured navigation or filtering UI
  • Monitor population changes for vintage baseball or Pokemon card sets by periodically querying search_population_report
  • Cross-reference HeadingID values from population searches to retrieve full grading distributions for investment analysis
Pricing & limitsSee full pricing →
TierPriceCredits/monthRate limit
Free$0/mo1005 req/min
Hobby$30/mo1,00020 req/min
Developer$100/mo5,000100 req/min

One credit = one API call regardless of which marketplace API you call. Exceeding the rate limit returns a 429 response. Authenticate with the X-API-Key header.

Frequently asked questions
Does PSA have an official developer API?+
PSA does not publish a general-purpose public developer API for population report or certification data. The PSA Cert Verification tool is available on psacard.com as a consumer-facing feature, but there is no documented REST API with keys, documentation, or terms for developer use.
What exactly does get_population_report_by_set return, and how do I get the inputs for it?+
It returns an array of card entries for a specific set, each with SpecID, SubjectName, CardNumber, Variety, and numeric grade counts from Grade1 through Grade10. You obtain the required set_id from the HeadingID field and category_id from the SportCategoryID field returned by search_population_report. Pagination is controlled via start and length.
Does the API return actual sale prices or price history for graded cards?+
Not currently. The search_price_guide endpoint returns set-level metadata including SMRSetID, SetName, and SetNameSEO, but does not return individual card prices or historical price data. You can fork this API on Parse and revise it to add an endpoint targeting per-card price data from the PSA Price Guide.
Are there any known limitations with the get_cert_details endpoint?+
Yes. The endpoint may be subject to Cloudflare challenges on the PSA side, which can result in failed or delayed responses for specific certification lookups. This is noted in the endpoint description. The other endpoints — population report search and price guide search — do not carry this warning.
Can I filter population report searches by category to narrow results?+
A category_id parameter is available on both search_population_report and search_price_guide, but non-zero values may return empty results. The reliable approach is to use category_id: 0 (the default) to search across all categories, then use the CategoryName and SportCategoryID fields in the response to identify the right category for downstream filtering with get_population_report_by_set.
Page content last updated . Spec covers 5 endpoints from psacard.com.
Related APIs in MarketplaceSee all →
pricecharting.com API
Access collectible pricing data from PriceCharting.com. Search for Pokémon cards, US coins, and other collectibles to retrieve current prices across multiple grades (ungraded, PSA 9, PSA 10, MS62, MS66, and more), browse full set listings, view historical price trends, and explore recent sold listings.
pmgnotes.com API
Access detailed grading population data for banknotes across different countries, series, and denominations, plus stay updated with the latest news from PMGnotes.com. Search and retrieve comprehensive census information to track banknote rarity, grades, and collecting trends.
beckett.com API
Search and retrieve trading card news articles from Beckett.com across non-sport and vintage card categories, including checklists and set information. Browse posts by category or look up specific articles to stay updated on the latest card market trends and releases.
tcdb.com API
Browse and retrieve detailed trading card information from the Trading Card Database (TCDB), including set listings, checklists, parallel variants, and special notations such as rookie cards, short prints, and variations. Search across sports and years to access comprehensive card and set metadata.
pokemontcg.com API
Search and browse detailed information about Pokémon Trading Card Game cards and sets, including card types, rarities, and supertypes. Filter cards by set, rarity, and attributes to find exactly what you're looking for in the TCG database.
pkmncards.com API
Search and browse Pokémon trading card game cards with detailed metadata, high-quality images, and advanced filtering by set, Pokémon name, or card type. Filter across thousands of cards from different sets to find exactly what you're looking for.
snkrdunk.com API
Access data from snkrdunk.com.
tcgplayer.com API
Search for trading cards across all games and sets on TCGPlayer, and instantly access detailed pricing information by condition plus current seller listings with prices, shipping costs, and seller ratings. Compare card values and find the best deals from multiple sellers all in one place.