Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/invvd/mtg-cheaper-deck/llms.txt

Use this file to discover all available pages before exploring further.

MTG Cheaper Deck works entirely from a local SQLite database rather than querying Scryfall’s search API at analysis time. The sync script sync_scryfall.py populates that database by downloading Scryfall’s pre-built Oracle Cards bulk export — one compressed file containing every unique card face, including prices, legalities, keywords, and oracle text. Fetching a single ~40 MB file once is orders of magnitude faster and friendlier to Scryfall’s infrastructure than issuing individual card lookups for an entire deck. Prices and legalities in the local database stay accurate as long as you re-run the sync regularly — daily is recommended.

Running the Sync

python sync_scryfall.py
The script prints download progress as a percentage while the bulk file is transferred, then streams through it and commits cards in batches, reporting a running count:
Consultando metadata de bulk-data...
jsonl_download_uri: https://data.scryfall.io/oracle-cards/oracle-cards-...jsonl.gz
Descargando Oracle Cards...
Descargando... 100%
38247 cartas sincronizadas...

Listo: 38247 cartas sincronizadas en 14.3s.

What It Does

1

Fetch the bulk-data metadata

The script calls https://api.scryfall.com/bulk-data/oracle-cards to retrieve the current metadata object for the Oracle Cards dataset. It reads the jsonl_download_uri field from the response to get the actual download URL for the current snapshot.
2

Download the compressed card file

The .jsonl.gz file (approximately 40 MB compressed) is downloaded in streaming 1 MB chunks to ./data/oracle-cards.jsonl.gz. A progress percentage is printed to the console as chunks arrive.
3

Stream and parse card objects

The gzip file is opened in text mode and read line by line. Each line is a complete JSON object representing one Oracle card. Streaming avoids loading the entire decompressed file into memory at once.
4

Upsert in batches of 1,000

Parsed card dicts are accumulated into a batch. When the batch reaches 1,000 entries, it is committed to the database with a single INSERT ... ON CONFLICT DO UPDATE statement targeting the oracle_id primary key. This keeps individual transactions small and allows the running count to update incrementally on the console.
5

Skip entries without an oracle_id

Any JSON object that lacks an oracle_id field is skipped and counted separately. If any entries were skipped, the final output prints a notice with the count. Cards without an oracle_id cannot be used as a stable deduplication key and would corrupt the upsert logic.

Data Extracted per Card

The card_to_dict() function maps each Scryfall JSON object to the columns of the local cards table:
ColumnSource fieldNotes
oracle_idoracle_idPrimary key; stable across printings
scryfall_ididThe specific printing Scryfall chose as representative
namename
color_identitycolor_identityJSON array, e.g. ["U", "B"]
color_identity_maskcomputedBitmask: W=1, U=2, B=4, R=8, G=16
type_linetype_lineFull type line including subtypes
cmccmcConverted mana cost as a float
keywordskeywordsJSON array of Scryfall keyword strings
oracle_textoracle_textFalls back to joined card faces (see below)
powerpowerFalls back to card_faces[0].power
toughnesstoughnessFalls back to card_faces[0].toughness
price_usdprices.usdCast to float; None if absent or non-numeric
legalitieslegalitiesFull legalities dict stored as JSON
edhrec_rankedhrec_rankInteger; absent cards stored as None
updated_atreleased_atDate string of the card’s original release
Double-faced card fallback: When a card has no top-level oracle_text (common for transform and modal double-faced cards), the text from all card_faces entries is joined with \n//\n to produce a single combined oracle text string. For power and toughness, the fallback reads from card_faces[0] only.

Scheduling a Daily Sync

Scryfall updates its bulk exports roughly once a day. Prices in particular can shift significantly day to day. To keep your local data current, add a crontab entry that runs the sync automatically:
crontab -e
0 6 * * * cd /path/to/mtg-cheaper-deck && .venv/bin/python sync_scryfall.py >> ./data/sync.log 2>&1
This example runs at 06:00 every day, appending all output (stdout and stderr) to ./data/sync.log for easy review.

Scryfall API Compliance

The sync script identifies itself to Scryfall using the User-Agent string MTGCheaperDeck/0.1 (https://github.com/; contacto de desarrollo personal), as recommended by Scryfall’s API guidelines. The tool never calls Scryfall’s per-card search endpoint — that endpoint is rate-limited to approximately 10 requests per second and is unnecessary when the full Oracle Cards bulk file is available. One bulk download covers all ~38,000 cards in a single request.

Re-Sync Is Safe

The upsert operation is fully idempotent. Re-running sync_scryfall.py at any time — even immediately after a previous run — updates every column for any oracle_id already present in the database and inserts rows for any new cards. No duplicate rows are created. The only observable difference between two identical runs is that prices and legalities reflect the latest Scryfall snapshot.
If Scryfall’s bulk-data metadata response does not contain a jsonl_download_uri field, the sync raises a RuntimeError with a message listing the keys that were actually present. Scryfall has occasionally renamed or restructured fields in this endpoint. If the sync suddenly stops working, check the raw response from https://api.scryfall.com/bulk-data/oracle-cards and update the key name in sync_scryfall.py accordingly.

Build docs developers (and LLMs) love