Fantasy Flight Games · 7,868 printings
SW: Unlimited card scanner API
Hyperspace and showcase variants, plus leaders that are printed on both sides.
- 7,868
- Matchable printings
- 5
- Languages
- 500 ms
- Median match, live socket
- $0
- Charged for a miss
The problem
Why SW: Unlimited is hard to identify
Star Wars: Unlimited was built with parallels from the first set. Hyperspace variants use alternate framing on cards that also exist in standard form, showcase treatments add a third, and foil runs of each add more. Hyperspace and showcase printings carry numbers of their own — Darth Vader is SOR 087, his hyperspace printing SOR 351 — and their framing differs, so they are matched as their own printings. A foil shares its base printing's picture and number, so a foil and its non-foil come back with the printing unresolved and both listed.
Leaders complicate the frame itself: they are double-sided, and which face is showing depends on how the card was placed under the camera. The index holds one face per leader, the one the card's catalog image shows, so scan leaders that side up; the other face comes back unresolved rather than as a guess.
wss://api.tcggraph.com/v1/scanidle- 0
- Frames
- 0
- Matched
- 0
- Unbilled
A replayed transcript of SW: Unlimited 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 SW: Unlimited would read them. The match returns all of them, so you never have to infer a printing from a card name.
| Field | Why it decides the price |
|---|---|
| Card number | Locates the printing within the set; hyperspace and showcase are numbered apart from the standard card. |
| Variant treatment | Standard, hyperspace and showcase differ in framing and in price. |
| Foiling | Foil runs of each variant are separate printings. |
| Card face | Leaders are double-sided and may present either side. |
| Language | English, French, German, Spanish and Italian — the five the game is printed in — each matched on its own image and linked to the English card. |
Where scanners fail
SW: Unlimited 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.
Foils share the picture
A foil is printed with the same art and number as its standard card, so nothing a camera sees reliably separates the two. They come back as candidates for the operator to choose between.
Double-sided leaders
A leader photographed on its back face is unrecognisable to anything keyed on the front. It is a common intake error because the two faces look like two different cards.
Integrate it
Scanning SW: Unlimited 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=star-wars-unlimited returns, including the SW: Unlimited-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 plansconst { 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: ["star-wars-unlimited"],
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());
}
};{
"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": "swu_ibh_52",
"game": "star-wars-unlimited",
"name": "Watch This",
"set": {
"code": "IBH",
"name": "In the Blast Shadow"
},
"rarity": "Special",
"prices": [
{
"source": "tcgplayer",
"region": "NA",
"currency": "USD",
"market": 19.02
},
{
"source": "cardmarket",
"region": "EU",
"currency": "EUR",
"market": 13.88,
"trend": 13.88,
"avg7": 14.14
}
]
},
"alternatives": []
}
],
"scans": {
"billed": 1,
"remaining": 5842
}
}FAQ
Scanning SW: Unlimited
Start with SW: Unlimited, 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 SW: Unlimited, Pokémon and Magic mixed together resolves card by card without you sorting it first.