Any Pokémon card, identified from a photo, in one call.
Post a photo, get the exact printing back as JSON. It is the same recognition engine that runs the moonstone scanner, behind one REST endpoint and a hard monthly quota.
- 97.5%top-1 on a 159-photo benchmark
- 60,000+printings in the catalog
- EN + JP1996 vintage to this month
- PSA · BGS · CGCslab labels parsed
- 8ranked candidates per scan
Free sandbox tier, no credit card to start. Holo, reverse, Poké Ball and Master Ball patterns are all separate printings here, and the accuracy claim above is one fixed benchmark of real listing photos that anyone can hold us to. See how it is measured.
curl -X POST https://www.moonstonehq.com/api/v1/scan \
-H "x-api-key: mk_live_..." \
-H "Content-Type: application/json" \
-d "{\"image\": \"$(base64 -i card.jpg)\"}"From zero to a first match
Three steps, one endpoint, no SDK to install. A key from the dashboard works against production immediately on the free tier.
- Create a key.
Sign in and generate a key. The secret is shown once, so store it server-side. Every key starts on the free Sandbox plan.
- Send a photo.
POST it to
/api/v1/scanas base64. Phone photos, listing photos and slab photos all work, with no cropping or warping needed. - Read the match.
matchis the best identification, with catalog id, name, number and set.candidatescarries the ranked alternatives andconfidenceruns 0 to 100.
Accuracy and latency, on one fixed benchmark
Every figure below comes from the same set of 159 photographs taken from live marketplace listings. The set does not change between runs, it was not picked for easy cards, and the engine never sees the listing title that arrived with the photo.
- 97.5%155 / 159Top-1 accuracy, productionThe latest run of the benchmark against the live API, scored on the top-ranked candidate alone.
- 98.1%156 / 159Top-1 accuracy, certified local runThe gate a scanner change has to clear before it is allowed to ship.
- 5.3sp50Server time, image pathMedian server time across the same 159 calls against production, for a request that carries a photo.
- 5.9sp50Round trip, image pathThe same calls timed at the caller instead, with the upload and the network included.
The 159 images come from live marketplace listings: phone angles, glare on foil, cropped corners, cluttered backgrounds, and titles that name the wrong card. The engine is never shown the title. It is scored on pixels.
A case passes when the top-ranked candidate is the exact printing, down to the collector number. The right Pokémon from the wrong set is a miss, and so is the correct card sitting in second place.
The production figure comes from the endpoint you would be paying for, on the same catalog snapshot it serves everyone else. The local figure is the pre-ship gate, which is why the two are reported separately instead of averaged into one friendlier number.
The production run missed 4 of the 159, the certified local run missed 3. One of those is not a misidentification: the catalog carries two rows for the same physical card, and the scan named the other row.
Most card recognition APIs publish no accuracy figure at all. A percentage tied to a fixed, named, adversarial set is one you can check, and one we have to re-earn on every change to the engine.
- Japanese and English. Japanese-exclusive prints next to their English mirrors, which share the art and differ only in the printing.
- Graded slabs. Shot through the case, label in frame, carrying the glare and reflections a case adds.
- Same-art reprints. Printings separable only by collector number or by printed set total.
- Promos and subsets. Trainer Gallery, Galarian Gallery, and Classic Collection replicas that print the original card's number on the face.
- Foil and pattern variants. Holo, reverse, Poké Ball and Master Ball, each a distinct printing in the catalog.
- Vintage through current. 1996 releases scored in the same benchmark as sets that shipped this month.
Half of those 159 calls came back faster than the median and half came back slower. A large image, a cold path, or an ambiguous photo that triggers the second visual pass all sit above it. Size client timeouts off the tail rather than off the median, and run scans concurrently up to your plan's per-minute limit instead of waiting on one at a time.
Part of every scan is the server running your photo through the vision model. A client that can run that model itself sends the result in vectors and the server skips that work, which lowers server time. No p50 is published for that path yet, because it has not been measured across the whole benchmark the way the two figures above were.
When retrieval is ambiguous the API runs a second visual pass before it answers. refine: false skips it and trims latency, and it gives up some of the accuracy these figures were measured with. Leave it on unless you have measured that trade on your own images.
Request
One required field and two optional ones. Everything else about a scan is decided by the engine rather than by a knob you have to tune.
https://www.moonstonehq.com/api/v1/scanAuthenticate with an x-api-key header, or with Authorization: Bearer if that suits your client better. The body is JSON.
imagerequiredstring- The card photo as base64 (raw or data URI). JPEG, PNG, or WebP. Long edge around 1200 to 1600 px scans best; keep the request body under ~3 MB of image data (the platform rejects larger bodies before our JSON error can reach you).
vectorsoptionalnumber[][], optional- An escape hatch for callers that can run the vision model themselves: a single SigLIP embedding of the photo you are sending (Xenova/siglip-base-patch16-224, 768-d, mean-pooled over the patch tokens, L2-normalized), as a one-element array. The server then skips its own embedding pass and the scan spends less time on the server. Still send image: the rest of the pipeline reads pixels. A vector that fails validation is ignored and the server embeds from image as usual. A vector that validates is trusted and used for retrieval, so one computed from a different photo, a different model, or different preprocessing will cost accuracy. Send it only if you are running the same model on the same bytes.
footerHioptionalstring, optional- A native-resolution crop of the card's bottom 18 percent, as a data URI, taken after you have located the card's corners. The collector-number reader prefers it and reads noticeably better from it than from a downscaled full frame. Send it only if you are already detecting the card in your own pipeline: a blind bottom crop of an untrimmed photo feeds the reader background instead of a footer, which is worse than sending nothing.
labelHioptionalstring, optional- The same idea for a graded slab: a native-resolution crop of the grading label. The label parser reads company, grade and certification number off it. Sending it also TELLS the engine the photo is a slab, and that changes how the rest of the card is read, so send it only when you have actually detected one. On a raw card it is worse than sending nothing.
refineoptionalboolean, default true- When retrieval is ambiguous the API automatically runs a second visual-refine pass before answering. Set false to skip it and shave latency.
Response
One stable shape for every outcome, including the ones that are not a card. When the engine cannot identify the photo it says so rather than returning its best guess as if it were the answer.
{
"object": "scan_result",
"match": {
"id": "sv2a-150",
"name": "Mewtwo",
"number": "150",
"set": "Pokemon Card 151",
"image": "https://...",
"priceUsd": 42.31,
"url": "https://www.moonstonehq.com/card/sv2a-150"
},
"candidates": [ ... up to 8, ranked ... ],
"confidence": 87,
"confident": true,
"cardBack": false,
"sealed": null,
"slabLabel": { "company": "PSA", "grade": "9", ... },
"usage": { "plan": "developer", "scansUsedThisPeriod": 41, "monthlyQuota": 500 }
}x-monthly-usedScans consumed in the current period.x-monthly-quotaThe plan's monthly ceiling.x-ratelimit-limit-rpmRequests per minute allowed on the plan.
POST /api/v1/scan1 unit. One card photo in, ranked candidates out. The endpoint everything below is measured against.POST /api/v1/binder9 units. One photo of a 9-pocket binder page in, per-pocket candidates out. Bills nine units because it is nine scans. A page that does not complete bills nothing.
Idempotency-KeyApplies to /api/v1/scan; /api/v1/binder does not support it yet. Send any unique string up to 255 characters. A retry carrying a key already used returns 409 without running or billing a second scan, so a client timeout cannot cost you three units for one photo. Keys are remembered for 24 hours and are scoped to your account.Fair useRequests to /api/v1/scan that reach the scan backend, successful or not, are capped daily at four times your monthly scan quota. This is separate from the quota itself, which only completed scans consume.
matchobject | null- The best identification: catalog id, name, number, set, and art url. Null when nothing was identifiable, which is also what a card back or a sealed product returns.
candidatesarray- The ranked alternatives, up to 8, each in the same shape as match. candidates[0] is match.
confidencenumber | null- How strongly the top candidate won, 0 to 100.
confidentboolean- The engine's own verdict on that score, so you do not have to pick a threshold.
cardBackboolean- The photo showed the back of a card. Detected and refused rather than guessed at.
sealedobject | null- The frame was sealed product, not a single card: a box, an ETB, or a pack. Carries the identity, a link, and a price when known.
slabLabelobject | null- The parsed grading label on a slab photo: company, grade, and certification details when legible.
usageobject- Your own meter after this call: plan, scansUsedThisPeriod, monthlyQuota.
Errors
Every failure comes back in the same envelope. Authenticated responses carry the metering headers; a 401 or a 429 omits the ones it cannot know rather than sending them empty, so test for the header before parsing it. A scan that never completed does not consume quota, and 402 and 429 never do. On a 502 the scan may or may not have completed: read x-monthly-used on that response, which is authoritative, rather than assuming either way. 502 and 503 carry retryable: true.
400Bad requestThe body was not JSON, or image was missing. The message names which.401UnauthorizedMissing, invalid, or revoked API key.402Quota exhaustedThe monthly quota is spent. Upgrade the plan; requests are never silently billed past quota.413Image too largeKeep the request body under ~3 MB of image data.429Rate limitedThe per-minute limit was hit. Honor the Retry-After header.502Backend unavailableThe scan backend did not complete. Read x-monthly-used on the response for your authoritative count before retrying.409Duplicate Idempotency-KeyA scan with this Idempotency-Key is already in flight or has completed. Reuse the original response, or send a new key.503Temporarily unavailableAuthorization could not be read. Honor Retry-After and retry; nothing was billed.
{
"error": { "code": 402, "message": "Monthly quota exhausted." }
}A 429 also sets Retry-After. A card back or a sealed product is not an error: the call succeeds with match: null and the field that says what was in frame.
Hard quotas, honest metering
Only successful scans count. A scan that never completed does not touch your quota, nothing bills past your plan, and the meter resets when your billing period does, on the same day of the month you subscribed. Sandbox accounts, which have no billing period, reset on the 1st (UTC).
Sandbox
Free
- 25 scans / month
- 10 requests / minute
- Full catalog: EN + JP, slabs, variants
- No card required
Developer
$9 / month
- 500 scans / month
- 30 requests / minute
- Full catalog: EN + JP, slabs, variants
- Cancel anytime in the billing portal
Growth
$39 / month
- 5,000 scans / month
- 60 requests / minute
- Full catalog: EN + JP, slabs, variants
- Cancel anytime in the billing portal
Scale
$199 / month
- 50,000 scans / month
- 120 requests / minute
- Full catalog: EN + JP, slabs, variants
- Cancel anytime in the billing portal
Paid plans renew automatically each month at the price shown, through Stripe, until you cancel. Cancel yourself in the billing portal at any time and keep the period you paid for. Terms: Billing and API.
Already a moonstone Pro member? The membership includes 200 scans a month at 20 requests per minute on the same engine. Full plan comparison lives on the pricing page.
Pokémon card scanning API: common questions
Is there an API to identify Pokémon cards from a photo?
Yes. The moonstone Scan API identifies any Pokémon card from a single photo: send a base64 image to POST /api/v1/scan and get back the card's catalog id, name, number, set, and ranked alternatives with a confidence score. It handles phone photos, eBay listing photos, and graded slab photos without cropping.
Does the Pokémon card scanner API work on Japanese cards?
Yes. The catalog covers 60,000+ printings across English and Japanese sets, from 1996 vintage through this month's releases, including Japanese exclusives and pattern variants like Poké Ball and Master Ball reverses.
Can it read graded slabs like PSA, BGS, and CGC?
Yes. Slab photos return both the card identification and the parsed grading label (company, grade, and certification details when legible). Label evidence like set codes and variant phrasing is used to disambiguate visually similar printings.
How accurate is the card recognition?
As of September 19, 2026, on a fixed benchmark of 159 real marketplace listing photos, production runs identify 155 correctly (97.5%), with a run-to-run range of 154 to 157. The figure is the one that reproduces across consecutive runs rather than the best single run. The set is not curated for easy cards: it includes Japanese printings, graded slabs shot through the case, and same-art reprints that differ only by collector number. The same benchmark is the gate every scanner change has to pass before it ships, run at least twice.
How fast is the Pokémon card scanning API?
Measured against production over those same 159 photos, a request that carries a photo has a p50 of about 5.3 seconds of server time and about 5.9 seconds round trip. Those are medians, not guarantees: half of the calls were slower, and a large image or an ambiguous photo that triggers the second visual pass sits above the median. Callers running the same vision model themselves can send the embedding in the vectors field, which skips the server's embedding pass and lowers server time, at the cost of owning the correctness of that vector.
How much does the Pokémon card scanning API cost?
A free Sandbox tier includes 25 scans per month with no credit card. Paid plans start at $9/month for 500 scans, $39/month for 5,000, and $199/month for 50,000, with hard quotas: failed calls never count, and nothing bills past your plan. A paid plan is a monthly subscription that renews automatically until you cancel; cancelling is self-service in the billing portal, you keep the period you paid for, and the account then returns to the free Sandbox tier.
How this stays honest
One fixed benchmark of 159 real listing photos, run against production, scored on the top answer alone. The current figure is 97.5%, printed above with the method and the misses next to it rather than on its own.
Identifications resolve to a catalog of real printings synced daily from market sources. No hallucinated cards: when the photo is a card back or unidentifiable, the API says so.
Quotas and rate limits apply per account across all keys. Keys can be created and revoked, up to five active at a time, from the dashboard, and a revoked key stops working on its next call. Keep secrets server-side; never ship them in client apps.
Start on the free tier
25 scans a month, no credit card, the same catalog and the same engine as every paid plan.