Skip to content
Prepaid, no subscription trap. Top up a balance and we draw the monthly fee from it.
tcggraph

Wizards of the Coast · 105,841 printings

Magic card scanner API

Thirty years of reprints means one card name maps to eighty different products.

105,841
Matchable printings
11
Languages
500 ms
Median match, live socket
$0
Charged for a miss

The problem

Why Magic is hard to identify

Magic's problem is scale in two directions at once. A staple like Lightning Bolt has been printed in dozens of sets, most with different artwork, and Secret Lair and Universes Beyond releases have added treatments that look nothing like the original. At the same time the reverse is true: the same artwork is reprinted across sets with only a set symbol and a copyright line to tell them apart, and some promos differ from the main-set printing by nothing but a stamp.

The modern frame treatments multiply it again. Borderless, extended art, showcase, retro frame and textless versions of one card in one set are separate products with separate prices, and foil and etched foil are separate again. A scanner keyed on artwork will confidently return the wrong one of five printings from the same booster.

The field that actually settles it is the collector number in the bottom-left, printed alongside the set code and the language letter. Frame treatments carry their own collector numbers, so reading that line settles the printing, treatment included. Foil and etched foil are finishes of that printing: they come back as its printings, each priced, for you to choose, because the photo is not used to tell them apart. Where the line cannot be read or the card prints none, reprints that kept the art come back identified with the printing unresolved and the candidates attached.

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

    A replayed transcript of Magic cards from the public demo catalog, including a frame that does not resolve and is not billed. Nothing is being inferred in your browser.

    What settles it

    The fields a match is decided on

    Read in the order a person grading Magic would read them. The match returns all of them, so you never have to infer a printing from a card name.

    FieldWhy it decides the price
    Collector number and set codeSince 2018 the bottom-left line is a unique product identifier.
    Frame treatmentBorderless, extended, showcase and retro are separate printings at separate prices.
    FoilingNon-foil, foil and etched foil diverge by multiples on the same card. Returned as the printing's finishes, each priced; not read from the photo.
    Copyright lineWhat a person reads on older printings with no collector number. The scanner does not read it, so those reprints come back unresolved with the candidates listed.
    LanguageEleven printed languages, every one of them held and matched; Japanese alternate-art printings carry their own premium.

    Where scanners fail

    Magic traps that return the wrong printing

    Each of these is a case where the artwork is right and the answer is still wrong. A confident wrong match costs more than a refused one, which is why an unresolved frame is free.

    Reprints that share art

    Core-set reprints often reuse the illustration exactly. The set symbol and the copyright year are the only visible difference, and the price gap between a 1997 and a 2021 printing of the same card can be two orders of magnitude.

    Five printings, one booster

    A mythic from a recent set can exist as regular, foil, extended art, borderless and showcase, all pulled from the same box. Matching on the illustration alone cannot distinguish four of the five.

    Old-border edge cases

    Alpha is separated from Beta by corner radius and from Unlimited by border colour. These are the most valuable cards in the game and the distinction is geometric rather than textual. The scanner measures neither, so these come back identified with the printing unresolved and all three as candidates.

    Integrate it

    Scanning Magic in about fifteen lines

    Pin the socket to one game and matching gets both faster and more certain, because there are fewer near-identical candidates to rule out.

    The card inside a match is the same object /v1/cards?game=magic-the-gathering returns, including the Magic-specific fields. If you already read this API, a scanner is a new input to code you have written rather than a second integration.

    Scanning is metered separately from data, so pointing a camera at a stack all morning cannot spend the credits your repricer needs at midnight.

    Scan pricing

    $29/mo · 6,000 matched cards

    Or $0.012 per matched card with no plan at all. Down to $0.0025 at volume, and a frame we cannot place is never billed.

    Compare scan plans
    Live Magic scanning
    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: ["magic-the-gathering"],
          minConfidence: 0.92,
        }),
      );
      // A JPEG straight off the camera, 1280 px across at quality 70.
      socket.send(camera.latestJpeg());
    };
    
    socket.onmessage = (event) => {
      const frame = JSON.parse(event.data);
    
      for (const match of frame.matches ?? []) {
        // printingResolved: set, number and language are settled. finishResolved:
        // the record has one finish. Either false is a choice for the operator.
        intake.add(match.card, match.printingResolved && match.finishResolved, match.alternatives);
      }
    
      // Unresolved frames arrive too, and cost nothing.
      for (const miss of frame.unresolved ?? []) showHint(miss.reason);
    
      // Every frame is answered. One in flight at a time: the next goes now.
      if (frame.type === "match" || frame.type === "unresolved" || frame.type === "error") {
        socket.send(camera.latestJpeg());
      }
    };
    A match, with both price feeds
    {
      "requestId": "scan_01K5Z8P4XQJ7YB3M",
      "latencyMs": 41,
      "matches": [
        {
          "confidence": 0.9962,
          "billed": true,
          "box": {
            "x": 0.171,
            "y": 0.064,
            "w": 0.658,
            "h": 0.872
          },
          "corners": [
            {
              "x": 0.183,
              "y": 0.064
            },
            {
              "x": 0.829,
              "y": 0.071
            },
            {
              "x": 0.817,
              "y": 0.936
            },
            {
              "x": 0.171,
              "y": 0.928
            }
          ],
          "distance": 21,
          "printingResolved": true,
          "finishResolved": true,
          "card": {
            "id": "mtg_b0faa7f2-b54",
            "game": "magic-the-gathering",
            "name": "Black Lotus",
            "set": {
              "code": "LEA",
              "name": "Limited Edition Alpha"
            },
            "rarity": "rare",
            "prices": [
              {
                "source": "cardmarket",
                "region": "EU",
                "currency": "EUR",
                "market": 33768.47,
                "trend": 33768.47,
                "avg7": 24192.86
              }
            ]
          },
          "alternatives": []
        }
      ],
      "scans": {
        "billed": 1,
        "remaining": 5842
      }
    }

    FAQ

    Scanning Magic

    Start with Magic, add the rest for nothing

    One key covers every game we carry, and scans are pooled across all of them. There is no per-game surcharge and no separate model to enable — a stack with Magic, Pokémon and One Piece mixed together resolves card by card without you sorting it first.

    Other games