mtgjson-sdk
Official Rust SDK for MTGJSON, built and maintained by the MTGJSON team.
A high-performance, DuckDB-backed Rust query client for MTGJSON.
Unlike traditional SDKs that rely on rate-limited REST APIs, mtgjson-sdk implements a local data warehouse architecture. It synchronizes optimized Parquet data from the MTGJSON CDN to your local machine, utilizing DuckDB to execute complex analytics, fuzzy searches, and booster simulations with sub-millisecond latency.
Key Features
- Vectorized Execution: Powered by DuckDB for high-speed OLAP queries on the full MTG dataset.
- Offline-First: Data is cached locally, allowing for full functionality without an active internet connection.
- Fuzzy Search: Built-in Jaro-Winkler similarity matching to handle typos and approximate name lookups.
- Data Science Integration: Optional Polars DataFrame output via the
polarscargo feature for zero-copy data transfer. - Fully Async: Thread-safe async wrapper (
AsyncMtgjsonSdk) usingtokio::task::spawn_blocking. - Booster Simulation: Accurate pack opening logic using official MTGJSON weights and sheet configurations.
Install
Or add to your Cargo.toml:
[]
= "0.1"
Optional features:
= { = "0.1", = ["async"] } # async support via tokio
= { = "0.1", = ["polars"] } # Polars DataFrame output
Quick Start
use MtgjsonSdk;
use HashMap;
Architecture
By using DuckDB, the SDK leverages columnar storage and vectorized execution, making it significantly faster than SQLite or standard JSON parsing for MTG's relational dataset.
- Synchronization: On first use, the SDK lazily downloads Parquet and JSON files from the MTGJSON CDN to a platform-specific cache directory (
~/.cache/mtgjson-sdkon Linux,~/Library/Caches/mtgjson-sdkon macOS,AppData/Local/mtgjson-sdkon Windows). - Virtual Schema: DuckDB views are registered on-demand. Accessing
sdk.cards()registers the card view; accessingsdk.prices()registers price data. You only pay the memory cost for the data you query. - Dynamic Adaptation: The SDK introspects Parquet metadata to automatically handle schema changes, plural-column array conversion, and format legality unpivoting.
- Materialization: Queries return
serde_json::Valueobjects for flexible consumption, or typed structs viaexecute_into<T>()for ergonomic deserialization.
Use Cases
Price Analytics
let sdk = builder.build?;
// Find the cheapest printing of a card by name
let cheapest = sdk.prices.cheapest_printing?;
// Aggregate statistics (min, max, avg) for a specific card
if let Some = cheapest
sdk.close;
Advanced Card Search
The search() method supports ~20 composable filters that can be combined freely:
use SearchCardsParams;
let sdk = builder.build?;
// Complex filters: Modern-legal red creatures with CMC <= 2
let aggro = sdk.cards.search?;
// Typo-tolerant fuzzy search (Jaro-Winkler similarity)
let results = sdk.cards.search?;
// Rules text search using regular expressions
let burn = sdk.cards.search?;
// Search by keyword ability across formats
let flyers = sdk.cards.search?;
// Find cards by foreign-language name
let blitz = sdk.cards.search?;
sdk.close;
| Field | Type | Description |
|---|---|---|
name |
Option<String> |
Name pattern (% = wildcard) |
fuzzy_name |
Option<String> |
Typo-tolerant Jaro-Winkler match |
localized_name |
Option<String> |
Foreign-language name search |
colors |
Option<Vec<String>> |
Cards containing these colors |
color_identity |
Option<Vec<String>> |
Color identity filter |
legal_in |
Option<String> |
Format legality |
rarity |
Option<String> |
Rarity filter |
mana_value |
Option<f64> |
Exact mana value |
mana_value_lte |
Option<f64> |
Mana value upper bound |
mana_value_gte |
Option<f64> |
Mana value lower bound |
text |
Option<String> |
Rules text substring |
text_regex |
Option<String> |
Rules text regex |
types |
Option<String> |
Type line search |
artist |
Option<String> |
Artist name |
keyword |
Option<String> |
Keyword ability |
is_promo |
Option<bool> |
Promo status |
availability |
Option<String> |
"paper" or "mtgo" |
language |
Option<String> |
Language filter |
layout |
Option<String> |
Card layout |
set_code |
Option<String> |
Set code |
set_type |
Option<String> |
Set type (joins sets table) |
power |
Option<String> |
Power filter |
toughness |
Option<String> |
Toughness filter |
limit / offset |
Option<usize> |
Pagination |
Collection & Cross-Reference
let sdk = builder.build?;
// Cross-reference by any external ID system
let cards = sdk.identifiers.find_by_scryfall_id?;
let cards = sdk.identifiers.find_by_tcgplayer_product_id?;
let cards = sdk.identifiers.find_by_mtgo_id?;
// Get all external identifiers for a card
let all_ids = sdk.identifiers.get_identifiers?;
// -> Scryfall, TCGPlayer, MTGO, Arena, Cardmarket, Card Kingdom, Cardsphere, ...
// TCGPlayer SKU variants (foil, etched, etc.)
let skus = sdk.skus.get?;
sdk.close;
Booster Simulation
let sdk = builder.build?;
// See available booster types for a set
let types = sdk.booster.available_types?; // ["draft", "collector", ...]
// Open a single draft pack using official set weights
let pack = sdk.booster.open_pack?;
for card in &pack
// Simulate opening a full box (36 packs)
let booster_box = sdk.booster.open_box?;
let total_cards: usize = booster_box.iter.map.sum;
println!;
sdk.close;
API Reference
Core Data
// Cards
sdk.cards.get_by_uuid // single card lookup
sdk.cards.get_by_uuids // batch lookup
sdk.cards.get_by_name// all printings of a name
sdk.cards.search // composable filters (see above)
sdk.cards.get_printings // all printings across sets
sdk.cards.get_atomic // oracle data (no printing info)
sdk.cards.find_by_scryfall_id // cross-reference shortcut
sdk.cards.random // random cards
sdk.cards.count // total (or filtered with kwargs)
// Tokens
sdk.tokens.get_by_uuid
sdk.tokens.get_by_name
sdk.tokens.search
sdk.tokens.for_set
sdk.tokens.count
// Sets
sdk.sets.get
sdk.sets.list
sdk.sets.search
sdk.sets.get_financial_summary
sdk.sets.count
Playability
// Legalities
sdk.legalities.formats_for_card // -> Result<Vec<Value>>
sdk.legalities.legal_in // all modern-legal cards
sdk.legalities.is_legal // -> Result<bool>
sdk.legalities.banned_in // also: restricted_in, suspended_in
// Decks & Sealed Products
sdk.decks.list
sdk.decks.search
sdk.decks.count
sdk.sealed.list
sdk.sealed.get
Market & Identifiers
// Prices
sdk.prices.get // full nested price data
sdk.prices.today // latest prices (all providers)
sdk.prices.history
sdk.prices.price_trend // min/max/avg statistics
sdk.prices.cheapest_printing
sdk.prices.most_expensive_printings
// Identifiers (supports all major external ID systems)
sdk.identifiers.find_by_scryfall_id
sdk.identifiers.find_by_tcgplayer_product_id
sdk.identifiers.find_by_mtgo_id
sdk.identifiers.find_by_mtg_arena_id
sdk.identifiers.find_by_multiverse_id
sdk.identifiers.find_by_mcm_id
sdk.identifiers.find_by_card_kingdom_id
sdk.identifiers.find_by // generic lookup
sdk.identifiers.get_identifiers // all IDs for a card
// SKUs
sdk.skus.get
sdk.skus.find_by_sku_id
sdk.skus.find_by_product_id
Booster & Enums
sdk.booster.available_types
sdk.booster.open_pack
sdk.booster.open_box
sdk.booster.sheet_contents
sdk.enums.keywords
sdk.enums.card_types
sdk.enums.enum_values
System
sdk.meta // version and build date
sdk.views // registered view names
sdk.refresh // check CDN for new data -> Result<bool>
sdk.sql // raw parameterized SQL
sdk.connection // &Connection for advanced usage
sdk.close // release resources (consumes self)
Performance and Memory
When querying large datasets (thousands of cards), use sql() for bulk analysis to avoid materializing large Vec<Value> collections. With the polars cargo feature, use sql_df() for zero-copy DataFrame handoff from DuckDB.
// Use raw SQL for bulk analysis
let rows = sdk.sql?;
Advanced Usage
Builder Pattern
use MtgjsonSdk;
use PathBuf;
use Duration;
let sdk = builder
.cache_dir
.offline
.timeout
.build?;
Error Handling
All SDK methods return Result<T, MtgjsonError>. Use Rust's ? operator for ergonomic error propagation:
use ;
// MtgjsonError variants:
// - MtgjsonError::DuckDb(_) -- DuckDB query errors
// - MtgjsonError::Http(_) -- network/download errors
// - MtgjsonError::Io(_) -- file system errors
// - MtgjsonError::Json(_) -- JSON parsing errors
// - MtgjsonError::NotFound(_) -- entity not found
// - MtgjsonError::InvalidArgument(_) -- invalid input
SqlBuilder
The SqlBuilder provides safe, parameterized query construction:
use SqlBuilder;
let = new
.select
.where_eq
.where_gte
.where_like
.where_in
.order_by
.limit
.build;
Additional builder methods: distinct(), join(), where_regex(), where_fuzzy(), where_or(), group_by(), having(), offset().
Raw DuckDB Access
For advanced queries, access the underlying DuckDB connection directly:
let sdk = builder.build?;
// Ensure views are loaded
let _ = sdk.cards.count?;
// Access raw DuckDB connection
let raw = sdk.connection.raw;
raw.execute_batch?;
// Query your custom table through the SDK
let rows = sdk.sql?;
Async Usage
Enable the async feature to use AsyncMtgjsonSdk, an async wrapper that dispatches all blocking SDK operations to a thread pool via tokio::task::spawn_blocking:
[]
= { = "0.1", = ["async"] }
use AsyncMtgjsonSdk;
async
Auto-Refresh for Long-Running Services
// In a scheduled task or health check:
if sdk.refresh?
Raw SQL
All user input goes through DuckDB parameter binding (? placeholders):
let sdk = builder.build?;
// Ensure views are registered before querying
let _ = sdk.cards.count?;
// Parameterized queries
let rows = sdk.sql?;
Examples
Deck REST API (examples/deck-api)
A complete REST API built with Axum that serves MTGJSON deck data. Demonstrates the AsyncMtgjsonSdk wrapper, CDN integration for individual deck files, and in-memory caching.
# On Windows:
# On Linux/macOS:
The server starts on http://localhost:3000 with the following endpoints:
| Endpoint | Description |
|---|---|
GET /api/meta |
MTGJSON dataset version and date |
GET /api/sets?set_type=expansion |
List sets, optionally filtered by type |
GET /api/sets/:code |
Get details for a single set |
GET /api/decks?set_code=40K&deck_type=Commander+Deck |
List decks, optionally filtered by set and/or type |
GET /api/decks/search?name=Necron |
Search decks by name substring |
GET /api/decks/:file_name |
Get full deck contents (mainBoard, sideBoard, commander, etc.) |
Quick test:
# List all Warhammer 40K commander decks
# Get the full card list for a deck
Development
# On Windows (uses prebuilt DuckDB binary):
# On Linux/macOS:
# Tests (120+ tests, no network required)
# Smoke test (downloads real data from CDN)
# Linting
License
MIT