AGPL-3.0 · SELF-HOSTABLE · YOUR SCANS STAY PUT
Drop in a folder of card scans, or a .zip of them. Get back an
identified, priced inventory and a CSV your marketplace already accepts.
Free while it is this young. Your scans, inventory and exports are yours alone. No other account can see them.
Images straight from a scanner, one card per file, loose or in a
.zip. Filenames don't matter. Front of the card is enough.
Every image is encoded and searched against a catalogue of reference card images. Confident matches go straight through.
Everything else waits in a queue with its runners-up and their scores, so you decide the close calls.
eBay or TCGplayer: a CSV you upload yourself. Nothing here touches a marketplace on your behalf.
Any flatbed will do, and so will a phone on a tripod: the matcher wants a
readable front, not a studio. jpg, png,
tif and webp, up to
300 scans and 64 MB per upload.
Reprints share artwork. Image search gives you a name you can trust and a printing you cannot. The same illustration appears in set after set, separated on the card by a symbol a few pixels wide.
Both prices are real rows from the catalogue foilstack fetched, and the bars are the review queue's own. The two scores are illustrative. A scan of your card produces its own.
The same illustration, printed twice, separated by a small black symbol and the word "Shadowless", and by eleven and a half thousand dollars. A tool that shows you only the winner will eventually sell that card for $855. So auto-accept requires a high score, a clear margin over the runner-up, and, when the runner-up is the same card in another printing, prices close enough that being wrong doesn't matter.
Every one of these is a catalogue the install fetches for itself, on your machine, from the source plugins that ship with it. Ingest only the games you sell: each one is a separate command, and nothing is downloaded until you ask for it.
Missing yours? A source plugin is one Python file with one method that yields rows. The one that covers all 13 above is about two hundred lines, and it is the worked example.
A foil is priced as a foil. Where several printings all answer to
"foil", you name the one you hold and the price follows it. Until you
do, the dearest match is used and the row says guessed,
because an overpriced card sits unsold where an underpriced one is
already gone.
Prices sync daily and every move is recorded, so each card carries its own trend rather than a number with no past. Upstream keeps only today, so this is history that exists because you ran it.
Four copies of the same card is one row with a quantity, not four rows to scroll past. Open it to see every copy, its condition, what you paid and what it is worth now.
Mark a card sold and it leaves your stock totals while staying in the record with what it fetched, so the margin column means something.
Select what you want to list, pick a channel, and download the CSV. There is no API client behind any of these names, no OAuth token held and no key stored: nothing here can list, reprice or sell anything on your behalf, because nothing here can reach a marketplace at all.
Three containers and a Postgres volume. No telemetry, no upload of your images to anyone, and, running it yourself, no account to create: one implicit owner holds everything, and the login screen never appears.
git clone https://github.com/foilstack/foilstack.git
cd foilstack && cp .env.example .env
docker compose up -d # migrations run on start
docker compose exec web foilstack ingest --source tcgcsv --game pokemon
docker compose exec web foilstack embed
Source plugins fetch card data from upstream on your machine. This project redistributes no prices and no images. Adding a game or a sport means writing one plugin.
A marketplace exporter is a TOML column mapping, not code. Anyone can read one in ten seconds and be certain it does nothing else.