Skip to content

NeoSatoshi

FeaturesPricingFAQAPIBlogLoginSign UpYouTubeDiscord beitreten

Wie baue ich eine Scanner-App für Sammelkarten?

Direkte Antwort

Eine Scanner-App für Sammelkarten nimmt ein Foto auf, schickt es an eine API zur Kartenerkennung und zeigt die Karten an, die zurückkommen. Mit der NeoSatoshi Detection API ist das eine einzige POST-Anfrage: Ein Bild als Base64 oder ein Bildlink geht hinein, JSON mit Name, Set, Nummer, Cardmarket-Produkt-ID und Tagespreis pro Karte kommt heraus, für Pokémon, One Piece, Disney Lorcana, Yu-Gi-Oh! und Magic. Das Python-Skript unten läuft so, wie es ist, mit dem öffentlichen Demo-Schlüssel (10 Erkennungen pro Tag, ohne Anmeldung); für den produktiven Einsatz erstellst du mit jedem bezahlten Plan einen eigenen Schlüssel.

Was du brauchst

  • Python 3 und die Bibliothek requests (pip install requests).
  • Ein Foto mit einer oder mehreren Karten, JPEG oder PNG, bis 12 MB. Mehrere Karten pro Foto gehen bei Pokémon; bei den anderen Spielen schickst du eine Karte pro Foto.
  • Einen API-Schlüssel: den öffentlichen Demo-Schlüssel im Skript unten, oder später deinen eigenen in der Umgebungsvariable NEOSATOSHI_API_KEY.

Schritt 1: ein Foto scannen

Speichere das als scan.py. Es liest das Foto, schickt es als Base64 und gibt jede Karte mit Set, Nummer, Cardmarket-Produkt-ID, 30-Tage-Durchschnittspreis und Konfidenz aus.

Python: scan.py

import base64
import os
import sys

import requests

API_URL = "https://europe-west1-neosatoshi.cloudfunctions.net/detectApi"
# Public demo key: 10 detections per day per network, no signup.
# Set NEOSATOSHI_API_KEY to use your own key instead.
API_KEY = os.environ.get(
    "NEOSATOSHI_API_KEY",
    "nsk_live_9c253fbed995042a98280ad13e5c47d3c4a59c9b4f0f7274",
)


def detect(path, tcg="pokemon"):
    """Send one photo and return the parsed JSON response."""
    with open(path, "rb") as f:
        image = base64.b64encode(f.read()).decode("ascii")
    res = requests.post(
        API_URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        json={"image": image, "tcg": tcg},
        timeout=120,  # synchronous: the cards come back in this response
    )
    if res.status_code == 422:
        return {"count": 0, "cards": []}  # no card found, nothing charged
    res.raise_for_status()
    return res.json()


if __name__ == "__main__":
    tcg = sys.argv[2] if len(sys.argv) > 2 else "pokemon"
    data = detect(sys.argv[1], tcg)
    for card in data["cards"]:
        card_set = card.get("set") or {}
        cm = (card.get("prices") or {}).get("cardmarket") or {}
        print(
            card.get("name"),
            "|", card_set.get("name"), card_set.get("number"),
            "| Cardmarket ID", card.get("cardmarket_id"),
            "| avg30", cm.get("avg30"), cm.get("currency", ""),
            "| confidence", card.get("confidence"),
        )
    if "demo" in data:
        print("Demo detections left today:", data["demo"]["remaining_today"])

Starte python scan.py card.jpg für Pokémon oder gib das Spiel mit: python scan.py card.jpg lorcana (ebenso magic, yugioh und onepiece). Mit dem Demo-Schlüssel zeigt die letzte Zeile, wie viele Erkennungen heute noch übrig sind.

Schritt 2: einen Link statt einer Datei schicken

Liegt das Foto schon online, schick image_url statt image und spar dir das Base64. So kann auch ein Chat-Assistent ohne Codeausführung die API testen. Mit curl:

curl

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"}'

Dasselbe in Python, mit dem Schlüssel aus scan.py:

Python

import requests

from scan import API_KEY, API_URL

res = requests.post(
    API_URL,
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "image_url": "https://neosatoshi.com/static/images/example_api_lorcana.jpg",
        "tcg": "lorcana",
    },
    timeout=120,
)
res.raise_for_status()
for card in res.json()["cards"]:
    print(card.get("full_name") or card.get("name"), card.get("cardmarket_id"))
  • Der Link muss öffentlich sein, https auf dem Standardport, höchstens 2.048 Zeichen lang und ohne Benutzername oder Passwort darin.
  • Die Datei muss ein JPEG oder PNG sein (geprüft am Inhalt, nicht am Namen), höchstens 12 MB gross und innerhalb von 15 Sekunden herunterladbar. Bis zu 3 Weiterleitungen werden verfolgt.
  • Signierte Links aus deinem eigenen Speicher funktionieren, solange sie beim Eintreffen der Anfrage aus dem Internet erreichbar sind.
  • Schick entweder image oder image_url, nie beides.

Schritt 3: einen Ordner in eine CSV scannen

Speichere das neben scan.py als batch.py und starte python batch.py photos/ (oder python batch.py photos/ magic). Es schickt die Fotos nacheinander, wiederholt vorübergehende Fehler und schreibt eine Zeile pro erkannter Karte in cards.csv.

Python: batch.py

import csv
import pathlib
import sys
import time

import requests

from scan import detect  # the script from step 1, saved as scan.py

FIELDS = ["file", "name", "set", "number", "rarity", "language",
          "cardmarket_id", "trend_eur", "avg30_eur", "confidence"]


def error_code(response):
    try:
        return response.json().get("error")
    except ValueError:
        return None


def detect_with_retry(path, tcg, attempts=3):
    for attempt in range(attempts):
        last = attempt == attempts - 1
        try:
            return detect(path, tcg)
        except requests.Timeout:
            if last:
                raise
        except requests.HTTPError as err:
            status = err.response.status_code
            if error_code(err.response) == "demo_limit_reached":
                sys.exit("Demo limit reached for today. Use your own API key.")
            if status == 402:
                sys.exit("No card scans left on this key.")
            if status not in (429, 500, 502, 504) or last:
                raise
        time.sleep(5 * 2 ** attempt)  # wait 5 s, then 10 s


def main(folder, tcg="pokemon", out="cards.csv"):
    photos = sorted(p for p in pathlib.Path(folder).iterdir()
                    if p.suffix.lower() in (".jpg", ".jpeg", ".png"))
    with open(out, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        writer.writeheader()
        for path in photos:
            data = detect_with_retry(path, tcg)
            for card in data["cards"]:
                card_set = card.get("set") or {}
                cm = (card.get("prices") or {}).get("cardmarket") or {}
                writer.writerow({
                    "file": path.name,
                    "name": card.get("name"),
                    "set": card_set.get("name"),
                    "number": card_set.get("number"),
                    "rarity": card.get("rarity"),
                    "language": card.get("language"),
                    "cardmarket_id": card.get("cardmarket_id"),
                    "trend_eur": cm.get("trend"),
                    "avg30_eur": cm.get("avg30"),
                    "confidence": card.get("confidence"),
                })
            print(f"{path.name}: {len(data['cards'])} card(s)")


if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "pokemon")

Die Spalten sind ein Anfang. Ergänze reverse_holo und first_edition für Pokémon, set.code und edition für Yu-Gi-Oh! oder prices.cardmarket_foil für Lorcana und Magic. Die Felder pro Spiel stehen in der Dokumentation (auf Englisch).

Timeouts

Die API ist synchron: Die Karten kommen in der Antwort auf dieselbe Anfrage zurück, es gibt nichts abzufragen. Eine warme Anfrage dauert etwa 5 Sekunden. Die erste Anfrage nach einer ruhigen Phase (ein Kaltstart) dauert 10 bis 15 Sekunden. Der Server gibt nach etwa 90 Sekunden auf und antwortet mit 504, setz das Timeout im Client also auf 120 Sekunden und nicht tiefer. Schick die Fotos eines Ordners nacheinander oder wenige parallel, nicht Hunderte auf einmal.

Fehlerbehandlung

  • 400: Die Anfrage ist falsch, zum Beispiel kein Bild, kein JPEG oder PNG, über 12 MB, sowohl image als auch image_url oder ein unbekanntes tcg. Korrigier die Anfrage; wiederholen hilft nicht.
  • 401: Der Schlüssel fehlt oder ist falsch. Prüf den Header Authorization: Bearer.
  • 402: In deinem Plan sind keine Karten-Scans mehr übrig.
  • 422 (no_card_detected): Es wurde keine Karte gefunden. Das wird nicht berechnet. Zeig deinem Nutzer einen Hinweis zu Licht und Bildausschnitt. Mit dem Demo-Schlüssel zählt es trotzdem als eine der 10 täglichen Anfragen.
  • 429: entweder demo_limit_reached (die 10 Anfragen pro Tag des Demo-Schlüssels für dein Netzwerk sind aufgebraucht; der Zähler startet um Mitternacht UTC neu) oder rate_limited (zu viele Anfragen pro Minute mit deinem Schlüssel; warte und wiederhole mit wachsender Pause).
  • 500 oder 502: ein vorübergehender Fehler. Nach einer kurzen Pause wiederholen. Mit dem Demo-Schlüssel gibt ein Fehler auf unserer Seite den Versuch zurück.
  • 504 (detection_timeout): Die Erkennung hat zu lange gedauert, oft bei einem Kaltstart. Einmal wiederholen.

Fehlerantworten enthalten einen error-Code und eine message, logge also beides. Erfolgreiche Antworten enthalten eine request_id; gib sie an, wenn du dich beim Support meldest.

Schritt 4: das Ergebnis deinen Nutzern zeigen

  • Zeig die Konfidenz an und markiere Karten unter einer Schwelle, die du wählst, mit "bitte prüfen".
  • Biete alternatives als Dropdown an, damit ein falscher erster Treffer mit einem Klick korrigiert ist.
  • Ist ein Druck offen (zum Beispiel print_unconfirmed bei Magic oder rarity_unconfirmed bei Yu-Gi-Oh!), frag deinen Nutzer, statt zu raten. Die API lässt den Preis in diesem Fall absichtlich leer.
  • Zeig keine Zustandsnote an: Die API beurteilt den Zustand nicht, den setzt dein Nutzer.
  • Behalte den Schlüssel auf deinem Server. Bau ihn nie in eine Handy-App oder Browser-Code ein, wo er ausgelesen werden kann. Eine Handy-App lädt das Foto auf dein Backend, und dein Backend ruft die API auf.

Schritt 5: auf einen eigenen Schlüssel wechseln

Der Demo-Schlüssel ist zum Testen da. Für den produktiven Einsatz erstellst du einen eigenen Schlüssel in den Einstellungen unter API-Schlüssel. Schlüssel brauchen einen bezahlten Plan: Start-Up, der kleinste bezahlte Plan, kostet 19 pro Monat (15 pro Monat bei jährlicher Zahlung, derselbe Betrag in EUR, USD oder CHF) und enthält 1.000 Karten-Scans pro Monat, gemeinsam mit der NeoSatoshi-App. Pro erkannter Karte wird ein Karten-Scan verbraucht, Fotos ohne Karte kosten nichts, und jede Antwort mit deinem eigenen Schlüssel zeigt credits.remaining statt demo. Aktuelle Pläne: Preise. Setz den Schlüssel mit export NEOSATOSHI_API_KEY=..., und die Skripte oben nutzen ihn ohne Änderung.

Mehr Beispiele und das volle Schema

  • Dokumentation (auf Englisch) mit Live-Beispielen, Snippets für curl, JavaScript und Python, allen Feldern pro Spiel und einer Testbox.
  • OpenAPI-Spezifikation, um einen Client in deiner Sprache zu erzeugen.

Häufige Fragen

Kann ich die API direkt aus einer Handy-App aufrufen?

Technisch ginge das, aber dann könnte jeder den Schlüssel aus der App auslesen. Schick das Foto an dein eigenes Backend und ruf die API von dort auf.

Welche Programmiersprachen kann ich nutzen?

Jede Sprache, die einen HTTPS-POST mit JSON schicken kann. Die Dokumentation hat Beispiele für curl, JavaScript und Python, und aus der OpenAPI-Spezifikation lassen sich Clients für die meisten anderen erzeugen.

Was kostet ein Scan?

Ein Karten-Scan pro erkannter Karte aus dem Monatsguthaben deines Plans; ein Foto mit drei Pokémon-Karten braucht drei, ein Foto ohne Karte keinen. Unter Preise steht, wie viele Scans jeder Plan enthält.

Welche API sollte ich für meine App wählen?

Das hängt von deinen Spielen, Sprachen und deinem Marktplatz ab. Unser datierter Vergleich mit Ximilar, CardSight, Open-Source-Modellen und Bild-KI-Modellen: welche API zur Kartenerkennung 2026.

Verwandte Fragen

Welche API zur Kartenerkennung sollte ich 2026 nutzen?

Gibt es eine API, die Pokémon-Karten auf einem Foto erkennt?

Gibt es eine API zur Kartenerkennung für Magic, Lorcana, Yu-Gi-Oh! und One Piece?

Weiterlesen

Zur Dokumentation und zum Demo-Schlüssel

Alles aus dieser Anleitung mit allen Feldern pro Spiel, dazu eine Testbox für deine eigenen Fotos. 10 Gratis-Erkennungen pro Tag, ohne Anmeldung.

Kostenlos ausprobieren

Für die ersten 10 Erkennungen ist kein Account nötig.

NeoSatoshi

KI-Kartenerkennung für Verkäufer. Bulk-Listing Workflow. Spare jede Woche Stunden.

Anwendungsfälle

BlogFAQAnleitungenKostenlose ToolsListing-SoftwareeBay Listing-ToolShopify Listing-ToolCardmarket-WorkflowKartenscannerVerkaufsanalyseOne Piece Listing-SoftwareOne Piece eBay-ToolOne Piece KartenscannerLorcana Listing-SoftwareYu-Gi-Oh! Listing-SoftwareMagic Listing-Software

Entwickler

© 2026 NeoSatoshi

Die auf dieser Website dargestellten wörtlichen und grafischen Informationen über das Pokémon Sammelkartenspiel, einschließlich der Kartenbilder und des Textes, unterliegen dem Urheberrecht von The Pokémon Company (Pokémon), Nintendo, Game Freak, Creatures und/oder pokemontcg.io. Inhalte des One Piece Card Game sind Eigentum von Eiichiro Oda/Shueisha, Toei Animation und Bandai. Inhalte von Disney Lorcana sind Eigentum von Disney und Ravensburger. Inhalte von Yu-Gi-Oh! sind Eigentum von Studio Dice/Shueisha, TV Tokyo und Konami. Inhalte von Magic: The Gathering sind Eigentum von Wizards of the Coast. Diese Website wird von keinem dieser Rechteinhaber produziert, empfohlen oder unterstützt.

AGBDatenschutzKontakt
Loading...

Seite wird geladen...

Deine Privatsphäre

Wir verwenden essenzielle Cookies, um die Website zu betreiben. Mit deiner Einwilligung nutzen wir zusätzlich Analytics, um NeoSatoshi zu verbessern, und Marketing, um zu messen, welche unserer Anzeigen funktionieren. Dafür wird ein Skript von OpenAI geladen. Du kannst beides einzeln unter Einstellungen wählen.