Developer reference
One API. A complete data foundation.
Query verified marketplace sales alongside a structured card catalog for identity, discovery, pricing, and market intelligence. The same account powers REST and AI-native MCP tools.
Quickstart
Quickstart
Get your first response in under a minute.
1. Get a free API key
Sign up at thecardapi.com → No credit card required. Your key arrives instantly.
2. Make your first request
curl "https://thecardapi.com/api/v1/market/sales?q=psa+10+ohtani&limit=5" \
-H "x-market-api-key: tca_your_key_here"3. Explore
Use the playground to build and test queries interactively before writing any code. The full parameter reference is in the GET /sales section below.
Authentication
Authentication
All requests require an x-market-api-key header (REST) or an Authorization: Bearer header (MCP). The same key works for both. Get one free — no credit card required.
# REST API
GET /api/v1/market/sales
x-market-api-key: tca_your_key_here
# MCP Server
POST /api/mcp
Authorization: Bearer tca_your_key_hereDon't have a key? Get one free →
Base
Base URL
REST API
https://thecardapi.com/api/v1/marketAll REST paths below are relative to this base.
MCP Server
https://www.thecardapi.com/api/mcpJSON-RPC 2.0 over streamable HTTP. See the MCP Server section.
Endpoints
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /sales | Search and filter sales records |
| GET | /sales/{id} | Single sale by ID |
| GET | /daily | Aggregate stats for a date |
| GET | /platforms | Platform list with last updated date |
| GET | /coverage | Latest data freshness status |
| GET | /sales/export/csv | Download filtered sales as CSV |
| POST | /webhook | Add a webhook endpoint (max 5) — available on all paid plans |
| GET | /webhook | List active webhook endpoints |
| DELETE | /webhook/{endpoint_id} | Remove a webhook endpoint |
MCP tools are invoked via POST /api/mcp — see the MCP Tools section for individual tool references.
GET
GET /sales
Search and filter individual sale records. Results are ordered by date descending by default. Your plan's lookback window is enforced on date_from automatically.
| Parameter | Type | Description | |
|---|---|---|---|
| q | string | optional | Full-text search on listing title (min 4 chars). Supports boolean syntax: (term1,term2) for OR, -term for NOT, "exact phrase" for phrase match. |
| platform | string | optional | Filter by platform. Values: ebay · tcgplayer · goldin · lelands · scp · hakes · rea. See GET /platforms for the full list. |
| listing_type | string | optional | Comma-separated: auction, fixed_price, best_offer |
| date_from | date | optional | Start date YYYY-MM-DD. Clamped to your plan's lookback window. |
| date_to | date | optional | End date YYYY-MM-DD |
| price_min | number | optional | Minimum sale price (USD) |
| price_max | number | optional | Maximum sale price (USD) |
| print_run_min | integer | optional | Minimum print run (e.g. 1 for 1/1 only) |
| print_run_max | integer | optional | Maximum print run (e.g. 99 to filter to /99 and lower numbered cards) |
| shipping_max | number | optional | Maximum buyer shipping cost in USD. Use shipping_max=0 for free-shipping listings only. Filters to records where shipping_price ≤ this value (null shipping_price records are excluded). |
| category | string | optional | Filter by card category: sports, tcg, non_sport. eBay records indexed from June 6, 2026 onward. Auction house sources (Goldin, REA, etc.) return null until backfilled. |
| graded | boolean | optional | graded=true returns only professionally graded cards (any grading company); graded=false returns only raw/ungraded. Works on every platform. Autograph-authentication-only slabs (PSA/DNA, JSA, BAS) count as ungraded — the card's condition was never graded. |
| grader | string | optional | Filter by a specific grading company: PSA, BGS, SGC, CGC, etc. (case-insensitive) |
| grade | string | optional | Filter by grade value: 10, 9.5, 9, etc. Combine with grader (e.g. grader=PSA&grade=10). |
| condition | string | optional | Filter by listing condition string, e.g. Pre-Owned (ungraded) or New (Other) (graded slab). eBay records only. |
| sort | string | optional | date_desc (default) | date_asc | price_desc | price_asc |
| page | integer | optional | Page number (default: 1) |
| limit | integer | optional | Results per request (default: 25, max: 1,000). Same cap on all plans. |
| cursor | string | optional | Opaque cursor from a previous response's next_cursor field. Efficiently paginate large result sets without offset drift. When provided, page is ignored. Available on all plans. |
| indexed_after | datetime | optional | ISO 8601 UTC. Return only records indexed after this time. Useful for incremental sync. |
Cursor pagination
Every response includes a next_cursor field in pagination when more results exist. Pass it as ?cursor= on the next request to fetch the next page — no offset drift, no missed records. Available on all plans.
# Page 1 — no cursor needed GET /v1/market/sales?q=psa+10+trout&limit=1000 → pagination.next_cursor: "eyJwZ19pZCI6IDE4N..." # Page 2 — pass the cursor GET /v1/market/sales?q=psa+10+trout&limit=1000&cursor=eyJwZ19pZCI6IDE4N... → pagination.next_cursor: "eyJwZ19pZCI6IDE2N..." # or null if last page
CSV export
Download a filtered result set as a CSV file. Same filter params as GET /sales — no cursor or page. Available on Starter and above.
CSV rows count against your shared daily budget — the same pool as API calls. A Builder user who pulls 30K rows via the API has 20K remaining, usable via API or CSV.
curl "https://thecardapi.com/api/v1/market/sales/export/csv?q=psa+10+ohtani&date_from=2026-06-01" \ -H "x-market-api-key: tca_your_key_here" \ -o sales.csv
Search syntax
(psa,bgs) — title contains "psa" OR "bgs"-reprint — title does NOT contain "reprint"-(lot,album) — title contains neither "lot" nor "album""topps chrome" — exact phrase matchohtani (psa,bgs) -(lot,album) -reprint — combine all formsResponse
Response schema
{
"data": [
{
"id": "137222685761",
"platform": "eBay",
"listing_type": "auction",
"title": "2021 Bowman Chrome Kyle Harrison Auto PSA 10",
"sale_date": "2026-06-14",
"sold_at": "2026-06-14T00:00:00Z",
"price": 47.50,
"original_price": null,
"currency": "USD",
"price_confirmed": true,
"bids": 12,
"image_url": "https://i.ebayimg.com/...",
"thumbnail_url": "https://i.ebayimg.com/...",
"listing_url": "https://www.ebay.com/itm/137222685761",
"cert": "93265128",
"condition": null,
"grade": "10",
"grader": "PSA",
"grading_company": "Professional Sports Authenticator",
"has_autograph_grade": false,
"has_grade_qualifier": false,
"label": null,
"grade_qualifier": null,
"autograph_grade": null,
"player": "Kyle Harrison",
"manufacturer": "Bowman",
"card_set": "2021 Bowman Chrome",
"card_number": "BCP-42",
"year": 2021,
"season": "2021",
"league": "Major League (MLB)",
"sport": "Baseball",
"team": "San Francisco Giants",
"features": ["Auto", "Rookie"],
"print_run": null,
"shipping_price": 4.99,
"category": "sports"
}
],
"pagination": {
"total": 258817,
"page": 1,
"limit": 25,
"pages": 10353,
"has_more": true,
"next_cursor": "eyJwZ19pZCI6IDE4NzUwMDAwLCAic29ydCI6ICJkYXRlX2Rlc2MifQ=="
},
"meta": {
"coverage_date_from": "2026-06-01",
"coverage_date_to": "2026-06-17",
"platforms_covered": ["eBay", "Goldin", "Lelands", "SCP Auctions", "Hakes", "REA", "TCGplayer"],
"generated_at": "2026-06-17T10:00:00Z"
}
}| Field | Type | Coverage | Description |
|---|---|---|---|
| id | string | 100% | Unique sale identifier (platform-prefixed) |
| platform | string | 100% | Platform name. Values: eBay · Goldin · Lelands · SCP Auctions · Hakes · REA · TCGplayer |
| listing_type | string | 100% | auction · fixed_price · best_offer |
| title | string | 100% | Raw listing title |
| sale_date | string | 100% | YYYY-MM-DD of the sale |
| sold_at | string | 100% | ISO 8601 timestamp (UTC). Day-boundary precision: ends in T00:00:00Z unless exact time was captured. |
| price | number | 100% | Final sale price in USD. For eBay: all-in buyer price. For Goldin: hammer price only — buyer also pays ~22% buyer's premium on top. For best_offer: the true negotiated price. |
| original_price | number|null | new | Pre-negotiation asking price for best_offer sales (the listed price before the offer was accepted). Compare with price to see the negotiated discount. eBay records from Aug 2026 onward; null elsewhere. |
| currency | string | 100% | Currency of price and original_price (ISO 4217: USD, GBP, CAD, AUD, EUR, …). Records before Aug 2026 report USD regardless of listing currency until the historical backfill completes. |
| price_confirmed | boolean | 100% | true = confirmed final price. false = fast-settle estimate (auction BIN/BO, updated to true within minutes once confirmed). |
| bids | integer|null | ~60% | Number of bids (auction only) |
| image_url | string|null | ~85% | Full-size listing image URL |
| thumbnail_url | string|null | ~85% | Thumbnail image URL |
| listing_url | string|null | ~99% | Direct link to the sold listing on eBay (or other marketplace). Note: eBay may redirect logged-in users to a product catalog page. To view the original sold listing, open the URL in an Incognito/Private browsing window. This redirect does not affect programmatic/API access. |
| cert | string|null | ~12% | Grading certificate number |
| condition | string|null | ~8% | Raw condition string from listing |
| grade | string|null | ~12% | Numeric grade (e.g. "10", "9.5", "Auth") |
| grader | string|null | ~12% | Grader abbreviation: PSA · BGS · CGC · SGC |
| grading_company | string|null | ~12% | Full grading company name |
| has_autograph_grade | boolean|null | ~12% | Whether the slab has a separate auto grade |
| has_grade_qualifier | boolean|null | ~12% | Whether the grade has a qualifier (e.g. OC, MK) |
| grade_qualifier | string|null | ~3% | Qualifier type (e.g. "OC", "MK", "ST") |
| label | string|null | ~10% | Grading label variant (e.g. "Gold Label") |
| autograph_grade | string|null | ~4% | Autograph sub-grade where graded separately |
| player | string|null | ~0.3% | Player name. Populated for catalog-matched records. null if not yet matched. |
| manufacturer | string|null | ~0.4% | Card manufacturer (e.g. Topps, Panini, Bowman). Populated for catalog-matched records. |
| card_set | string|null | ~0.4% | Set name (e.g. 2022 Topps Update Series). Populated for catalog-matched records. |
| card_number | string|null | ~0.4% | Set card number (e.g. #295 or BCP-42). Distinct from print run (/299). |
| year | integer|null | ~0.3% | Card year. Populated for catalog-matched records. |
| season | string|null | ~0.3% | Sports season (e.g. 2024-25). Populated for catalog-matched records. |
| league | string|null | ~0.3% | League name (e.g. Major League (MLB), National Basketball Association (NBA)). |
| sport | string|null | ~0.4% | Sport (e.g. Baseball, Basketball, Hockey). Populated for catalog-matched records. |
| team | string|null | ~0.3% | Team name at time of card printing. Populated for catalog-matched records. |
| features | string[] | ~0.3% | Array of card features extracted from listing (e.g. ["Auto", "Rookie", "Patch"]). Empty array if not populated. |
| print_run | integer|null | ~36% | Print run (denominator of serial fraction). /99 → 99. /10 → 10. 1/1 → 1. Null for unlisted base cards. Filter with print_run_min / print_run_max. |
| shipping_price | number|null | ~67% | Buyer shipping cost in USD. 0.00 = free shipping. Null when the source doesn't provide it. Filter with shipping_max. |
| category | string|null | ~99%* | Card category. Values: sports · tcg · non_sport. eBay records indexed June 6, 2026+. Auction house sources return null until backfilled. Filter with ?category=sports. (*eBay Jun 6, 2026+ only) |
listing_type: "best_offer" — the pricefield is the true final negotiated price, not the listing price. Most data providers don't expose this. We do.platform: "goldin" records, price is the hammer price— what the auctioneer called at close. The buyer also pays a buyer's premium (~22% current, ~20% pre-2022) on top. eBay prices are all-in. Factor this in when comparing values across platforms.Error
Error codes
| Status | Meaning |
|---|---|
| 401 | Invalid or missing API key |
| 403 | Action requires a higher plan or beta access |
| 404 | Resource not found |
| 422 | Validation error — check parameter types and ranges |
| 429 | Daily sales limit reached. Resets at midnight UTC. Upgrade for more. |
| 500 | Server error — try again shortly |
Webhooks
Webhooks
$9/mo add-on · Available on all paid plans · 7-day free trial. Get started →
Register up to 5 HTTPS endpoints and receive newly indexed card sales without polling. Sales are delivered as signed batches of up to 1,000 records. Delivery is at-least-once: the cursor advances only after every active endpoint accepts a batch, and a retry may deliver the same sale again. Deduplicate on sale id.
Add an endpoint
POST /api/v1/market/webhook
x-market-api-key: tca_your_key_here
Content-Type: application/json
{
"url": "https://your-server.com/webhook",
"secret": "your-hmac-signing-secret",
"label": "prod-receiver"
}Max 5 active endpoints per key. label is optional.
List endpoints
GET /api/v1/market/webhook
x-market-api-key: tca_your_key_here{
"endpoints": [
{ "id": 1, "url": "https://your-server.com/webhook", "label": "prod-receiver", "created_at": "2026-06-20T10:00:00Z" },
{ "id": 2, "url": "https://your-server.com/backup", "label": "backup", "created_at": "2026-06-20T10:01:00Z" }
],
"count": 2,
"max": 5
}Remove an endpoint
DELETE /api/v1/market/webhook/{endpoint_id}
x-market-api-key: tca_your_key_hereDelivery payload
Each POST contains a batch with the core sale fields from GET /v1/market/sales plus delivery fields such as pg_id.
POST https://your-endpoint.com/webhook
Content-Type: application/json
X-Webhook-Signature: sha256=<hmac-sha256>
{
"event": "sales.batch",
"timestamp": "2026-06-20T06:09:00Z",
"count": 47,
"data": [
{
"id": "ebay-387214905012",
"pg_id": 123456789,
"title": "2011 Topps Update Mike Trout RC BGS 9.5",
"price": 4200.00,
"listing_type": "auction",
"sold_at": "2026-06-20T06:00:24Z",
...
}
]
}Verify the signature
Every delivery includes X-Webhook-Signature: sha256=<hex>. Verify against the raw request body before processing.
import hashlib, hmac
def verify_webhook(body: bytes, secret: str, sig_header: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, sig_header)| Detail | Value |
|---|---|
| Price | $9/mo add-on · 7-day free trial · Available on all paid plans |
| Max endpoints | 5 active endpoints per API key |
| Delivery latency | Polled every 30 seconds after indexing; backlog and endpoint retries can add delay |
| Batch size | Up to 1,000 records per delivery |
| Retry policy | 3 attempts with backoff (0s / 5s / 30s). Cursor holds on all-fail. |
| Delivery contract | At-least-once. Deduplicate on sale id. |
| Poll cadence | Every 30 seconds |
| Signature | HMAC-SHA256 — X-Webhook-Signature: sha256=<hex> |
| Event type | sales.batch |
Catalog
Catalog API
The Catalog API is the structured complement to the sales feed: 16.6M+ cards across 332,000+ sets and 15 sports, with checklists, rookie/auto/relic flags, and print runs. Relationship and checklist coverage varies by release. Sales tell you what's trading — the catalog tells you what exists.
Access
Included with Pro and Enterprise at no extra charge. Available on Builder as a $29/mo add-on. See pricing →
Typed identifiers — UCID, USID, URID, CFID, UPID
Catalog uses stable typed identifiers in one format, like UC-1KJZD-TZG7C-6: UC- for cards (ucid), US- for individual sets and checklists (usid), UR- for releases (urid), CF- for corresponding card families (cfid), and UP- for subjects (upid). Where an existing UF-family anchor maps one-to-one to a release, it remains a permanent lookup alias for that release's UR- ID. The final character is a check digit, so a typo returns an error instead of the wrong record. Names and years may be corrected over time; issued IDs remain stable. Key your integration on issued IDs.
- • Use each ID with its documented route or filter:
/catalog/{ucid},set_id=US-..., orplayer_id=UP-.... - • Case-insensitive, dashes optional —
uc1kjzdtzg7c6works - • Responses also include a display
slug(2023-topps/27) — for URLs and UI only, never a join key - • The integer card
idfield remains during the transition and is deprecated
Releases, checklists, and card families
A Release (URID) identifies one Catalog release anchor. A checklist (USID), also called a Catalog Set, is one named card list within that release. Each exact catalog card or version has its own UCID; a UCID does not identify an individual physical copy. An unlinked checklist remains available by USID and is absent from Release membership results.
Conceptual example · one Catalog release · URID
Whole table = Release · Column = checklist · Row = card family · Cell = exact catalog card. Labels are illustrative and appear only when reviewed.
A CFID groups corresponding exact catalog cards within the same Release/design boundary. For exact-correspondence coverage inferred from checklists, complete normalized card-number and subject rosters must match. Reviewed families are accepted separately. Each card keeps its own UCID. The relationship subtype is separate: an exact correspondence does not by itself mean parallel. Parallel, image variation, promo, and proof labels appear only where reviewed; unclassified members return version_subtype=unknown and subtype_classification_state=unknown. This leaves the correspondence usable without assigning an unproved version label.
Find a card's corresponding versions
Start with GET /catalog/{ucid}. The card detail includes release_id, card_family_id, and card_family_href. Follow the family link to retrieve the member UCIDs and each member's version subtype. Each UCID still identifies a separate exact card.
Family coverage is partial. A null family ID and link mean that the card has no published family assignment; they do not establish that no other versions exist. An unresolved release is also null. These Catalog relationships do not by themselves identify the exact version offered in a sale or establish a Sales-to-UCID match.
Example — 2025 Bowman #17
The 2025 Bowman Shohei Ohtani #17 card has its own UCID. Its published CFID connects 23 corresponding cards across checklists in the same release and design component. Each member keeps a separate UCID and USID. The correspondence is established; the version subtype of these members remains unknown unless separately reviewed.
# Look up the exact card, then follow its card_family_href
curl -H "x-api-key: $KEY" \
"https://www.thecardapi.com/api/v1/catalog/UC-J8FJK-CEYCS-P"
# This family's 23 corresponding card versions
curl -H "x-api-key: $KEY" \
"https://www.thecardapi.com/api/v1/catalog/card-families/CF-GXXDT-9630C-7"Endpoints
Base URL: https://www.thecardapi.com/api/v1/catalog — same x-api-key header as the sales API.
| /catalog | Search cards — at least one filter required |
| /catalog/{id} | Single card by UCID (or integer id), with release and CFID links when assigned |
| /catalog/categories | Preferred taxonomy for building catalog navigation and filters |
| /catalog/sets | Browse and search sets by category, subcategory, name, year, and more |
| /catalog/sets/{id} | Single set by USID with card counts |
| /catalog/families | Legacy direct-parent family index — UFID, name, year, and direct member count |
| /catalog/releases | Browse releases by name, sport, or year |
| /catalog/releases/{id} | Resolve a release URID or its mapped durable UFID alias |
| /catalog/releases/{id}/checklists | List that release's main, insert, and version checklists |
| /catalog/card-families | Browse CFIDs within one release; requires release_id |
| /catalog/card-families/{id} | One CFID with its member UCIDs and known version subtypes |
| /catalog/subjects | Broad Catalog subject search — athletes, teams, characters, and other depicted subjects |
| /catalog/players | Legacy alias of /catalog/subjects — retained for compatibility |
| /catalog/new-releases | Sets added to the catalog, newest first — filter by sport or year |
| /catalog/sports | Legacy source-category list — retained for compatibility |
Categories and subcategories
Use /catalog/categoriesto discover the catalog's stable, developer-facing taxonomy. Every set response includes one top-level category and one primary subcategory. The endpoint returns machine IDs, display labels, descriptions, and the valid subcategories beneath each category, so applications can build filter UIs without hardcoding our labels.
| category | string | optional | Top-level ID: sports, trading_card_games, or entertainment. |
| subcategory | string | optional | Stable primary ID such as baseball, pokemon, lorcana, magic_the_gathering, marvel, or star_wars. |
| sport | string | optional | Legacy source-category field retained for compatibility. New navigation should use category and subcategory. |
# Discover every valid category and subcategory
curl -H "x-api-key: $KEY" \
"https://www.thecardapi.com/api/v1/catalog/categories"
# Browse Lorcana sets
curl -H "x-api-key: $KEY" \
"https://www.thecardapi.com/api/v1/catalog/sets?subcategory=lorcana&limit=100"{
"data": [{
"usid": "US-...",
"set_name": "Disney Lorcana ...",
"category": "trading_card_games",
"subcategory": "lorcana",
"sport": "Gaming"
}],
"pagination": { "total": 22, "page": 1, "limit": 100, "pages": 1 }
}Machine IDs are stable; display labels may improve over time. /catalog/sports and thesport filter continue to work for existing integrations.
Set browse parameters — GET /catalog/sets
| category | string | optional | Top-level category ID from /catalog/categories. |
| subcategory | string | optional | Subcategory ID from /catalog/categories. A subcategory also implies its parent category. |
| sport | string | optional | Legacy source-category filter, case-insensitive. |
| q | string | optional | Case-insensitive set-name search (min 2 chars). |
| year | integer | optional | Filter by set year. |
| family_of | USID or UFID | optional | Legacy filter for the currently linked root checklist and its direct children. Use release_id to browse the release's assigned checklists. |
| release_id | URID or UFID | optional | Return checklists in the specified release anchor; a mapped UFID alias resolves to the same release. |
| main_only | boolean | optional | true returns top-level sets only, excluding inserts, parallels, autos, and other sub-sets. |
| page / limit | integer | optional | Pagination. Default limit: 25; maximum: 100. |
Card search parameters — GET /catalog
| set_id | USID | optional | Filter by set — fastest path. Accepts the set's USID. |
| release_id | URID or UFID | optional | Filter to cards in a release, using its URID or mapped legacy UFID alias. |
| card_family_id | CFID | optional | Filter to the exact cards in one published card family. |
| player_id | UPID | optional | Discover Catalog cards associated with a subject UPID. These source-backed subject links have partial coverage; they are not reviewed exact-athlete or Sales attributions. |
| card_number | string | optional | Exact card number, case-insensitive (leading # ignored). Combine with set_id for a one-record existence check. |
| set_name | string | optional | Case-insensitive substring match on set name (min 2 chars). |
| q | string | optional | Full-text search across card subject and set name (min 2 chars). |
| sport | string | optional | Baseball, Basketball, Football, Hockey, Soccer, Gaming (TCG), Racing, and more. |
| year | integer | optional | Set year. |
| is_rookie / is_auto | boolean | optional | Card attribute filters. |
| page / limit | integer | optional | Pagination. Max limit: 100 (Builder), 1,000 (Pro/Enterprise). |
Unrecognized parameters return a 400 listing the supported ones — a misspelled filter never silently returns unfiltered results.
Example — one-record existence check
curl -H "x-api-key: $KEY" \
"https://www.thecardapi.com/api/v1/catalog?set_id=US-J28FC-5H09C-4&card_number=27"{
"data": [{
"ucid": "UC-1KJZD-TZG7C-6",
"slug": "2023-topps/27",
"set_usid": "US-J28FC-5H09C-4",
"set_name": "2023 Topps",
"parent_set_name": null,
"card_number": "27",
"subject": "Mike Trout",
"is_rookie": false,
"is_auto": false,
"print_run": null
}],
"pagination": { "total": 1, "page": 1, "limit": 100, "pages": 1 }
}Existing set responses retain parent product context through parent_set_name and parent_set_usid. A parent link alone does not establish whether the checklist is an insert, parallel, or another version. Serial-numbered cards expose print_run when known.
Catalog allowance
Catalog usage has its own daily pool — it never consumes your sales budget, and vice versa. Each record returned counts as 1 (minimum 1 per request); counters reset at 00:00 UTC. Every /catalog response carries X-RateLimit-Limit / X-RateLimit-Remaining headers for this pool.
| Builder + Catalog add-on | 500 records/day |
| Pro (included) | 2,000 records/day |
| Enterprise | Custom — defined in Order Form |
Higher catalog allowances and bulk licensing are scoped under an Enterprise agreement — reach out to discuss.
Players
Players API
Search a reviewed cohort of 2,317 athlete identities, open a profile by stable UPID, browse Catalog cards linked to that subject, and read timestamped bookmaker prices for reviewed player-award futures. Coverage is intentionally partial. A missing Players result does not mean the athlete or their cards are missing from the broader Catalog.
For broad subject discovery, use /catalog/subjects. It includes athletes, teams, characters, and other depicted subjects; /catalog/players remains an equivalent legacy alias. A Catalog subject is not automatically a reviewed athlete profile.
Endpoints
Base URL: https://www.thecardapi.com/api/v1/players — use the same x-api-key header and Catalog entitlement.
| GET /players?search= | Prefix-search reviewed athlete names |
| GET /players/{upid} | Reviewed athlete profile with Catalog and futures section links |
| GET /players/{upid}/cards | Paginated Catalog cards linked to the subject UPID |
| GET /players/{upid}/futures | Reviewed player-award futures prices from a local snapshot |
Search parameters — GET /players
| search | string | required | Case-insensitive start-of-name search (2–100 characters), or an exact UPID. |
| sport | string | optional | Optional reviewed primary-sport filter. |
| page | integer | optional | Page number. Default: 1. |
| limit | integer | optional | Results per page. Default: 20; maximum: 50. |
Results are ordered by canonical name, then UPID. Responses include deterministic pagination,schema_version, an opaque data_revision, and ISO data_as_of. Coverage is partial: a missing result does not establish that the athlete is absent from Catalog.
Profile and discovery fields
| upid | string | optional | Stable player identifier. |
| canonical_name | string | optional | Reviewed athlete display name. |
| primary_sport | string | optional | Reviewed primary sport. |
| primary_league | string | optional | Reviewed league, normalized separately from primary sport. |
| schema_version / data_revision / data_as_of | string / string / ISO date or datetime | optional | Public schema version, opaque dataset revision, and data freshness. |
| catalog.card_count | integer | optional | Cards linked to this Catalog subject. |
| catalog.first_card_year / last_card_year | integer|null | optional | Observed linked-card year range; this is not the athlete's career range. |
| sections.cards / sections.futures | object | optional | Status and link for each separately permissioned section. |
The profile links to available sections. It does not claim a current team, upcoming game, career status, confirmed honor, or player statistics.
Card links and allowance
/players/{upid}/cards uses the same query, ordering, pagination, and accounting as /catalog?player_id=. Card-link coverage is partial. These links support Catalog discovery; they are not independently reviewed exact-athlete card attribution and do not assert a match to any Sales API record.
Players requests use the existing Catalog daily record pool and return its standard quota headers. Search and card-list requests charge records returned, with a minimum of one record per request; a profile request charges one. Unknown or unreviewed UPIDs return 404. A registry or Catalog identity mismatch fails closed with 503.
Build a player card browser
Search for a reviewed athlete, use the returned UPID to open the small discovery profile, then follow its cards section to browse linked Catalog cards. The profile's card count and card-year range describe Catalog links, not playing-career statistics.
# Search by name; exact UPID lookup also works in search=
curl -H "x-api-key: $KEY" \
"https://www.thecardapi.com/api/v1/players?search=Shohei"
# Open the reviewed profile and its section links
curl -H "x-api-key: $KEY" \
"https://www.thecardapi.com/api/v1/players/UP-CRNYZ-EV13Q-8"
# Browse the first five linked Catalog cards
curl -H "x-api-key: $KEY" \
"https://www.thecardapi.com/api/v1/players/UP-CRNYZ-EV13Q-8/cards?limit=5"Selected fields from the held Shohei Ohtani profile, using a Builder or higher key:
{
"upid": "UP-CRNYZ-EV13Q-8",
"canonical_name": "Shohei Ohtani",
"primary_sport": "baseball",
"primary_league": "MLB",
"catalog": {"card_count": 18822},
"sections": {
"cards": {"status": "partial", "href": "/api/v1/players/UP-CRNYZ-EV13Q-8/cards"},
"futures": {"status": "available", "href": "/api/v1/players/UP-CRNYZ-EV13Q-8/futures"}
},
"schema_version": "2026-10-09",
"data_revision": "players-2026-10-08.1",
"data_as_of": "2026-10-08"
}Player-award futures — Builder, Pro, and Enterprise
GET /players/{upid}/futures returns reviewed player-award market prices from local snapshots. Current coverage is 781 players and 2,253 player-award quote links across MLB, NFL, NBA, and NHL; a player can have no matched quotes. Coverage is partial. Starter keys are denied, including those with Catalog access. Eligible requests use the normal Catalog daily allowance: each response charges its returned rows, with a minimum of one record, and returns X-RateLimit-Limit and, for capped plans, X-RateLimit-Remaining from the Catalog pool. Customer requests read our saved snapshot; they do not contact the quote source live.
Player futures market data powered by PropLine; prices originate from the named bookmakers. The response identifies PropLine once as thedata_provider, while each quote identifies its bookmaker.
| data_provider | string | optional | Market-data provider for this futures response: PropLine. |
| market | object | optional | Normalized key, readable name, scope=player_award, and reviewed league. |
| bookmaker / outcome_name | string / string | optional | Bookmaker and priced player outcome. |
| price_american / price_decimal | integer|null / number|null | optional | Bookmaker market price in American and decimal formats; null when unpriced. |
| source_updated_at / observed_at | datetime|null / datetime | optional | Source quote time and our capture time, so clients can assess age. |
| state | string | optional | quoted, stale, unpriced, settled, or unknown_freshness when source time is unavailable. |
| seven_day_baseline | object|null | optional | Actual same-book quote near seven days earlier; null until comparable history exists. |
| raw_implied_probability_change_pp | number|null | optional | Change in raw implied probability, in percentage points; bookmaker margin is not removed. |
We plan one four-sport snapshot per day at launch. Seven-day movement is unavailable at launch and appears only after actual captures provide a comparable same-book baseline. These are market prices, not confirmed honors, predictions, fair probabilities, or card valuations. Player statistics, upcoming games, current team, team futures, and game lines are outside this release.
Compare books on one award
Fetch a reviewed player's futures and compare rows with the same market key and league. The captured Connor McDavid Hart Trophy rows below illustrate why each book's source time matters: one line is stale while another is freshly quoted. This is a selected-fields excerpt from the held snapshot, not a current price or a complete API response.
curl -H "x-api-key: $KEY" \
"https://www.thecardapi.com/api/v1/players/UP-KYTXM-Z8Z5H-P/futures"{
"upid": "UP-KYTXM-Z8Z5H-P",
"data_provider": "PropLine",
"quotes": [
{
"market": {"key": "hart_trophy_winner", "name": "2026-27 Hart Trophy Winner", "scope": "player_award", "league": "NHL"},
"bookmaker": "fanduel", "outcome_name": "Connor McDavid",
"price_american": 250, "price_decimal": 3.5,
"source_updated_at": "2026-10-01T22:02:41Z",
"observed_at": "2026-10-08T23:53:37Z", "state": "stale",
"seven_day_baseline": null, "raw_implied_probability_change_pp": null
},
{
"market": {"key": "hart_trophy_winner", "name": "NHL Awards 2026/27 - Hart Trophy Winner", "scope": "player_award", "league": "NHL"},
"bookmaker": "draftkings", "outcome_name": "Connor McDavid",
"price_american": 300, "price_decimal": 4.0,
"source_updated_at": "2026-10-08T23:52:36Z",
"observed_at": "2026-10-08T23:53:37Z", "state": "quoted",
"seven_day_baseline": null, "raw_implied_probability_change_pp": null
}
]
}Watch real seven-day movement
A watchlist can alert only when raw_implied_probability_change_pp is non-null and crosses a threshold you choose, comparing the same bookmaker and outcome with an actual earlier snapshot. At launch this field and seven_day_baseline are null; do not treat missing history as zero movement. A player with no accepted quote returns an emptyquotes: [] with partial coverage; that does not prove no market exists. A stale or unpriced row keeps its explicit state.
# Read the saved award prices once a real seven-day baseline exists
curl -H "x-api-key: $KEY" \
"https://www.thecardapi.com/api/v1/players/UP-KYTXM-Z8Z5H-P/futures"A player without an accepted award link has an empty quote list:
{
"quotes": [],
"coverage": {"status": "partial", "scope": "reviewed_player_awards"}
}Rate
Rate limits
Sales requests are limited by rows returned per UTC day and how far back you can query. Catalog and Players share a separate daily Catalog record allowance.
Lookback controls how far back your key may query. Daily allowance controls how many rows may be returned. Neither is a completeness guarantee. Platform availability and reporting time vary; see Status for current freshness.
| Plan | Daily sales-row allowance | Lookback |
|---|---|---|
| Free | 5,000 | 3 days |
| Starter | 10,000 | 14 days |
| Builder | 50,000 | 30 days |
| Pro | 200,000 | 90 days |
| Enterprise | Custom | Unlimited |
Daily counters reset at 00:00:00 UTC each day. API calls and CSV exports draw from the same shared budget — a Builder user who downloads 30K rows via CSV has 20K remaining for API calls that day.
Max limit is 1,000 rows per request on all plans. View full pricing →
Pro add-ons
Unlimited Lookback requires an active Pro subscription. The Catalog API is included with Pro; on Builder it's a $29/mo add-on. See add-ons →
Every response from /v1/market/sales includes rate limit headers so you can track usage programmatically:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Your plan's daily sales cap |
| X-RateLimit-Remaining | Sales rows remaining today |
| X-RateLimit-Reset | Unix timestamp of next reset (00:00:00 UTC) |
MCP
MCP Server
The MCP (Model Context Protocol) server exposes 7 analysis tools over a single streamable HTTP endpoint. Any MCP-compatible client — Claude Desktop, Claude Code, Cursor, Windsurf, ChatGPT Desktop, LangChain — can connect with one URL.
Endpoint
Protocol: JSON-RPC 2.0 · Transport: streamable HTTP
Discover available tools
{
"jsonrpc": "2.0",
"id": "1",
"method": "tools/list"
}Claude Desktop
{
"mcpServers": {
"thecardapi": {
"command": "npx",
"args": ["mcp-remote", "https://www.thecardapi.com/api/mcp",
"--header", "Authorization: Bearer YOUR_API_KEY"]
}
}
}Claude Code
claude mcp add thecardapi \
--transport http \
--url https://www.thecardapi.com/api/mcp \
--header "Authorization: Bearer YOUR_API_KEY"Cursor / Windsurf
{
"mcpServers": {
"thecardapi": {
"url": "https://www.thecardapi.com/api/mcp",
"headers": { "Authorization": "Bearer YOUR_API_KEY" }
}
}
}See the full MCP integration guide → for all clients.
MCP
MCP Tools
Each tool is called via method: "tools/call" with a name and arguments object.
Find recent sold prices for any card. Returns individual sale records sorted by date. Supports filtering by grader, grade, listing type, date range, and price range.
Parameters
| query | string | optional | Full-text search — e.g. 'PSA 10 Luka Doncic 2018 Prizm Silver' |
| platform | string | optional | Filter by platform (e.g. ebay) |
| grader | string | optional | psa | cgc | beckett | sgc |
| grade | string | optional | 10 | 9.5 | 9 | 8.5 | 8 | 7 | auth | 10pristine |
| listing_type | string | optional | Auction | FixedPrice | BestOffer |
| date_from | string | optional | Start date YYYY-MM-DD |
| date_to | string | optional | End date YYYY-MM-DD |
| price_min | number | optional | Minimum sale price |
| price_max | number | optional | Maximum sale price |
| limit | integer | optional | Results to return (1–20, default: 10) |
Example call
{
"jsonrpc": "2.0", "id": "1",
"method": "tools/call",
"params": {
"name": "search_cards",
"arguments": {
"query": "PSA 10 Luka Doncic 2018 Prizm Silver",
"listing_type": "BestOffer",
"limit": 10
}
}
}Response (abbreviated)
{
"total_returned": 10,
"results": [
{
"id": "ebay-137222685761",
"title": "2018 Panini Prizm Silver Luka Doncic PSA 10",
"platform": "eBay",
"listing_type": "BestOffer",
"price": 4800.00,
"sale_date": "2026-06-16",
"sold_at": "2026-06-16T14:23:00Z",
"bids": null,
"grader": "PSA",
"grade": "10",
"slab_serial": "84566824",
"listing_url": "https://www.ebay.com/itm/137222685761"
}
],
"note": "BestOffer prices reflect the true final negotiated amount, not the list price."
}See exactly how PSA/BGS grade affects price for a specific card. Returns a full price ladder sorted grade-high to grade-low, plus the grade where the value jump is sharpest.
Parameters
| query | string | required | Card to analyze — e.g. 'Luka Doncic 2018 Prizm Silver' |
| grader | string | optional | psa (default) | cgc | beckett | sgc |
| lookback_days | integer | optional | Days of history to include (default: 90) |
Example call
{
"jsonrpc": "2.0", "id": "1",
"method": "tools/call",
"params": {
"name": "grade_impact_analysis",
"arguments": {
"query": "Luka Doncic 2018 Prizm Silver",
"grader": "psa",
"lookback_days": 90
}
}
}Response (abbreviated)
{
"query": "Luka Doncic 2018 Prizm Silver",
"grader": "PSA",
"lookback_days": 90,
"total_sales": 49,
"price_ladder": [
{ "grade": "10", "sales": 6, "avg_price": 9200, "median_price": 8750, "min_price": 7800, "max_price": 11200 },
{ "grade": "9", "sales": 28, "avg_price": 3400, "median_price": 3250, "min_price": 2800, "max_price": 4100 },
{ "grade": "8", "sales": 12, "avg_price": 1950, "median_price": 1875, "min_price": 1600, "max_price": 2300 },
{ "grade": "7", "sales": 3, "avg_price": 850, "median_price": 820, "min_price": 750, "max_price": 980 }
],
"best_value_grade": {
"grade": "9",
"reason": "Stepping up to 10 costs 171% more — this is the steepest value jump in the price ladder."
}
}PSA vs CGC vs SGC vs BGS for the same card at the same grade. Find which grader commands the highest market premium.
Parameters
| query | string | required | Card to compare — e.g. 'Wembanyama 2023 Prizm Silver' |
| grade | string | required | Grade to compare across graders — e.g. 10 | 9.5 | 9 | 8.5 | 8 |
| lookback_days | integer | optional | Days of history (default: 90) |
Example call
{
"jsonrpc": "2.0", "id": "1",
"method": "tools/call",
"params": {
"name": "grader_comparison",
"arguments": {
"query": "Wembanyama 2023 Prizm Silver",
"grade": "9"
}
}
}Response (abbreviated)
{
"query": "Wembanyama 2023 Prizm Silver",
"grade": "9",
"lookback_days": 90,
"by_grader": [
{ "grader": "PSA", "sales": 18, "avg_price": 1450, "median_price": 1380 },
{ "grader": "BGS", "sales": 7, "avg_price": 1210, "median_price": 1175 },
{ "grader": "CGC", "sales": 4, "avg_price": 980, "median_price": 950 },
{ "grader": "SGC", "sales": 2, "avg_price": 890, "median_price": 890 }
],
"takeaway": "PSA commands the highest avg price at $1450",
"comparisons": [
{ "grader": "BGS", "pct_cheaper_than_top": 17 },
{ "grader": "CGC", "pct_cheaper_than_top": 32 },
{ "grader": "SGC", "pct_cheaper_than_top": 39 }
]
}Is this card trending up or down? Compares a recent time window against the prior window of equal length, with volume data. Upper bound is always yesterday so partial-day data never skews the result.
Parameters
| query | string | required | Card to analyze — e.g. 'Prizm Silver Wembanyama' |
| lookback_days | integer | optional | Length of each comparison window in days (default: 7) |
| grader | string | optional | Optional — filter by grader: psa | cgc | beckett | sgc |
| grade | string | optional | Optional — filter by grade (e.g. 10, 9.5) |
Example call
{
"jsonrpc": "2.0", "id": "1",
"method": "tools/call",
"params": {
"name": "price_momentum",
"arguments": {
"query": "Wembanyama Prizm Silver",
"lookback_days": 14
}
}
}Response (abbreviated)
{
"query": "Wembanyama Prizm Silver",
"window_days": 14,
"recent_window": {
"from": "2026-07-03", "to": "2026-07-16",
"sales": 42, "avg_price": 1380
},
"prior_window": {
"from": "2026-06-19", "to": "2026-07-02",
"sales": 35, "avg_price": 1150
},
"price_change_pct": 20.0,
"volume_change_pct": 20.0,
"momentum": "rising",
"note": "'rising' means both price and volume increased vs the prior window of the same length."
}Should you grade this card? Real cost math (PSA/CGC/SGC/BGS fees + shipping, all tiers including bulk) vs actual market comps. Returns expected profit at each grade, break-even analysis, and honest caveats.
Parameters
| query | string | required | Card to evaluate — e.g. '1986 Fleer Jordan 57' |
| raw_purchase_price | number | required | What you paid (or plan to pay) for the raw ungraded card |
| target_grade | string | optional | Grade you hope to achieve: 10 | 9.5 | 9 | 8.5 (default: 10) |
| grader | string | optional | psa (default) | cgc | beckett | sgc |
| submission_tier | string | optional | bulk | economy | standard (default) | express |
Example call
{
"jsonrpc": "2.0", "id": "1",
"method": "tools/call",
"params": {
"name": "grading_value_calculator",
"arguments": {
"query": "1986 Fleer Jordan 57",
"raw_purchase_price": 6500,
"target_grade": "10",
"grader": "psa",
"submission_tier": "standard"
}
}
}Response (abbreviated)
{
"query": "1986 Fleer Jordan 57",
"raw_purchase_price": 6500,
"grader": "PSA",
"target_grade": "10",
"grading_costs": {
"service_tier": "standard",
"per_card_fee": "$79.99",
"turnaround": "25 days",
"shipping_roundtrip": "$40–$60 (continental US)",
"total_estimate": "$119.99–$139.99",
"total_mid_estimate": 130
},
"grade_scenarios": [
{ "grade": "10", "sales": 8, "avg_price": 42000, "expected_profit": 35370, "is_target": true },
{ "grade": "9", "sales": 22, "avg_price": 9800, "expected_profit": 3170, "is_target": false },
{ "grade": "8", "sales": 11, "avg_price": 4200, "expected_profit": -2330, "is_target": false }
],
"math": {
"if_card_grades_at_target": "$42000 (avg sale price)",
"minus_raw_cost": "-$6500",
"minus_grading_and_ship": "-$130 (midpoint estimate)",
"expected_profit": "$+35370",
"break_even_grade": "8"
},
"caveats": [
"Grading outcome is NOT guaranteed — these numbers assume the card achieves each grade.",
"Only 8 comps found at 10 — price estimate may be unreliable."
]
}Code
Code examples
Python — REST API
import requests
API_KEY = "tca_your_key_here"
BASE = "https://thecardapi.com/api/v1/market"
HEADERS = {"x-market-api-key": API_KEY}
# Search recent Ohtani auctions
r = requests.get(f"{BASE}/sales", headers=HEADERS, params={
"q": "Shohei Ohtani",
"listing_type": "auction",
"price_min": 50,
"limit": 50,
})
for sale in r.json()["data"]:
print(sale["sale_date"], sale["price"], sale["title"][:60])
# Incremental sync — only records indexed since your last poll
r = requests.get(f"{BASE}/sales", headers=HEADERS, params={
"indexed_after": "2026-06-15T10:00:00Z",
"limit": 100,
})Python — MCP tools/call
import requests
MCP_URL = "https://www.thecardapi.com/api/mcp"
HEADERS = {"Authorization": "Bearer tca_your_key_here", "Content-Type": "application/json"}
def call_tool(name: str, arguments: dict) -> dict:
payload = {
"jsonrpc": "2.0", "id": "1",
"method": "tools/call",
"params": {"name": name, "arguments": arguments},
}
r = requests.post(MCP_URL, headers=HEADERS, json=payload)
result = r.json()
if result.get("error"):
raise RuntimeError(result["error"]["message"])
import json
return json.loads(result["result"]["content"][0]["text"])
# Grade ladder for Mantle cards over $1,000
data = call_tool("grade_impact_analysis", {
"query": "Mickey Mantle",
"grader": "psa",
"lookback_days": 30,
})
for row in data["price_ladder"]:
avg = "{:,.0f}".format(row['avg_price'])
print(f"PSA {row['grade']}: ${avg} avg ({row['sales']} sales)")cURL
# REST — search for PSA 10 cards
curl "https://thecardapi.com/api/v1/market/sales?q=psa+10&limit=25" \
-H "x-market-api-key: tca_your_key_here"
# MCP — call price_momentum tool
curl https://www.thecardapi.com/api/mcp \
-H "Authorization: Bearer tca_your_key_here" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"price_momentum","arguments":{"query":"Wembanyama Prizm Silver"}}}'Node.js
const res = await fetch(
"https://thecardapi.com/api/v1/market/sales?q=topps+chrome&limit=50",
{ headers: { "x-market-api-key": "tca_your_key_here" } }
);
const { data, pagination } = await res.json();
console.log(`${pagination.total} results`);LangChain (Python)
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_anthropic import ChatAnthropic
async def main():
async with MultiServerMCPClient({
"thecardapi": {
"url": "https://www.thecardapi.com/api/mcp",
"transport": "streamable_http",
"headers": {"Authorization": "Bearer tca_your_key_here"},
}
}) as client:
tools = await client.get_tools()
model = ChatAnthropic(model="claude-sonnet-4-6")
agent = create_react_agent(model, tools)
result = await agent.ainvoke({
"messages": "Is grading my 1986 Fleer Jordan worth it at $6,500?"
})
print(result["messages"][-1].content)
asyncio.run(main())Ready to start building?
Free tier includes 5,000 sales rows returned per UTC day with 3-day lookback. No credit card required.