Skip to content
Crypto-only, prepaid. Top up a balance and we draw from it monthly — nothing is ever charged automatically.
tcggraph

Card recognition

Point a camera at a card. Get the printing.

Not the card name — the printing. Which set, which number, which language, which finish, and what it is worth in both markets. Over a socket while the card is still under the lens, or as a single photo upload. Milliseconds either way, and you are never billed for a card we could not place.

38 ms
Median match, server-side
8
Games, every printing
$0.0025
Per scan at volume
$0
Charged for a miss

Live scanning

One connection, a whole intake session

The slow part of scanning a thousand cards has never been the matching. It is the thousand round trips: a connection opened, a TLS handshake paid for, a request queued and a spinner shown, once per card, while an operator waits with the next card in their hand.

So live mode is a socket. You open it once at the start of a session and push frames as fast as the camera produces them. Matches come back on the same connection, tagged with the frame they belong to, and land while the card is still under the lens. Frames that do not resolve are answered too, with the candidates and the reason, so your interface can say flatten it instead of freezing.

In practice that is the difference between four hundred cards an hour and eight hundred, with the same person doing the same work.

38 ms
p50
74 ms
p95
140 ms
p99

Measured server-side, from the frame landing to the match leaving, at 1080p single-card frames. Your uplink is on top of that, which is why the socket exists: it pays TLS and connection setup once instead of on every card.

wss://api.tcggraph.com/v1/scanidle
    0
    Frames
    0
    Matched
    0
    Unbilled

    A replayed session transcript using cards from the public demo catalog. Nothing is being inferred in your browser — it is here to show the shape and the cadence of what arrives on the socket, including the frame that does not resolve and is not billed.

    A whole scanner
    const socket = new WebSocket("wss://api.tcggraph.com/v1/scan", [
      "tcggraph.v1",
      `bearer.${process.env.TCGGRAPH_KEY}`,
    ]);
    
    socket.onopen = () =>
      socket.send(
        JSON.stringify({
          type: "config",
          games: ["pokemon", "magic-the-gathering"],
          minConfidence: 0.92,
          prices: ["cardmarket", "tcgplayer"],
        }),
      );
    
    socket.onmessage = (event) => {
      const frame = JSON.parse(event.data);
      if (frame.type === "match") addToInventory(frame.matches[0]);
      if (frame.type === "unresolved") showHint(frame.reason);
    };
    
    // Binary frames need no envelope. Push them straight off the camera.
    setInterval(async () => socket.send(await captureJpeg()), 100);

    Three ways in

    Stream it, post it, or send nine cards at once

    The same match object comes back from all of them, so a batch importer and a live scanner share one parser.

    Live scanning

    WebSocket

    Open one connection and send camera frames as fast as your device produces them. Matches come back on the same socket, tagged with the frame they belong to, so a card is identified while it is still under the lens and the operator never waits on a request. Frames that do not resolve are answered too — with the candidates and a reason — so your UI can say hold it flatter instead of freezing.

    Best for Intake desks, buylist kiosks, phone cameras, anything with a continuous view of cards moving past.

    Single photo

    HTTPS

    A plain multipart upload, a public URL, or base64 in a JSON body. One request, one response, no polling and no job ids to chase. The response is the same shape the socket emits, so code written against one works against the other, and a batch importer and a live scanner can share a single parser.

    Best for Photo uploads, server-side backfills, listing tools, anything where the image already exists.

    Many cards, one frame

    Either

    One image can contain many cards. Each is located, matched and returned with its own box and its own confidence, so a nine-pocket binder page becomes nine catalog records from one photo. You are billed per card matched rather than per image, and cards the model will not commit to are not billed at all.

    Best for Binder and box digitisation, showcase shelves, collection onboarding.

    The response

    A match is the whole card, priced, in one round trip

    Identification and pricing are one operation here. Everywhere else they are two metered calls, which means finding out what a scanned card is worth costs about double the sticker price.

    Single photo
    curl -X POST "https://api.tcggraph.com/v1/scan" \
      -H "Authorization: Bearer $TCGGRAPH_KEY" \
      -F image=@front.jpg \
      -F games=pokemon \
      -F minConfidence=0.92
    POST /v1/scan — a match
    {
      "requestId": "scan_01K5Z8P4XQJ7YB3M",
      "latencyMs": 41,
      "matches": [
        {
          "confidence": 0.9962,
          "billed": true,
          "box": {
            "x": 0.171,
            "y": 0.064,
            "w": 0.658,
            "h": 0.872
          },
          "printing": {
            "id": "pkm_sv3pt5_199",
            "language": "en",
            "finish": "holofoil",
            "edition": "unlimited",
            "collectorNumber": "199/165"
          },
          "card": {
            "id": "pkm_sv3pt5_199",
            "game": "pokemon",
            "name": "Charizard ex",
            "set": {
              "code": "sv3pt5",
              "name": "151"
            },
            "rarity": "Special Illustration Rare",
            "prices": [
              {
                "source": "tcgplayer",
                "region": "NA",
                "currency": "USD",
                "market": 370.48
              },
              {
                "source": "cardmarket",
                "region": "EU",
                "currency": "EUR",
                "market": 312.67,
                "trend": 312.67,
                "sellers": 86
              }
            ]
          },
          "alternatives": []
        }
      ],
      "scans": {
        "billed": 1,
        "remaining": 5842
      }
    }
    POST /v1/scan — an unresolved frame, billed 0
    {
      "requestId": "scan_01K5Z8P51N4WQD0T",
      "latencyMs": 29,
      "matches": [],
      "unresolved": [
        {
          "box": { "x": 0.19, "y": 0.07, "w": 0.63, "h": 0.86 },
          "reason": "glare across set symbol",
          "alternatives": [
            { "printingId": "pkm_sv3pt5_199", "confidence": 0.71 },
            { "printingId": "pkm_sv3pt5_223", "confidence": 0.68 }
          ]
        }
      ],
      "scans": { "billed": 0, "remaining": 5842 }
    }
    What you can rely on
    matches[].confidenceFloat
    0 to 1. Above your threshold the match is returned and billed; below it, the frame comes back as a candidate list and costs nothing.
    matches[].billedBoolean
    Whether this match consumed a scan. Sum it if you want to reconcile your own counter against ours.
    matches[].boxObject
    Normalised x, y, width and height of the card inside the frame, for drawing an overlay without a second pass.
    matches[].cardCard
    The whole catalog record — the identical object /v1/cards returns, including images, legalities and game-specific fields.
    matches[].card.prices[Price]
    Cardmarket in EUR and TCGplayer in USD, on the match, in the same response. No second request and no second charge.
    matches[].printingObject
    Which physical printing was matched: language, finish, edition and collector number, not just the card name.
    matches[].alternatives[Object]
    Ranked runners-up with their confidences. Useful for a confirm step, and the only thing returned when nothing clears the threshold.
    latencyMsInt
    Server-side time from frame received to match emitted. Excludes network transit, so you can tell our latency from your connection's.
    scansObject
    Billed count for this call and the balance left on the period.

    Why ours

    Six things that are different, and one that matters most

    The first one on this list is the reason people move. You should not pay for our uncertainty, or for your own bad lighting.

    A miss costs nothing

    You are billed for a card we will commit to, not for an image you sent. A blurred frame, a thumb over the set symbol, a card we genuinely cannot place — all free, and all answered with the candidates so your operator can fix the shot. Most providers charge on submission, which means you pay for your own bad lighting.

    The price is already in the response

    A match returns the full catalog record with Cardmarket in EUR and TCGplayer in USD attached. Elsewhere identification and pricing are separate metered operations, so the card you scanned costs you twice before you know what it is worth. Here it is one charge and one round trip.

    It is a socket, not a queue

    Cards are identified while they are still under the lens, on a connection that stays open across a whole intake session. No per-image handshake, no job id to poll, no spinner between cards. An operator working a stack never waits, which is the difference between four hundred cards an hour and eight hundred.

    Printings, not just card names

    Knowing a card is Charizard is worth very little. The match tells you which Charizard: which set, which collector number, which language, which finish, which edition. That is the field that decides whether the card is worth three dollars or three hundred, and it is the one most scanners quietly guess at.

    Scans have their own meter

    A scan never spends your data credits and a catalog lookup never spends your scans. A heavy intake morning cannot starve the repricer that runs at midnight, and you can read the two numbers separately when you are working out what your product actually costs to run.

    Same object as the rest of the API

    The card inside a match is byte-for-byte the object /v1/cards returns. Your existing parser, types and database columns already handle it, so adding a scanner is a new input to code you have written rather than a second integration to maintain.

    Scan pricing

    Priced per matched card, metered on its own

    Recognition does not draw on your data credits and your data plan does not draw on your scans, so a heavy intake morning cannot starve the job that reprices your stock at midnight.

    Recognition Starter

    A collection app, a single intake desk, a listing tool finding its feet.

    $29/mo

    6,000

    matched cards per month · $0.0048 each

    Overage
    $0.008 per scan
    Beats pay-as-you-go from
    2,417 scans
    Unmatched frames
    Never billed
    • Live socket and photo upload
    • All 8 games, every printing
    • Cardmarket EUR and TCGplayer USD on the match
    • Multi-card frames
    • 2 concurrent sockets
    • Misses are never billed
    Choose Starter

    Recognition Growth

    Popular

    A shop running intake most days, or an app with real users scanning.

    $89/mo

    25,000

    matched cards per month · $0.0036 each

    Overage
    $0.005 per scan
    Beats pay-as-you-go from
    7,417 scans
    Unmatched frames
    Never billed
    • Everything in Starter
    • 10 concurrent sockets
    • Condition hints on the match
    • Slab label reading
    • Session transcripts, 30 days
    • Webhook on every session close
    Choose Growth

    Recognition Scale

    Bulk operations, marketplaces, and anyone pointing more than one camera at cards.

    $249/mo

    100,000

    matched cards per month · $0.0025 each

    Overage
    $0.0035 per scan
    Beats pay-as-you-go from
    20,750 scans
    Unmatched frames
    Never billed
    • Everything in Growth
    • 50 concurrent sockets
    • Priority inference queue
    • Regional endpoint pinning
    • Private catalog matching
    • 99.95% uptime SLA
    Choose Scale

    Or scan without a plan

    $0.012per matched card

    No plan, no minimum, no monthly fee. Scans are drawn from the same prepaid balance your data plan sits on, at a flat rate, and stop when the balance runs out. Someone digitising one collection over a weekend should not have to sign up for a month, and at this rate the cheapest per-scan option elsewhere still costs about ten times more.

    • No monthly fee and no minimum spend
    • Drawn from the balance you already top up
    • Still not billed for a miss
    • Switch to a plan the moment it is cheaper — the calculator on this page tells you when
    Create an account

    Overage costs more per scan than the plan it extends, and it stops once it reaches your plan price — so the worst a month can cost is double. If you pay overage twice running, the next plan up is cheaper. You can set a hard stop instead if you would rather fail than spend.

    Work out your bill

    Tell it your volume and it will argue against itself

    Including the cases where no plan at all is the cheapest thing you can do. We would rather you paid us the right amount than signed up for the wrong tier and left.

    5,000

    Cheapest here

    $29.00

    Recognition Starter · $0.0058 per card

    250500,000
    • No plan

      5,000 scans at $0.012

      $60.00

    • Recognition StarterCheapest

      6,000 included, 1,000 spare

      $29.00

    • Recognition Growth

      25,000 included, 20,000 spare

      $89.00

    • Recognition Scale

      100,000 included, 95,000 spare

      $249

    Pay-as-you-go is a flat $0.012 per matched card with no monthly fee. Plan rows include overage where the volume runs past the allowance, capped at the plan price. We show whichever is actually cheapest, including when that is not a plan.

    The market

    What everyone else charges for the same scan

    Per-scan figures are each provider's plan price divided by the scans that plan includes, which is the number that lands on your invoice.

    ProviderEntryIncludedPer scanPricesBills missesLive stream
    TCGGraph$29 / mo6,000 scans$0.0048Both marketsNoWebSocket
    TCGGraph, no planNonePay as you go$0.012Both marketsNoWebSocket
    Ximilar€59 / mo100,000 credits≈ €0.0059No, +10 creditsYesNo
    NeoSatoshi$29 / mo2,500 scans≈ $0.0116Cardmarket onlyNoNo
    TCGAPIs£99 / mo1,000 scans≈ £0.099BundledNot statedNo
    CardGrader.AI$49 pack400 credits$0.1225Separate moduleYesNo

    Published list prices from each provider's own pricing page, checked in September 2026, converted to a per-scan figure by dividing the plan price by the scans it includes. Ximilar bills 10 credits to identify a trading card and 10 more to price it, so a scan that tells you what a card is worth is about €0.0118 there rather than €0.0059. Two providers charge whether or not the card is identified. None of them offer a persistent connection for continuous scanning.

    Who buys it

    What people actually point cameras at cards for

    Bulk intake

    Card shops, buylist desks, bulk buyers

    A camera over the counter and one operator feeding cards. Each card is priced and in inventory before the next one lands, so a shoebox becomes a stock list in an afternoon instead of a week. The socket is the whole point here: at four hundred cards an hour, a round trip per card is most of your day.

    Collection apps

    Consumer apps, deck trackers, portfolio tools

    Point the phone at a card and it is in the collection, with the right printing and today's price on both sides of the Atlantic. Onboarding a new user is the hardest part of a collection app, and typing set codes is where they quit.

    Listing automation

    Marketplace sellers, listing tools, repricers

    A photo becomes a draft listing: title, set, number, language, finish, and a market price to anchor against. Because the match carries the catalog id, the listing links to a real product rather than a free-text guess that nobody can search for.

    Binder digitisation

    Collectors, estate sales, insurers

    One photo of a nine-pocket page returns nine cards with boxes, so a binder is a spreadsheet in a few minutes. Billed per card matched, which makes it cheaper per card than photographing them one at a time.

    Trade and event tooling

    Tournament organisers, judges, trade nights

    Check a physical deck against a submitted list, or confirm the cards in a trade are the printings that were agreed. Legality comes back on the card object, so a format check does not need a second lookup.

    Grading prep

    Submission services, graders, consignment

    Identify the exact printing before a card goes in the queue, so the submission form is right the first time. On Growth and up the match carries condition hints and reads slab labels, which is enough to sort a raw pile from an already-graded one.

    FAQ

    Questions about scanning

    Turn a shoebox into a stock list

    Recognition Starter is $29 a month for six thousand matched cards, or $0.012 a card with no plan at all. Either way, the frames we cannot place cost you nothing.

    • No card on file
    • No annual lock-in
    • Misses never billed