Skip to content

Card Identification API — Identify Cards From a Photo

Send one photo of a card, graded slab or sealed product. The API reads what is printed on it (name, collector number or card code, set, edition stamps, foil) and returns the catalog cards that match, ranked, with their TCGPlayer IDs.

POST /v1/identify

Authentication: API key (any plan, including Free) or x402 at $0.02 per photo.

Identification is billed in scans, separately from your daily request limit. Each photo uses one scan.

  • Every account gets 25 free scans once, on its first identification.
  • More scans come in prepaid packs from the dashboard (Billing tab). Packs never expire.
PackPricePer scan
1,000 scans$151.5¢
5,000 scans$601.2¢
25,000 scans$2501.0¢
100,000 scans$8000.8¢

Every photo the reader looks at uses a scan, including photos it asks you to retake, because the reading work is done either way. A scan is refunded automatically if the photo reader itself is unavailable (503 IDENTIFY_UNAVAILABLE). A request that is rejected before reading, such as an unsupported file or a missing image, never uses a scan.

The scans left are in the X-Scans-Remaining response header, in meta.scans_remaining, and in GET /v1/usage (scans.balance) when you are signed in.

GET /v1/identify/usage

Your balance, the scans used on each of the last 30 days (UTC) by outcome, and the free trial and pack purchases added to your balance. It works with your API key or a dashboard login, is free, and does not count against your daily request limit. The dashboard’s Billing tab shows the same data as a chart.

Terminal window
curl "https://api.tcgapi.dev/v1/identify/usage" -H "X-API-Key: YOUR_KEY"
{
"data": {
"balance": 978,
"trial_pending": false,
"days": [
{ "date": "2026-10-14", "scans": 22, "refunded": 0, "matched": 19, "ambiguous": 2, "retake": 1, "not_found": 0, "unsupported": 0 }
],
"ledger": [
{ "reason": "purchase", "scans": 1000, "amount_cents": 1500, "created_at": "2026-10-14T15:02:11.000Z" },
{ "reason": "trial", "scans": 25, "amount_cents": null, "created_at": "2026-10-14T14:40:03.000Z" }
]
}
}

days always holds 30 entries, oldest first, including days with no scans; the example shows one. scans is the scans used that day; refunded counts reads that failed on the reader’s side and were given back. ledger lists up to 50 additions, newest first.

Send the photo in one of three ways. JPEG, PNG or WebP, up to 8 MB. Phone photos work as they are; there is no need to resize them.

BodyHow
multipart/form-datathe photo in an image field
application/json{"image": "<base64 or data URL>"}
image/jpeg, image/png, image/webpthe raw image bytes

Options (query parameters, or fields in the form or JSON body):

ParameterTypeDescription
gamestringA game slug from GET /v1/games. Searches only that game. Leave it out to let the photo decide.
includestringprices adds the current prices of the top candidate. No extra charge.

Examples:

Terminal window
# Multipart upload
curl -X POST "https://api.tcgapi.dev/v1/identify?include=prices" \
-H "X-API-Key: YOUR_KEY" \
# Raw image body, searching one game
curl -X POST "https://api.tcgapi.dev/v1/identify?game=one-piece-card-game" \
-H "X-API-Key: YOUR_KEY" \
-H "Content-Type: image/jpeg" \
--data-binary @op01-120.jpg
const form = new FormData();
form.append('image', fileInput.files[0]);
const res = await fetch('https://api.tcgapi.dev/v1/identify?include=prices', {
method: 'POST',
headers: { 'X-API-Key': process.env.TCGAPI_KEY },
body: form,
});
const { data } = await res.json();
if (data.status === 'matched') console.log(data.candidates[0].name);

A real response for a photo of Darkrai (Burning Shadows 87/147), shortened to its first candidate:

{
"data": {
"id": "6a74ebcb0ad4418ab7de1bb69970923e",
"status": "matched",
"message": null,
"read": {
"game": "pokemon",
"name": "Darkrai",
"number": "87/147",
"set": "BUS Burning Shadows",
"language": "english",
"kind": "raw",
"product_type": null,
"finish": "holo",
"stamp": null,
"grade": null,
"readable": {
"name": true,
"number": true,
"set": true
},
"notes": "Burning Shadows set symbol"
},
"candidates": [
{
"card_id": 24535,
"tcgplayer_id": 138577,
"name": "Darkrai",
"number": "87/147",
"rarity": "Holo Rare",
"product_type": "Cards",
"set": {
"id": 5500119,
"name": "SM - Burning Shadows",
"code": null
},
"game": {
"slug": "pokemon",
"name": "Pokemon"
},
"image_url": "https://product-images.tcgplayer.com/fit-in/400x400/138577.jpg",
"match": {
"number": "exact",
"name": 1,
"set": 0.5,
"edition": true
},
"printings": [
"Holofoil",
"Reverse Holofoil"
],
"suggested_printing": "Holofoil",
"score": 8.25,
"prices": [
{
"printing": "Holofoil",
"market_price": 1.23,
"low_price": 0.27,
"median_price": null,
"lowest_with_shipping": 1.76,
"price_change_7d": -6.11,
"price_change_30d": -10.22,
"last_updated_at": "2026-10-13T22:08:52.239Z"
},
{
"printing": "Reverse Holofoil",
"market_price": 1.34,
"low_price": 0.15,
"median_price": null,
"lowest_with_shipping": 1.64,
"price_change_7d": 1.52,
"price_change_30d": -15.72,
"last_updated_at": "2026-10-13T22:08:52.239Z"
}
]
}
],
"warnings": []
},
"meta": {
"charged": "scan",
"scans_remaining": 24
}
}
ValueMeaning
matchedExactly one catalog card fits everything the photo shows: the printed number or code, the name, and any edition stamp. It is candidates[0].
ambiguousSeveral cards fit, or the best one doesn’t fit on every point. Show the candidates and let the user pick. Common causes: a pre-release, full-art or reprint version with the same number, or a set the photo doesn’t show.
retakeThe photo is not good enough to trust: blur, glare, a cut-off number, or several items in one photo. message says what to fix. Candidates found from the readable parts are still listed as hints.
not_foundNothing in the catalog matches what was read.
unsupportedA card game that isn’t in the catalog, or a language the catalog doesn’t list (non-English printings, except Japanese Pokemon).

The API never guesses. A candidate whose printed number disagrees with the photo is dropped. A card is only matched when nothing else scores as well.

  • read: what the photo shows. Empty fields are unreadable, not guessed. stamp is the edition mark seen (for example 1st Edition or Prerelease), null when the photo clearly shows none, and unclear when glare or cropping hides where it would be. grade is filled for graded slabs.
  • candidates: up to 10, best first. match.number is exact, partial (the same number with the set total on one side only, for example 169/249 against 169) or unknown (no number to compare). match.name and match.set are the share of words that agree (0 to 1). match.edition is false when the card’s title names a stamp the photo doesn’t show, or the reverse.
  • suggested_printing: the printing the photo points to (finish, 1st Edition stamp), when it decides one. Use it with /v1/cards/{id}/prices.
  • warnings: for example that a graded slab is priced as the raw card. Graded sale prices are not in the catalog.
StatusCodeWhenScan used
400INVALID_IMAGENo image, unsupported format, or larger than 8 MBNo
400INVALID_GAMEgame is not a catalog game slugNo
401API_KEY_REQUIREDNo key and no x402 paymentNo
402SCAN_BALANCE_EMPTYNo scans left. Buy a pack in the dashboard.No
403IDENTIFY_REGION_UNAVAILABLEThe photo reader is not available in your country. Retrying won’t help.Refunded
503IDENTIFY_UNAVAILABLEThe photo reader is unavailableRefunded

Measured on October 14, 2026, with this endpoint’s own code against the live catalog:

  • Real phone photos (81 Pokemon cards and sealed products photographed by a vendor): the right item was matched 88% of the time and was among the candidates every time, with no wrong matches.
  • Every game with cards in the catalog (262 catalog images, about 5 per game): the right card was among the candidates 92% of the time and matched 55% of the time. Most of the rest were ambiguous between versions that share one printed number, such as pre-release and alternate-art printings.

Tips for better results: photograph one item at a time, fill the frame, keep the bottom edge (number and set) in focus, and tilt away from glare.

  • Pass game when your app already knows it. It narrows the search and avoids mistaking similar-looking games.
  • Store card_id, not the name. It is the key for every other endpoint.
  • x402 agents pay $0.02 per photo with no account. See x402.
  • Building a scanning app? The card scanner guide walks through it, and Trading card recognition APIs compared shows how this endpoint compares with other recognition services.