Skip to content
Prepaid, no subscription trap. Top up a balance and we draw the monthly fee from it.
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
    // Mint a single-use ticket, so no key reaches client code.
    const { ticket } = await fetch("https://api.tcggraph.com/v1/scan/tickets", {
      method: "POST",
      headers: { authorization: `Bearer ${process.env.TCGGRAPH_KEY}` },
    }).then((r) => r.json());
    
    const socket = new WebSocket(`wss://api.tcggraph.com/v1/scan?ticket=${ticket}`);
    
    socket.onopen = () =>
      socket.send(
        JSON.stringify({
          type: "config",
          games: ["pokemon", "magic-the-gathering"],
          minConfidence: 0.92,
        }),
      );
    
    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);

    Two ways in

    Stream it, or post it

    The same match object comes back from both, 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.

    One card per frame. A frame is a card. Photograph a binder page pocket by pocket, or crop the page yourself before sending — the cost is identical either way, because scans are counted per matched card rather than per request. Locating several cards in one image is coming; shipping it early would mean billing you for cuts through artwork.

    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_ex7_99",
            "language": "en",
            "finish": "holofoil",
            "edition": "unlimited",
            "collectorNumber": "99"
          },
          "card": {
            "id": "pkm_ex7_99",
            "game": "pokemon",
            "name": "Rocket's Mewtwo ex",
            "set": {
              "code": "ex7",
              "name": "Team Rocket Returns"
            },
            "rarity": "Rare Holo EX",
            "prices": [
              {
                "source": "cardmarket",
                "region": "EU",
                "currency": "EUR",
                "market": 5201.01,
                "trend": 5201.01,
                "avg7": 4398.42
              }
            ]
          },
          "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",
          "detail": "glare across the set symbol",
          "candidates": 2,
          "bestConfidence": 0.71
        }
      ],
      "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 is unresolved, which tells you where the card was and why it failed, 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[].printingResolvedBoolean
    True when the artwork belongs to exactly one printing, so the set, number and finish on the card record are the matched ones. False when reprints or finishes share the artwork: the card is identified, and which of its printings it is comes from alternatives.
    matches[].alternatives[Object]
    When the printing is unresolved, the other printings sharing the artwork — one of them is the card in front of you. When it is resolved, ranked runners-up for a confirm step. Carried on a billed match only: an unresolved frame reports how many candidates remained, not which.
    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 where the card was and what went wrong, so your operator can fix the shot rather than guess. 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, and an honest flag when it cannot be one

    Knowing a card is Charizard is worth very little — which Charizard decides whether it is worth three dollars or three hundred. So a match names the printing: set, collector number, language, finish, edition. Where that is impossible it says so instead of guessing. Around a quarter of the catalog reuses artwork across reprints and finishes, and for those the card comes back identified with the printing marked unresolved and every candidate attached. That is the field most scanners quietly guess at, and a wrong guess sits in your inventory at the wrong price until somebody notices.

    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
    • 6,000 matched cards a month
    • 600 a day, so a loop with a bug cannot spend the month
    • Live socket and photo upload, same matcher on both
    • All 8 games, every printing we hold
    • Cardmarket EUR and TCGplayer USD on the match
    • Misses and refusals 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
    • 25,000 matched cards a month
    • 2,500 a day
    • $0.0036 a scan — a quarter off Starter's rate
    • Overage at $0.005 rather than $0.008
    • Everything in Starter
    Choose Growth

    Recognition Scale

    Bulk operations, marketplaces, and apps whose users scan all day.

    $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
    • 100,000 matched cards a month
    • 10,000 a day
    • $0.0025 a scan — the lowest rate we publish
    • Overage at $0.0035
    • Everything in Growth
    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

    Point a phone at a binder and pull the pocket under it, card by card, and a shelf of binders becomes a spreadsheet in an afternoon. The socket is what makes that bearable: no handshake per card, so the pace is however fast somebody can turn a page.

    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