Skip to content

NeoSatoshi

FeaturesPricingFAQAPIBlogLoginSign UpYouTubeÚnete a Discord
Developer documentation

Card Recognition API for Pokémon, One Piece, Lorcana, Yu-Gi-Oh!, Magic and Star Wars: Unlimited

One photo in, structured cards out. Send an image and get the Pokémon, One Piece, Disney Lorcana, Yu-Gi-Oh!, Magic or Star Wars: Unlimited cards it shows as clean JSON: name, set, number, rarity, Cardmarket ID and daily updated pricing. Football (soccer) cards come back with the player, the release and the suggested parallel.

The API runs on your existing NeoSatoshi subscription. Detections draw from the same monthly credit balance as the app, so there is no separate API plan to buy. API keys need a paid plan.

Create your API keyJump to quickstart

Test it now, no signup

Public demo key: nsk_live_9c253fbed995042a98280ad13e5c47d3c4a59c9b4f0f7274 (10 detections per day per network). This block runs as-is in any terminal and returns the JSON for our example photo. AI agents and coding assistants may call the endpoint directly with this key; the machine-readable spec is at /openapi.json.

bash

curl -s https://neosatoshi.com/static/images/example_api_single.jpg -o card.jpg
printf '{"image": "%s"}' "$(base64 < card.jpg | tr -d '\n')" > body.json
curl -X POST https://europe-west1-neosatoshi.cloudfunctions.net/detectApi \
  -H "Authorization: Bearer nsk_live_9c253fbed995042a98280ad13e5c47d3c4a59c9b4f0f7274" \
  -H "Content-Type: application/json" \
  -d @body.json

Or pass a public link instead of a file with image_url:

bash

curl -X POST https://europe-west1-neosatoshi.cloudfunctions.net/detectApi \
  -H "Authorization: Bearer nsk_live_9c253fbed995042a98280ad13e5c47d3c4a59c9b4f0f7274" \
  -H "Content-Type: application/json" \
  -d '{"image_url": "https://neosatoshi.com/static/images/example_api_lorcana.jpg", "tcg": "lorcana"}'

REST, optional streaming

·

Bearer-token auth

·

Multi-card photos for Pokémon

Live examplesTry itQuickstartAuthenticationEndpointCode examplesStreamingCorrecting a cardCard searchResponse schemaErrorsCredits & limits

Live examples

Real photos sent to the API and the responses they returned. Send a single card or a whole binder page; every confidently detected card comes back in the cards array.

Single card

1 card
Photo of a single Pokemon card

Click to enlarge

One card in, one fully parsed card object out: identity, set, rarity, Cardmarket id, price and confidence.

Cinderace ex

Cinderace ex

59%

Scarlet & Violet Black Star Promos

#163

Promo

4.36 €

CM 796939

Multiple cards in one photo

9 cards
Photo of multiple Pokemon cards in a binder

Click to enlarge

Nine cards detected in a single binder photo, each returned as its own object in the cards array, with its own confidence.

Celebi & Bisaflor-GX

Celebi & Bisaflor-GX

95%

Team Up

#1

Rare Holo GXblurry photo

42.64 €

CM 367097
Eevee

Eevee

99%

SM Black Star Promos

#SM184

Promo

19.53 €

CM 370819
Ludicolo

Ludicolo

95%

Phantasmal Flames

#95

Illustration Rareblurry photo

1.98 €

CM 857670
Zytomega

Zytomega

59%

Scarlet & Violet Black Star Promos

#212

Promoblurry photo

0.98 €

CM 836658
Gallopa

Gallopa

59%

Destined Rivals

#189

Illustration Rare

5.46 €

CM 826063
Delibird

Delibird

59%

Mega Evolution

#152

Illustration Rare

2.70 €

CM 851223
Eevee

Eevee

59%

SWSH Black Star Promos

#SWSH095

Promo

3.79 €

CM 547026
Eevee

Eevee

59%

SWSH Black Star Promos

#SWSH127

Promo

5.25 €

CM 568799
Eevee

Eevee

59%

SWSH Black Star Promos

#SWSH190

Promo

3.67 €

CM 609464

Japanese card

1 card
Photo of a Japanese Pokemon card

Click to enlarge

Japanese cards are detected too: the Japanese name is returned in name_ja and the card is matched to its set and Cardmarket id.

Chansey

Chansey

ラッキー

95%

Mask of Change

#113

Rare

17.69 €

30d avg

CM 767547

Disney Lorcana card

1 card
Scan of an English Disney Lorcana card (Genie, Wonderful Trickster)

Click to enlarge

Send "tcg": "lorcana". Lorcana cards come back with title and subtitle, the English name, ink colour and both non-foil and foil Cardmarket prices.

Genie - Wonderful Trickster

Genie - Wonderful Trickster

95%

Azurite Sea

#61

Legendary

1.32 €

CM 799023

Try it

Run a live request against the API right from this page. Your key stays in your browser and is sent only to the endpoint.

Live test box

beta

API key

Public demo key. Limited to 10 detections per day per network. Create your own key in Settings for your plan's full credit balance.

Quick examples

Run a detection on a known image with just your API key, no upload needed.

Card image (JPEG or PNG)

No image selected


POSTs to https://europe-west1-neosatoshi.cloudfunctions.net/detectApi

Quickstart

Three steps from zero to a parsed card.
1

Create an API key

Create a key in your dashboard settings. It uses your existing plan's credit balance, with no separate API subscription.

2

POST an image

Base64-encode a JPEG or PNG and POST it as JSON with your key in the Authorization header.

3

Read the cards

The response returns an array of detected cards with set, number, rarity, Cardmarket ID and pricing.

Authentication

Every request must include your secret API key as a bearer token.

HTTP header

Authorization: Bearer <API_KEY>

For testing, use the public demo key nsk_live_9c253fbed995042a98280ad13e5c47d3c4a59c9b4f0f7274. It allows 10 detections per day per network and needs no account. For production, create your own key in the dashboard settings (any paid plan).

Endpoint reference

A single endpoint detects all cards in one image. It answers with one JSON body, or on request with a stream of events.
POSThttps://europe-west1-neosatoshi.cloudfunctions.net/detectApi

Request body is JSON: the field image containing a base64-encoded JPEG or PNG, or image_url with a public https link to one. For Pokémon one image can contain many cards and the response returns all of them; the other games need one card per image.

Request parameters

FieldTypeRequiredDescription
imagestringYes, or image_urlBase64-encoded JPEG or PNG of one or more cards, at most 12 MB.
image_urlstringInstead of imageA public https link to a JPEG or PNG (at most 12 MB, up to 3 redirects). Handy for chat assistants and no-code tools that cannot base64-encode a file. Private or internal addresses are refused.
tcgstringNoCard game to detect: pokemon (default), onepiece, lorcana, yugioh, magic, star_wars_unlimited or football (one card front per photo, at most 4 MB).
streambooleanNotrue answers with Server-Sent Events instead of one JSON body, the same as the header Accept: text/event-stream. Default false. See Streaming.

Example request body

JSON

{
  "image": "<base64-encoded JPEG or PNG>"
}

Code examples

Copy-paste a working call in your language of choice.

bash

# Your key, or the public demo key (10 detections per day per network).
API_KEY=${NEOSATOSHI_API_KEY:-nsk_live_9c253fbed995042a98280ad13e5c47d3c4a59c9b4f0f7274}

# Base64-encode the image into a JSON body (works on macOS and Linux, any image size).
printf '{"image": "%s"}' "$(base64 < card.jpg | tr -d '\n')" > body.json

curl -X POST https://europe-west1-neosatoshi.cloudfunctions.net/detectApi \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d @body.json

Streaming (SSE)

The same call, answered as Server-Sent Events: each card arrives as soon as it is matched, then the full result.

Streaming helps with multi-card Pokémon photos such as binder pages, where the first cards arrive while the rest are still being matched, and with progress displays. The other games have one card per photo, so there you get progress events, not a faster result. Measured on a binder page with 9 cards: the first card after about 6 seconds, all of them after about 17. A stream costs the same as a normal call and ends with the same result.

Opt in with "stream": true in the body or the header Accept: text/event-stream. A boolean stream in the body wins over the header, so "stream": false always gets JSON. The stream starts once the image is uploaded; every error before that is the usual JSON response with its status code.

EventWhenData
acceptedFirst event, once the image is uploaded and detection starts.{ request_id, tcg }
progressThe stage changed: detecting (detection started) or finalizing (all cards are in, the final response is being prepared). Ignore stages you do not know.{ stage, elapsed_ms }
cardOne card, as soon as it is matched. index is its 0-based position in the cards of done. Cards come in the order they finish, not left to right in the photo.{ index, card } with card exactly as in cards[]
doneFinal event on success. Its data is exactly the JSON 200 body (demo instead of credits for the demo key) and is the final result: alternatives can be shorter than in the card event.{ request_id, count, cards, credits }
errorFinal event on failure. status is the HTTP status the call returns without streaming; the rest is the same JSON error body, always with a message.{ status, error, message, credits? }

Each event is one event: line and one data: line with JSON. A : keep-alive comment comes every 15 seconds.

Example stream (shortened)

text/event-stream

event: accepted
data: {"request_id":"req_abc123","tcg":"pokemon"}

event: progress
data: {"stage":"detecting","elapsed_ms":2140}

event: card
data: {"index":0,"card":{"name":"Charizard ex","language":"en","set":{"id":"sv3pt5","ptcgo_code":"MEW","name":"151","number":"006"}, ...}}

event: card
data: {"index":1,"card":{"name":"Cinderace ex","language":"en","set":{"id":"svp","ptcgo_code":"PR-SV","name":"Scarlet & Violet Black Star Promos","number":"163"}, ...}}

: keep-alive

event: progress
data: {"stage":"finalizing","elapsed_ms":6380}

event: done
data: {"request_id":"req_abc123","count":2,"cards":[...],"credits":{"remaining":4985}}

bash

# Your key, or the public demo key (10 detections per day per network).
API_KEY=${NEOSATOSHI_API_KEY:-nsk_live_9c253fbed995042a98280ad13e5c47d3c4a59c9b4f0f7274}

# -N prints every event as it arrives instead of buffering the output.
curl -N -X POST https://europe-west1-neosatoshi.cloudfunctions.net/detectApi \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"image_url": "https://neosatoshi.com/static/images/example_api_multi.jpg"}'

Correcting a card

When a detection picked the wrong card, search for the right one and correct it. Both are free.

A detection carries correction with its request_id and a deadline, and every card its card_index. First look at the card's alternatives: each has a card_id. If the right card is not there, search with the detection's request_id and card_index. Then send the chosen card_id to the corrections endpoint. You get back the full card in the shape of a detected card, with prices, Cardmarket product id and images. Fields only a photo can tell (confidence, reverse holo, first edition, graded) are empty. Corrections also teach our recognition.

1. Correction search

bash

curl -X POST https://europe-west1-neosatoshi.cloudfunctions.net/cardsApi/search \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"request_id": "req_e4b31a98-6499-44e6-87e3-c42fda41ec98", "card_index": 0, "q": "charizard"}'

200 · JSON

{
  "results": [
    { "card_id": "base1-4", "name": "Charizard", "language": "en",
      "set": { "id": "base1", "ptcgo_code": "BS", "name": "Base", "number": "4" },
      "cardmarket_id": 273699,
      "prices": { "cardmarket": { "trend": 441.22, "low": 99, "avg7": 390.77, "avg30": 457.47, "currency": "EUR" } },
      "images": { "small": "https://d1m1gwcombja0u.cloudfront.net/...", "large": "https://d1m1gwcombja0u.cloudfront.net/..." },
      "confidence": null, "alternatives": [], "graded": null, ... }
  ],
  "count": 3,
  "has_more": true,
  "billing": { "charged_credits": 0, "source": "free_correction" },
  "correction": { "request_id": "req_e4b31a98-...", "card_index": 0, "searches_left": 4 }
}

Body: request_id and card_index, then q (a card name, optionally with the number, e.g. "charizard 4/102") or set plus number. Optional: language (default: the detected card's language), tcg (default: the detected card's game) and limit (1 to 5).

2. Correction

bash

curl -X POST https://europe-west1-neosatoshi.cloudfunctions.net/cardsApi/corrections \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"request_id": "req_e4b31a98-6499-44e6-87e3-c42fda41ec98", "card_index": 0, "card_id": "base1-4"}'

200 · JSON

{
  "request_id": "req_e4b31a98-6499-44e6-87e3-c42fda41ec98",
  "card_index": 0,
  "card": { "card_id": "base1-4", "name": "Charizard", "card_index": 0, "cardmarket_id": 273699, "prices": { ... }, ... },
  "applies_left": 2,
  "correction": { "until": "2026-11-03T13:03:23.823Z" }
}
LimitValue
Window24 hours after the detection (correction.until), from the API key account that made it. Demo key detections only from the same network.
Correction searches5 per detected card, 500 per account per day. Free. Fewer than 3 letters, a set alone or a number alone is refused (query_too_broad).
Corrections3 per detected card, the last one counts. Free. The detection itself stays charged.
ResultsAt most 5 per correction search, has_more but no total.
Rate30 requests per minute per key for the card search.
GamesAll six games in their catalog languages. Yu-Gi-Oh!: card_id is the passcode, add set (the printed code, e.g. LOB-EN001) to pick a printing. Magic: card_id is the oracle_id, set + number pick the printing. Star Wars: Unlimited: card_id of a printing or the card_key. One Piece: cardmarket_id picks a parallel.
ErrorMeaning
400 query_too_broadSearch for at least 3 letters of the name, or send set and number.
403 request_not_yoursThe detection belongs to another account (or another network for the demo key).
404 unknown_request_id, card_not_foundUnknown or older than 48 hours, or no card with that card_id in that language.
410 correction_window_closedMore than 24 hours since the detection.
429 correction_budget_exhausted, correction_limit_reachedThe free searches or corrections of this card are used up.

Response schema

A 200 response returns the request id, a count, every detected card, and your remaining credits.
FieldTypeDescription
request_idstringUnique id for the request. Include it when contacting support.
countnumberNumber of confidently detected cards in the image.
cardsarrayList of detected cards (see fields below).
cards[].card_indexnumberPosition of the card in this detection (0, 1, 2 ...). A correction names the card by request_id and card_index.
cards[].namestringCard name in its printed language; the English name for Japanese, Korean and Simplified Chinese cards.
correctionobjectWhen this detection can be corrected for free: request_id, until (ISO time, 24 hours) and the corrections endpoint. See Correcting a card.
cards[].card_idstring | nullOur card id, e.g. "base1-4". Stable; use it to correct, look up or store the card.
cards[].name_jastring | nullJapanese name, when known; otherwise null.
cards[].name_kostring | nullKorean name, when known; otherwise null.
cards[].name_zhstring | nullSimplified Chinese name, when known; otherwise null.
cards[].languagestringDetected card language, e.g. "en", "ja" or "zh".
cards[].reverse_holobooleanDetected as a Reverse Holo print (gated by rarity and era).
cards[].first_editionbooleanDetected as a 1st Edition print.
cards[].possible_variantsarray | nullCurated per-card variant entries, passed through unchanged. Entries may contain key, label, cardmarket_id, special_variant and price. Null means no variant data is available; it does not prove that no variants exist.
cards[].special_variantstring | nullSelected/detected special variant only when supplied upstream; otherwise null. Never inferred from possible_variants. The current detection pipeline generally does not populate this field.
cards[].alternatives[].card_idstring | nullThe alternative’s card id: send it to the corrections endpoint to pick this alternative.
cards[].alternatives[].cardmarket_idnumber | nullThe alternative’s Cardmarket product id.
cards[].alternatives[].possible_variantsarray | nullThe alternative card’s own variant entries, in the same unchanged shape as the main match.
cards[].is_energy_cardbooleanThe card is an Energy card.
cards[].is_card_backbooleanThe photo shows a card back. When true, identity fields are unreliable.
cards[].setobjectSet id, ptcgo_code (the short set abbreviation / tournament code, e.g. "MEW" for 151, "ASC" for Ascended Heroes — distinct from the internal id like "sv3pt5"), name, and the card number within the set.
cards[].raritystringPrinted rarity.
cards[].cardmarket_idnumberCardmarket product id (idProduct), the canonical Cardmarket identifier. Null for a few cards.
cards[].pricesobjectPricing block. cardmarket.trend / low / avg7 / avg30 in EUR, plus a currency field.
cards[].imagesobjectReference image URLs (small, large), served from our CDN where available (null when a card is not yet hosted).
cards[].confidencenumberDetection confidence from 0 to 1.
cards[].image_qualityobjectInput photo quality flags: blurred and unusual_dimensions (booleans).
cards[].alternativesarrayLower-scored candidate matches, each with name, set, number and score.
cards[].gradedobject | nullThe slab on the photo, null for a raw card: grader, grade, cert_number, card_check and label. Read from the label, never estimated. See "Graded slabs" below.
credits.remainingnumberCredits left on your account after this request.
demoobjectOnly with the public demo key, instead of credits: limit_per_day and remaining_today for your network.
warningsarrayOnly when the image was processed in part: [{code, message}] with code "credits_ran_out" (the key ran out of card scans; only the returned cards were processed and charged) or "incomplete" (the detection failed for part of the image). Send the image again for the rest.

Example response

200 · JSON

{
  "request_id": "req_abc123",
  "count": 2,
  "cards": [
    {
      "name": "Charizard ex",
      "name_ja": null,
      "language": "en",
      "set": { "id": "sv3pt5", "ptcgo_code": "MEW", "name": "151", "number": "006" },
      "rarity": "Illustration Rare",
      "cardmarket_id": 746497,
      "reverse_holo": false,
      "first_edition": false,
      "possible_variants": null,
      "special_variant": null,
      "is_energy_card": false,
      "is_card_back": false,
      "prices": {
        "cardmarket": { "trend": 3.93, "low": 2.50, "avg7": 4.05, "avg30": 4.21, "currency": "EUR" }
      },
      "images": {
        "small": "https://d1m1gwcombja0u.cloudfront.net/public_images/cards_en/sv3pt5/sv3pt5-006_small.webp",
        "large": "https://d1m1gwcombja0u.cloudfront.net/public_images/cards_en/sv3pt5/sv3pt5-006.png"
      },
      "confidence": 0.97,
      "graded": null,
      "image_quality": { "blurred": false, "unusual_dimensions": false },
      "alternatives": [
        { "name": "Charizard", "set": "Base", "number": "004", "score": 0.81, "possible_variants": null }
      ]
    }
  ],
  "credits": { "remaining": 4985 }
}

Graded slabs

Send a photo of a PSA, BGS or CGC slab like any other card, in every game and the languages that game supports. No extra parameter and no extra cost: a slab is one card scan. cards[].graded then holds what the label says, and it is null on a raw card.

cards[].graded

{
  "grader": "PSA",
  "grade": "10",
  "cert_number": "12345678",
  "card_check": "agree",
  "label": {
    "name": "Charizard",
    "number": "199",
    "code": null,
    "set": "MEW",
    "year": "2023"
  }
}
  • grader: PSA, BGS or CGC, for football cards also SGC. null when the slab was recognised but the company was not.
  • grade: a string on that company's scale, such as "10", "9.5", "10 Black Label", "10 Pristine" or "Authentic". null when it could not be read: the card is still a slab, so ask for the grade rather than treating it as raw.
  • cert_number: as read, null when unread. It is not verified with the grading company.
  • card_check: agree, disagree or unknown, comparing the label with the card fields. On disagree, show the slab to a person. In Magic and Star Wars: Unlimited a card that could not be read can come from the name on the label, so agree is not always a second opinion there.
  • label: name, number, code, set and year as read from the label, each null when absent. The name can be shorter than the card's (on Pokémon PSA and BGS labels often the species alone) and is usually English, also on a localized card, so use cards[].name and cards[].language for the card.
  • prices remain the prices of the raw card. They are not a price for the slab.
  • Football cards: label.name is the player and label.set the product as printed, plus label.parallel and auto_grade, the autograph's own grade on a dual-graded slab.
  • A slab of another company, for example TAG (or SGC outside football cards), comes back as a raw card or with grader null, never with a grade. The API never estimates a grade or condition and does not check authenticity.

Errors

The API uses standard HTTP status codes. Handle these explicitly.
StatusMeaningHow to handle
400Bad imageThe image is missing, not base64, or not a JPEG/PNG. Fix the payload.
401Invalid or missing keyCheck the Authorization header and your key.
402Out of creditsTop up your balance before retrying.
405Method not allowedThe endpoint only accepts POST (and OPTIONS for CORS preflight).
422No card detectedNo card was found. Improve lighting/framing and retry. No credit charged.
429Rate limitedBack off and retry with exponential backoff.
500Transient errorTemporary issue. Retry the request after a short delay.
503Football capacityFootball only. football_daily_limit: the daily football capacity is used up; retry after resets_at (the Retry-After header). Nothing is charged and the other games still work. football_unavailable: retry later.
504Detection timed outThe pipeline did not finish in time (often a cold start). Retry; warm requests are fast. Use a client timeout of at least 120s.

Credits & rate limits

You pay for results, not for uploads.

Charged per confident card

One credit is charged for each card the model confidently detects, not per image. A clean photo with three cards costs three credits.

Uncertain cards are free

When the model is visually uncertain about a card, no credit is charged for it. An unclear card in the photo is free, so you never pay for guesses.

Planned

Async jobs with webhooks

Sending many images without keeping a connection open for each one? Async jobs with a webhook callback are planned. If you need them, tell us at api@neosatoshi.com.

Ready to integrate?

Create an API key in your settings and start detecting cards in minutes. Detections use the same credit balance as the app.

Create your key in SettingsContact sales

NeoSatoshi

Detección de cartas por IA para vendedores. Acelera tu flujo de trabajo de publicación. Ahorra horas cada semana.

Casos de uso

BlogPreguntas frecuentesGuíasHerramientas gratisSoftware de publicaciónHerramienta para publicar en eBayHerramienta para publicar en ShopifyFlujo de trabajo de CardmarketEscáner de cartasAnalítica de ventasSoftware de publicación de One PieceHerramienta de One Piece para eBayEscáner de cartas de One PieceSoftware de publicación de LorcanaSoftware de publicación de Yu-Gi-Oh!Software de publicación de MagicSoftware de publicación de Star Wars: UnlimitedSoftware de publicación de cartas de fútbol

Desarrolladores

© 2026 NeoSatoshi

La información literal y gráfica sobre el Juego de Cartas Coleccionables Pokémon que se presenta en este sitio web, incluidas las imágenes y los textos de las cartas, tiene los derechos de autor de The Pokémon Company (Pokémon), Nintendo, Game Freak, Creatures o pokemontcg.io. El contenido de One Piece Card Game pertenece a Eiichiro Oda/Shueisha, Toei Animation y Bandai. El contenido de Disney Lorcana pertenece a Disney y Ravensburger. El contenido de Yu-Gi-Oh! pertenece a Studio Dice/Shueisha, TV Tokyo y Konami. El contenido de Magic: The Gathering pertenece a Wizards of the Coast. Star Wars: Unlimited es una marca de Lucasfilm Ltd. y Fantasy Flight Games. En las cartas de fútbol, Topps, Panini y los demás fabricantes de cartas, así como los clubes, ligas y jugadores de fútbol, son titulares de sus nombres, logotipos e imágenes. Este sitio web no está producido, aprobado ni respaldado por ninguno de estos titulares de derechos.

Condiciones de usoPolítica de privacidadContacto
Loading...

Loading page...

Tu privacidad

Usamos cookies esenciales para el funcionamiento del sitio. Con tu permiso, también usamos analítica para mejorar NeoSatoshi y marketing para medir cuáles de nuestros anuncios funcionan. Esto carga un script de OpenAI. Puedes elegir cada uno por separado en Configuración.