📄 Docs ⚙️ Dashboard 💳 Pricing 🔑 Get API Key 🏠 Main Site ↗

Card Recognition (photo → card)

Upload a phone photo, get back the top-3 candidate cards with a confidence label and an image-quality score. Pipeline: OpenCV cascade → DINOv2-S 384-dim FAISS lookup over 80k+ vectors → Tesseract OCR (EN + CJK) → 5-signal weighted scoring → optional rotation-fallback.

Response shape

{
  "candidates": [
    { "card_id": 3782, "name": "Charizard", "set_name": "Base Set",
      "score": 0.92, "rotation_used": 0 },
    { "card_id": 3801, "name": "Charizard ex", "set_name": "FireRed & LeafGreen",
      "score": 0.71 }
  ],
  "confidence_label": "high",            // high | medium | low | conflict
  "detection_method": "hybrid_embedding",
  "capture_quality": {
    "overall_score":     0.78,
    "color_reliability": "high",
    "glare_severity":    "low",
    "verdict":           "likely_real",
    "fake_risk":         0.04
  },
  "recognition_hint":   null
}

Tier gating

Session-cookie users (web app, mobile app) are unlimited. API-key callers need Starter+. Daily caps mirror app/tiers.py:

curl

curl -X POST \
  -H "Authorization: Bearer $CSM_KEY" \
  -F "file=@charizard_front.jpg" \
  "https://collectorstashmarket.com/api/cards/recognize"

Python

import os, requests, sys

CSM = "https://collectorstashmarket.com"
HDR = {"Authorization": f"Bearer {os.environ['CSM_KEY']}"}

with open(sys.argv[1], "rb") as f:
    r = requests.post(f"{CSM}/api/cards/recognize", headers=HDR, files={"file": f}, timeout=30)

if r.status_code == 403:
    sys.exit("Recognition requires a Starter+ key.")
r.raise_for_status()
data = r.json()
top  = (data["candidates"] or [None])[0]
print(f"{top['name']} ({top['set_name']}) — {data['confidence_label']} ({top['score']:.0%})")

# Self-learn: confirm the correct candidate so it becomes a future-scan exemplar
if top and data["confidence_label"] in ("medium", "low"):
    requests.post(
        f"{CSM}/api/cards/recognize/confirm",
        headers=HDR,
        json={"scan_id": data["scan_id"], "card_id": top["card_id"], "correct": True},
    )

TypeScript — browser file-upload

async function recognise(file: File, apiKey: string) {
  const fd = new FormData();
  fd.append("file", file);
  const r = await fetch("https://collectorstashmarket.com/api/cards/recognize", {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}` },
    body: fd,
  });
  if (!r.ok) throw new Error(await r.text());
  return r.json() as Promise<{
    candidates: { card_id: number; name: string; set_name: string; score: number }[];
    confidence_label: "high" | "medium" | "low" | "conflict";
    capture_quality: { overall_score: number; verdict: string; fake_risk: number };
  }>;
}

PHP

<?php
$key  = getenv("CSM_KEY");
$ch   = curl_init("https://collectorstashmarket.com/api/cards/recognize");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER     => ["Authorization: Bearer $key"],
    CURLOPT_POST           => 1,
    CURLOPT_RETURNTRANSFER => 1,
    CURLOPT_POSTFIELDS     => ["file" => new CURLFile("photo.jpg")],
]);
$resp = json_decode(curl_exec($ch), true);
echo "Best match: " . $resp["candidates"][0]["name"] . PHP_EOL;
echo "Confidence: " . $resp["confidence_label"] . PHP_EOL;

Node — multipart upload via undici

import { fetch, FormData, fileFrom } from "undici";

const fd = new FormData();
fd.set("file", await fileFrom("./photo.jpg", "image/jpeg"));

const r = await fetch("https://collectorstashmarket.com/api/cards/recognize", {
  method:  "POST",
  headers: { Authorization: `Bearer ${process.env.CSM_KEY}` },
  body:    fd,
});
console.log(await r.json());

Graded slabs (CGC / PSA / BGS / SGC)

For graded slabs use the dedicated back-scan endpoint to also resolve the cert number:

curl -X POST \
  -H "Authorization: Bearer $CSM_KEY" \
  -F "file=@cgc_back.jpg" \
  "https://collectorstashmarket.com/api/cards/recognize/slab-back?card_id=3782&grader_hint=CGC"

Returns cert_info + a follow_up_actions array (verify-URL, 👍/👎 confirm, create-listing). Slow grader lookups (CGC behind Cloudflare) complete asynchronously — poll /api/recognition/scan/{scan_id} for the final status.

Related