Skip to main content

Foilstack

Open-source card scanning, inventory and listing export. Drop in a .zip of card scans, get back an identified, priced inventory and a CSV your marketplace will accept.

Runs on your own machine. Your scans never leave the host.

Status: early. The matching pipeline works end to end; expect rough edges.

A scan of the whole workflow: reviewing matched cards, confirming one into inventory, opening a card to see its price trend, and exporting the selection as a marketplace CSV

Scans in, priced CSV out. Your scan on the left, the catalogue's guess beside it, and the score between them — because the top match is evidence, not an answer.

What it does

  1. Import — a .zip of card images, one image per card, any filenames.
  2. Match — every image is encoded and searched against a catalogue of reference card images. High-confidence matches are accepted automatically; everything else waits in a review queue with its runners-up.
  3. Inventory — condition, quantity, cost, market price, suggested list price, margin.
  4. Export — a CSV for TCGplayer or eBay, which you upload yourself.

Why the review queue matters

Reprints share artwork. A vector search over card images gives you a name you can trust and a printing you cannot — the same illustration may appear in a dozen sets at wildly different prices, separated on the physical card only by a set symbol a few pixels wide.

So the top match is shown next to its rivals with their scores, rather than presented as an answer. The auto-accept threshold defaults to 0.92 and is deliberately conservative: a missed auto-accept costs you one click, a wrong one puts a real card on sale at another card's price.

When the catalogue is wrong about all of its guesses, search it by name and pick the right card yourself — from the queue before committing, or from the inventory row afterwards. You are holding the card and can read the name off it, which is information the encoder never had.

Quick start

git clone https://github.com/foilstack/foilstack.git
cd foilstack
cp .env.example .env
docker compose up -d

That starts Postgres, the encoder and the web app. Migrations run automatically on every start, so an upgrade is git pull && docker compose up -d --build and nothing else.

The default encoder is a gated model. Accept the terms for DINOv3 and put an HF_TOKEN in your .env before the first run, or the encoder fails to load with a 401 that looks like a network problem.

Then build a catalogue. Start small — one game, a few hundred cards — to check the whole path works before committing hours to encoding:

docker compose exec web foilstack ingest --source tcgcsv --game pokemon --limit 300
docker compose exec web foilstack embed

ingest pulls catalogue rows and image URLs; embed downloads each reference image and encodes it. Two commands because they fail for different reasons and take very different amounts of time — a network blip during encoding should not cost you the ingest.

Ingest every game you intend to scan. Nearest-neighbour search can only answer with a card that is in the catalogue, so scanning Magic against a Pokemon-only catalogue returns Pokemon. foilstack plugins lists the games a source can fetch.

Open http://localhost:8090.

Running it for other people

There is no login screen by default: one implicit owner holds everything, and you never invent a password for a tool only you can reach. Set FOILSTACK_MULTI_USER=true and a real secret key and every scan, job and inventory row belongs to exactly one account — enforced by a NOT NULL column and a test suite that drives the real app and asserts a stranger gets nothing.

docs/accounts.md — registration control, invite codes, storage quotas, sign-in rate limiting.

Backups

A service, not a cron job you have to remember. The backup sidecar starts with everything else, dumps the database on a schedule, verifies what it wrote, mirrors your scans beside it, and drops a BACKUP_FAILING file when a run produces nothing usable. Checking for that file is the whole of your monitoring.

docs/backups.md — retention, copying off the machine, restoring.

Prices

Prices sync daily from TCGCSV and are stored per printing, so a foil is priced as a foil — which matters more than it sounds. Base Set Charizard is 1st Edition Holofoil at $10,000, Unlimited Holofoil at $2,146 and Holofoil at $855; ticking "foil" chooses between none of them.

Price history is the one thing here that cannot be rebuilt: upstream mirrors only the current day, so a day the sync does not run is a day of history gone for good.

docs/prices.md — the sync protocol, how history is stored, naming a printing.

Extending it

Source plugins fetch the catalogue and run code, so you add one deliberately and there is no registry. Export plugins are TOML column mappings — adding a marketplace means writing a file a reviewer can read in ten seconds and be certain does nothing else.

docs/plugins.md

Encoding the catalogue

The encoder runs on CPU by default so the stack starts anywhere — about one card a second, which is more than a day for a full Magic catalogue. If you have an NVIDIA GPU, an overlay turns it on:

docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d --build

docs/encoder.md — picking the right CUDA wheels, checking which device it actually loaded on, and filling the catalogue.

This project redistributes no card data

The catalogue is fetched from upstream, on your machine, by a plugin you chose. We ship the code that knows how to ask — not the prices, and not the images.

The one exception is the animation above, which shows a handful of card thumbnails because a demo of a card tool with no cards in it would be useless. No card data ships in any form the application reads.

Contributing

AGENTS.md has the working notes: how the pieces fit, the conventions, and the handful of mistakes this codebase has already made once.

uv sync --extra dev
uv run pre-commit install --hook-type pre-commit --hook-type pre-push
uv run pytest -q

Most of the suite needs nothing but Python. tests/test_isolation.py builds a throwaway Postgres database and drives the real app against it — it skips cleanly if there is no server, so start one before trusting a green run on anything touching accounts:

docker compose up -d postgres && uv run pytest -q

The pre-push hook runs the suite and refuses a push if anything skipped, for the same reason.

Supporting this

foilstack is free, AGPL, and costs you nothing to run. If it saves you an afternoon of typing card names into a spreadsheet:

Buy me a coffee

The link is in the sidebar of every install, self-hosted ones included.

Licence

AGPL-3.0. The bundled JetBrains Mono subsets under src/foilstack/web/static/fonts/ are licensed separately, under the SIL Open Font License 1.1 — see OFL.txt beside them.

The network clause is the point: if you host this as a service for other people, those people are entitled to the source, including your changes.

Running it for yourself or inside your own business triggers nothing. A shop using this in the back office owes no one anything. The obligation begins when you offer it as a service to others.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

foilstack-0.2.4.tar.gz (8.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

foilstack-0.2.4-py3-none-any.whl (8.5 MB view details)

Uploaded Python 3

File details

Details for the file foilstack-0.2.4.tar.gz.

File metadata

  • Download URL: foilstack-0.2.4.tar.gz
  • Upload date:
  • Size: 8.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for foilstack-0.2.4.tar.gz
Algorithm Hash digest
SHA256 696fb73682887b28b0e5690cd8613d5b28ec165bf3a0dd68797cd584f835f4a7
MD5 90fcb2130ab4272d667bba1a16f8f079
BLAKE2b-256 b77319e0eb629fe41b0b55792d8025b41d3f1060f9c9286bd186c1dc58497aef

See more details on using hashes here.

Provenance

The following attestation bundles were made for foilstack-0.2.4.tar.gz:

Publisher: release.yml on foilstack/foilstack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file foilstack-0.2.4-py3-none-any.whl.

File metadata

  • Download URL: foilstack-0.2.4-py3-none-any.whl
  • Upload date:
  • Size: 8.5 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for foilstack-0.2.4-py3-none-any.whl
Algorithm Hash digest
SHA256 0e960888f4524df8f9cf98433b759aa71a83b627ef45e866e950fac9b67a8bb2
MD5 3bdf3c491389faa275236822e8962828
BLAKE2b-256 0754e7ca830337034ee39d3a1084af5f2510544dc1afad8103c12292454b26ab

See more details on using hashes here.

Provenance

The following attestation bundles were made for foilstack-0.2.4-py3-none-any.whl:

Publisher: release.yml on foilstack/foilstack

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.2.44

2 files

0.2.34

2 files

0.2.28

2 files

0.2.18

2 files

0.2.14

2 files

0.2.8

2 files

0.2.5

2 files

This release

0.2.4 This release

2 files

0.2.1

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page