Card Identification API — Identify Cards From a Photo
Identify
Section titled “Identify”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/identifyAuthentication: API key (any plan, including Free) or x402 at $0.02 per photo.
Billing: scans
Section titled “Billing: scans”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.
| Pack | Price | Per scan |
|---|---|---|
| 1,000 scans | $15 | 1.5¢ |
| 5,000 scans | $60 | 1.2¢ |
| 25,000 scans | $250 | 1.0¢ |
| 100,000 scans | $800 | 0.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.
Usage and history
Section titled “Usage and history”GET /v1/identify/usageYour 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.
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.
Request
Section titled “Request”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.
| Body | How |
|---|---|
multipart/form-data | the photo in an image field |
application/json | {"image": "<base64 or data URL>"} |
image/jpeg, image/png, image/webp | the raw image bytes |
Options (query parameters, or fields in the form or JSON body):
| Parameter | Type | Description |
|---|---|---|
game | string | A game slug from GET /v1/games. Searches only that game. Leave it out to let the photo decide. |
include | string | prices adds the current prices of the top candidate. No extra charge. |
Examples:
# Multipart uploadcurl -X POST "https://api.tcgapi.dev/v1/identify?include=prices" \ -H "X-API-Key: YOUR_KEY" \
# Raw image body, searching one gamecurl -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.jpgconst 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);Response
Section titled “Response”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 }}status
Section titled “status”| Value | Meaning |
|---|---|
matched | Exactly one catalog card fits everything the photo shows: the printed number or code, the name, and any edition stamp. It is candidates[0]. |
ambiguous | Several 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. |
retake | The 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_found | Nothing in the catalog matches what was read. |
unsupported | A 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.
Fields
Section titled “Fields”read: what the photo shows. Empty fields are unreadable, not guessed.stampis the edition mark seen (for example1st EditionorPrerelease),nullwhen the photo clearly shows none, andunclearwhen glare or cropping hides where it would be.gradeis filled for graded slabs.candidates: up to 10, best first.match.numberisexact,partial(the same number with the set total on one side only, for example169/249against169) orunknown(no number to compare).match.nameandmatch.setare the share of words that agree (0 to 1).match.editionisfalsewhen 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.
Errors
Section titled “Errors”| Status | Code | When | Scan used |
|---|---|---|---|
| 400 | INVALID_IMAGE | No image, unsupported format, or larger than 8 MB | No |
| 400 | INVALID_GAME | game is not a catalog game slug | No |
| 401 | API_KEY_REQUIRED | No key and no x402 payment | No |
| 402 | SCAN_BALANCE_EMPTY | No scans left. Buy a pack in the dashboard. | No |
| 403 | IDENTIFY_REGION_UNAVAILABLE | The photo reader is not available in your country. Retrying won’t help. | Refunded |
| 503 | IDENTIFY_UNAVAILABLE | The photo reader is unavailable | Refunded |
Accuracy
Section titled “Accuracy”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
matched88% 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
matched55% of the time. Most of the rest wereambiguousbetween 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
gamewhen 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.