Magic: The Gathering API
Documentation
REST reference for the Magic: The Gathering API: sets, cards, search and images, with prices from TCGPlayer and CardMarket. Same API key as the Pokémon and One Piece APIs.
| Base path | https://api.pokewallet.io/mtg |
|---|---|
| Sets | 514 Magic: The Gathering sets |
| Card IDs | mtg_ + hex for cards on TCGPlayer; 64 hex characters for CardMarket-only cards |
| Prices | TCGPlayer (USD) for every finish and CardMarket (EUR) for normal and foil, refreshed daily |
| Free on every plan | Sets, set cards, card details, search and images |
| Ultra plan or 7-day trial | Prices by set, set statistics, completion value, trending sets and top cards |
| API key | The same key as the Pokémon and One Piece APIs, with shared rate limits |
https://api.pokewallet.io/cards, /sets…, One Piece at /op/…, the other games at /mtg/…, /ygo/…, /lor/… and so on. Quick Start
Get started with PokéWallet API in 4 simple steps:
Create an Account
Sign up for free to start using the API. Upgrade anytime from your dashboard.
Sign Up NowGenerate an API Key
Create your first API key from the dashboard.
Make Your First Request
Start querying the API with your key.
curl -H "X-API-Key: pk_live_your_key_here" \
"https://api.pokewallet.io/mtg/sets"Join the Discord Community
Stay up to date with new features, ask for help, and connect with other developers.
Join DiscordAuthentication
All API requests (except /health) require authentication using an API key.
API Key Format
API keys follow this format:
- Production keys:
pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - Development keys:
pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Authentication Methods
You can authenticate using either the X-API-Key header or Authorization header:
curl -H "X-API-Key: pk_live_your_key_here" \
"https://api.pokewallet.io/search?q=pikachu"curl -H "Authorization: Bearer pk_live_your_key_here" \
"https://api.pokewallet.io/search?q=pikachu"Rate Limits
Rate limits vary depending on your subscription plan:
| Plan | Hourly Limit | Daily Limit | Price |
|---|---|---|---|
| Free | 100 requests | 1,000 requests | $0/month |
| Early Access ⭐ | 1,000 requests | 10,000 requests | No longer available |
| Coffee ☕ | 1,000+ requests | 10,000+ requests | Buy a coffee on Ko-fi |
| Pro | 5,000 requests | 50,000 requests | €20/month |
| Ultra | 7,000 requests | 70,000 requests | €29/month |
| Business | Custom | Custom | Contact us |
Early Access was our initial launch tier and is no longer available for new users.
Rate Limit Headers
Every API response includes rate limit information:
X-RateLimit-Limit-Hour: 100
X-RateLimit-Remaining-Hour: 95
X-RateLimit-Limit-Day: 1000
X-RateLimit-Remaining-Day: 823Rate Limit Exceeded (429)
When you exceed your rate limit, you'll receive a 429 Too Many Requests response:
{
"error": "Rate limit exceeded",
"message": "Hourly limit exceeded",
"limits": {
"hourly": {
"limit": 100,
"used": 100,
"remaining": 0
},
"daily": {
"limit": 1000,
"used": 523,
"remaining": 477
}
}
}Public Endpoints
/healthNo Auth RequiredCheck API status, database, cache, and storage health with response times.
Example Request:
curl "https://api.pokewallet.io/health"Response Example (Healthy):
{
"status": "healthy",
"timestamp": "2025-12-16T10:30:00.000Z",
"version": "1.1.0",
"checks": {
"database": {
"status": "healthy",
"responseTime": 45
},
"cache": {
"status": "healthy",
"responseTime": 3
},
"storage": {
"status": "healthy",
"responseTime": 12
}
},
"responseTime": 60,
"region": "FRA"
}Response Example (Unhealthy):
{
"status": "unhealthy",
"timestamp": "2025-12-16T10:30:00.000Z",
"version": "1.1.0",
"message": "Critical services unavailable - API may not function correctly",
"checks": {
"database": {
"status": "unhealthy",
"responseTime": null,
"message": "Connection failed"
},
"cache": {
"status": "healthy",
"responseTime": 3
},
"storage": {
"status": "healthy",
"responseTime": 12
}
},
"responseTime": 1050,
"region": "FRA"
}/No Auth RequiredGet API information, version, and available endpoints.
Example Request:
curl "https://api.pokewallet.io/"Response Example:
{
"name": "PokeWallet API",
"version": "1.1.0",
"status": "production",
"authentication": {
"required": true,
"method": "API Key",
"header": "X-API-Key",
"format": "pk_live_xxxxxxxxxxxxx"
},
"endpoints": {
"health": { "path": "/health", "auth_required": false },
"search": { "path": "/search?q=charizard&limit=20", "auth_required": true },
"cardDetail": { "path": "/cards/:id", "auth_required": true },
"sets": { "path": "/sets", "auth_required": true },
"setDetails": { "path": "/sets/:setCode?page=1&limit=50", "auth_required": true },
"images": { "path": "/images/:id?size=high", "auth_required": true }
},
"rate_limits": {
"per_hour": 100,
"per_day": 1000,
"plan": "free"
}
}/gamesNo Auth RequiredLists the games available under their own prefix (/mtg, /ygo, …) with their routes. Pokémon and One Piece are not in this list: they use /cards, /sets… and /op/….
Example Request:
curl "https://api.pokewallet.io/games"Response Example (first of ten games):
{
"success": true,
"data": [
{
"code": "mtg",
"name": "Magic: the Gathering",
"endpoints": {
"sets": "/mtg/sets",
"set": "/mtg/sets/:set",
"card": "/mtg/cards/:id",
"search": "/mtg/search?q=",
"image": "/mtg/images/:id"
}
}
]
}The Pro plan does not include them: Pro covers Pokémon and One Piece only. Ultra (€29/month) includes everything in Pro plus the paid endpoints of all 12 games. Free endpoints (sets, cards, search, images) work on every plan.
Compare plans →Magic: The Gathering
All Magic: The Gathering endpoints live under /mtg. Same API key, same rate limits and the same response format as the Pokémon endpoints.
Card IDs
| Format | Example | Used for |
|---|---|---|
mtg_ + hex | mtg_2c2bae815d1b5d823542… | Every card with a TCGPlayer product, even if it also has CardMarket data |
| 64 hex characters, no prefix | e26f12b8b1fabf19… | Cards that exist only on CardMarket |
mtg_… ID only works under /mtg/. Finishes of the same product (Normal, Foil, …) are one card with one ID; each finish has its own TCGPlayer price. If you request a card by its CardMarket ID and it is linked to a TCGPlayer product, the response uses the canonical mtg_… ID. Response Format
Same shape as the Pokémon API: card_info, images, tcgplayer.prices and cardmarket.prices as arrays, pagination, errors as { "error", "message" }. Differences:
| Field | Magic: The Gathering |
|---|---|
Game fields in card_info | Instead of Pokémon's hp, stage, attacks…, the game's own fields in snake_case (present when the card has them): sub_type, flavor_text, p, t, upc |
card_type, card_text | Filled from the game's card type and rules text. card_text may contain HTML (<strong>, <em>…) as provided by TCGPlayer. |
card_info.variants, card_info.set_id | Always present, on every endpoint |
product_type | "card", "sealed" or null when unknown — see Data Notes |
release_date | ISO format, e.g. "2025-03-07T00:00:00" (Pokémon uses "16th June, 2023") |
images.languages | ["en"], or [] when there is no image |
Magic: The Gathering Endpoints
/mtg/setsAll Magic: The Gathering sets.
Response Fields:
| Field | Type | Description |
|---|---|---|
name | string | Set name |
set_code | string | null | Short code. Not unique: several sets can share it. |
set_id | string | Unique set identifier. Negative for sets that exist only on CardMarket. |
card_count | integer | Cards in the set (sealed products excluded). Omitted, not 0, if temporarily unavailable. |
sealed_count | integer | Sealed products in the set (booster boxes, displays, decks). Omitted, not 0, if temporarily unavailable. |
language | string | null | Set language |
release_date | string | null | ISO date |
Example Request:
curl -H "X-API-Key: pk_live_your_key_here" \
"https://api.pokewallet.io/mtg/sets"Response Example (first two sets):
{
"success": true,
"data": [
{
"name": "10th Edition",
"set_code": "10E",
"set_id": "1",
"card_count": 389,
"sealed_count": 9,
"language": "eng",
"release_date": "2007-07-13T00:00:00"
},
{
"name": "2021 Lunar New Year",
"set_code": "pl21",
"set_id": "-42",
"card_count": 1,
"sealed_count": 0,
"language": "eng",
"release_date": null
}
],
"total": 514
}/mtg/sets/:setCards of one set, paginated. Prices are empty in set listings, as on the Pokémon API: use /mtg/cards/:id for prices.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
set | string | Required | set_id (recommended, always unique) or set_code (exact match, case-insensitive) |
page | number | Optional | Page number (default: 1) |
limit | number | Optional | Results per page (default: 50, max: 200) |
Example Request:
curl -H "X-API-Key: pk_live_your_key_here" \
"https://api.pokewallet.io/mtg/sets/24684?limit=1"Response Example:
{
"success": true,
"set": {
"name": "Reality Fracture",
"set_code": "FRA",
"set_id": "24684",
"total_cards": 782,
"language": "eng",
"release_date": "2026-10-02T00:00:00"
},
"cards": [
{
"id": "mtg_2c2bae815d1b5d8235423b0c89f43f5383afbff3adbe2b6a895216ea36ddb4f83d2fec580f087b6707d9",
"card_info": {
"name": "Emrakul, the Exigent Doom",
"clean_name": "Emrakul the Exigent Doom",
"set_name": "Reality Fracture",
"set_code": "FRA",
"set_id": "24684",
"card_number": "1",
"rarity": "M",
"card_type": null,
"product_type": "card",
"variants": [
"Normal",
"Foil"
],
"card_text": "When you cast this spell, untap all lands you control.<br>\r\nFlying, trample<br>\r\nWard—Sacrifice three permanents.<br>\r\n3, Exile this card from your hand: Target land gains \"T: Add CC\" until this card is cast from exile. You may cast this card for as long as it remains exiled.",
"p": "12",
"t": "12",
"sub_type": "Legendary Creature — Eldrazi"
},
"images": {
"languages": [
"en"
]
},
"tcgplayer": {
"prices": [],
"url": "https://www.tcgplayer.com/product/716715/magic-reality-fracture-emrakul-the-exigent-doom"
},
"cardmarket": {
"product_name": "Emrakul, the Exigent Doom",
"prices": [],
"product_url": "https://www.cardmarket.com/en/Magic/Products/Singles/Reality-Fracture/Emrakul-the-Exigent-Doom"
}
}
],
"pagination": {
"page": 1,
"limit": 1,
"total": 782,
"total_pages": 782
}
}Disambiguation Response:
When a set_code is shared by several sets, the API returns the candidates instead of a set (status 200, as on the Pokémon API). Call again with the set_id you need.
{
"disambiguation": true,
"message": "Multiple sets found for set_code 'MOC'. Use set_id to specify which one.",
"matches": [
{
"set_id": "22979",
"set_code": "MOC",
"name": "Commander: March of the Machine",
"language": "eng",
"release_date": "2023-04-21T00:00:00"
},
{
"set_id": "23109",
"set_code": "MOC",
"name": "Planechase: March of the Machine",
"language": "eng",
"release_date": "2023-04-21T00:00:00"
}
],
"total": 2
}/mtg/cards/:idFull card with prices. id is a mtg_… ID or a 64-character CardMarket ID.
Example Request:
curl -H "X-API-Key: pk_live_your_key_here" \
"https://api.pokewallet.io/mtg/cards/mtg_2c2bae815d1b5d8235423b0c89f43f5383afbff3adbe2b6a895216ea36ddb4f83d2fec580f087b6707d9"Response Example:
{
"id": "mtg_2c2bae815d1b5d8235423b0c89f43f5383afbff3adbe2b6a895216ea36ddb4f83d2fec580f087b6707d9",
"card_info": {
"name": "Emrakul, the Exigent Doom",
"clean_name": "Emrakul the Exigent Doom",
"set_name": "Reality Fracture",
"set_code": "FRA",
"set_id": "24684",
"card_number": "1",
"rarity": "M",
"card_type": null,
"product_type": "card",
"variants": [
"Normal",
"Foil"
],
"card_text": "When you cast this spell, untap all lands you control.<br>\r\nFlying, trample<br>\r\nWard—Sacrifice three permanents.<br>\r\n3, Exile this card from your hand: Target land gains \"T: Add CC\" until this card is cast from exile. You may cast this card for as long as it remains exiled.",
"p": "12",
"t": "12",
"sub_type": "Legendary Creature — Eldrazi"
},
"images": {
"languages": [
"en"
]
},
"tcgplayer": {
"prices": [
{
"low_price": 28.08,
"mid_price": 32.62,
"high_price": 58.92,
"market_price": 29.08,
"direct_low_price": 29.48,
"updated_at": "2026-09-30T20:37:36.812579+00:00",
"sub_type_name": "Normal"
},
{
"low_price": 33.01,
"mid_price": 42.3,
"high_price": 94.97,
"market_price": 39.85,
"direct_low_price": null,
"updated_at": "2026-09-30T20:37:36.812579+00:00",
"sub_type_name": "Foil"
}
],
"url": "https://www.tcgplayer.com/product/716715/magic-reality-fracture-emrakul-the-exigent-doom"
},
"cardmarket": {
"product_name": "Emrakul, the Exigent Doom",
"prices": [
{
"variant_type": "normal",
"avg": 34.79,
"low": 14.3,
"trend": 16.27,
"avg1": 29.45,
"avg7": 31.97,
"avg30": 31.97,
"updated_at": "2026-09-30T14:49:39.980453+00:00"
},
{
"variant_type": "foil",
"avg": 59.99,
"low": 19.5,
"trend": 18.78,
"avg1": 54.99,
"avg7": 58.32,
"avg30": 58.32,
"updated_at": "2026-09-30T14:49:39.980453+00:00"
}
],
"product_url": "https://www.cardmarket.com/en/Magic/Products/Singles/Reality-Fracture/Emrakul-the-Exigent-Doom"
}
}tcgplayer.prices per finish, identified by sub_type_name, and one entry in cardmarket.prices per finish, identified by variant_type (normal, foil). updated_at is when that price last changed, not when it was last checked. A CardMarket-only card has tcgplayer: null and empty variants. /mtg/searchSearch cards by name or card number. Results have the same shape as /mtg/cards/:id, prices included.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
q | string | Required | 2–100 characters. Card name (all words, any order) or card number |
page | number | Optional | Page number (default: 1) |
limit | number | Optional | Results per page (default: 20, max: 100) |
Example Request:
curl -H "X-API-Key: pk_live_your_key_here" \
"https://api.pokewallet.io/mtg/search?q=Emrakul&limit=2"Response Example (results omitted):
{
"query": "Emrakul",
"results": [
{
"id": "mtg_a226650648bfbc74a51c389551e67786144bc98133f32b8812acf122033bde69de7cce366aa417",
"card_info": {
"name": "Emrakul, the Aeons Torn",
"clean_name": "Emrakul the Aeons Torn",
"set_name": "Pro Tour Promos",
"set_code": "PTP",
"set_id": "93",
"card_number": "1",
"rarity": "P",
"card_type": null,
"product_type": "card",
"variants": [
"Foil"
],
"card_text": "Emrakul, the Aeons Torn can't be countered. \r\n<br>\r\nWhen you cast Emrakul, take an extra turn after this one.\r\n<br>\r\nFlying, protection from colored spells, Annihilator 6 \r\n<br>\r\nWhen Emrakul is put into a graveyard from anywhere, its owner shuffles his or her graveyard into his or her library.",
"p": "15",
"t": "15",
"sub_type": "Legendary Creature — Eldrazi"
},
"images": {
"languages": [
"en"
]
},
"tcgplayer": {
"prices": [
{
"low_price": 104.13,
"mid_price": 124.86,
"high_price": 231.5,
"market_price": 123.86,
"direct_low_price": null,
"updated_at": "2026-09-30T20:37:32.272128+00:00",
"sub_type_name": "Foil"
}
],
"url": "https://www.tcgplayer.com/product/128632/magic-pro-tour-promos-emrakul-the-aeons-torn"
},
"cardmarket": {
"product_name": "Emrakul, the Aeons Torn",
"prices": [
{
"variant_type": "normal",
"avg": null,
"low": 36.05,
"trend": null,
"avg1": null,
"avg7": null,
"avg30": null,
"updated_at": "2026-09-30T14:49:31.144302+00:00"
},
{
"variant_type": "foil",
"avg": 48.99,
"low": 36.05,
"trend": 47.18,
"avg1": 42.98,
"avg7": 46.05,
"avg30": 51.83,
"updated_at": "2026-09-30T14:49:31.144302+00:00"
}
],
"product_url": "https://www.cardmarket.com/en/Magic/Products/Singles/Pro-Tour-Promos/Emrakul-the-Aeons-Torn"
}
}
],
"pagination": {
"page": 1,
"limit": 2,
"total": 53,
"total_pages": 27
},
"metadata": {
"total_count": 53,
"tcg": 51,
"cardmarket": 47,
"tcg_only": 6,
"cardmarket_only": 2,
"both_sources": 45,
"truncated": false
}
}metadata.truncated: true means more cards match than the search returns: add words to narrow it down. Words match anywhere in the name; a query shorter than 3 characters matches names and card numbers that start with it (27 finds 27, 027/264, 0027). Quotes are ignored; *, % and _ are plain text, not wildcards. /mtg/images/:idThe card image, one resolution (X-Image-Size: unsized): the size parameter of the Pokémon image endpoint does not apply. Cached for 24 hours.
Example Request:
curl -H "X-API-Key: pk_live_your_key_here" \
"https://api.pokewallet.io/mtg/images/mtg_2c2bae815d1b5d8235423b0c89f43f5383afbff3adbe2b6a895216ea36ddb4f83d2fec580f087b6707d9" \
--output cardUltra endpoints
Available on Ultra and Business plans, or during the 7-day trial. The Pro plan covers Pokémon and One Piece only: with Pro these endpoints answer 403 with an upgrade_url. Ultra includes everything in Pro. Same parameters and response format as the Pokémon endpoints of the same name.
| Endpoint | What it returns |
|---|---|
GET /mtg/prices/:set | TCGPlayer and CardMarket prices for every card of a set, one row per finish. ?source=tcg|cm |
GET /mtg/sets/:set/statistics | Price statistics and rarity breakdown. Sealed products excluded. ?variant=normal|foil |
GET /mtg/sets/:set/completion-value | Cost of one copy of every card in the set. Sealed products excluded. |
GET /mtg/sets/trending | Sets whose CardMarket prices moved most: 7-day average against 30-day average, cards from 1 € up. ?period=7d ?limit= |
GET /mtg/analytics/top-cards | Most expensive cards, or fastest-growing on CardMarket (7-day average against 30-day average, cards from 1 € up; current_price is the 7-day average). ?metric=price|growth ?source=tcg|cm ?limit= |
/cards/:id/price-history. Current prices are in the card detail and in /mtg/prices/:set; metric=growth works with source=cm only, and trending supports period=7d only. GET /mtg/prices/:set (first rows)
{
"success": true,
"source": "both",
"set_code": "FRA",
"set_id": "24684",
"data": [
{
"id": "mtg_2c2bae815d1b5d8235423b0c89f43f5383afbff3adbe2b6a895216ea36ddb4f83d2fec580f087b6707d9",
"card_number": "1",
"name": "Emrakul, the Exigent Doom",
"product_type": "card",
"variant": "Normal",
"tcgplayer": {
"low_price": 28.08,
"mid_price": 32.62,
"high_price": 58.92,
"market_price": 29.08,
"direct_low_price": 29.48,
"updated_at": "2026-09-30T20:37:36.812579+00:00"
},
"cardmarket": {
"avg": 34.79,
"low": 14.3,
"trend": 16.27,
"avg1": 29.45,
"avg7": 31.97,
"avg30": 31.97,
"updated_at": "2026-09-30T14:49:39.980453+00:00"
}
},
{
"id": "mtg_2c2bae815d1b5d8235423b0c89f43f5383afbff3adbe2b6a895216ea36ddb4f83d2fec580f087b6707d9",
"card_number": "1",
"name": "Emrakul, the Exigent Doom",
"product_type": "card",
"variant": "Foil",
"tcgplayer": {
"low_price": 33.01,
"mid_price": 42.3,
"high_price": 94.97,
"market_price": 39.85,
"direct_low_price": null,
"updated_at": "2026-09-30T20:37:36.812579+00:00"
},
"cardmarket": {
"avg": 59.99,
"low": 19.5,
"trend": 18.78,
"avg1": 54.99,
"avg7": 58.32,
"avg30": 58.32,
"updated_at": "2026-09-30T14:49:39.980453+00:00"
}
},
{
"id": "mtg_e4c34bcb45c655b0a16dead271fb9b06485f40592248f6d84d707822665da561a1e9164f1de7c0ad0f5e61cde70601",
"card_number": "1 // 5",
"name": "Cadet // Jace Double-Sided Token",
"product_type": "card",
"variant": "Normal",
"tcgplayer": null,
"cardmarket": null
}
],
"total": 1226
}GET /mtg/sets/:set/statistics
{
"set_info": {
"name": "Reality Fracture",
"set_code": "FRA",
"set_id": "24684",
"total_cards": 767
},
"price_statistics": {
"tcgplayer": {
"avg_market_price": "17.01",
"min_price": 0.12,
"max_price": 880,
"most_expensive": {
"id": "mtg_ec52a22ab58717a79d0006db028c7112b5e228b08893577d810b8a171d3ec22b2a6ea80e47e2ba4462fa7e17",
"name": "Omnipresence (Borderless) (Facet Foil) (0459)",
"price": 880
}
},
"cardmarket": {
"avg_price": "12.32",
"min_price": 0.03,
"max_price": 452.25,
"most_expensive": {
"id": "mtg_dbc07746db9723d9ba428f50cb6da85211bd1e7658f51bd599bccdcfffb190b95cc00b858dd59296cb227408",
"name": "Hexhaven Invigorator (Borderless) (Facet Foil)",
"price": 452.25
},
"trend_direction": "stable"
}
},
"rarity_breakdown": {
"M": 90,
"T": 15,
"C": 71,
"R": 134,
"U": 124,
"L": 35
}
}GET /mtg/sets/:set/completion-value
{
"set_info": {
"name": "Reality Fracture",
"set_code": "FRA",
"set_id": "24684",
"total_cards": 767
},
"completion_cost": {
"tcgplayer": {
"currency": "USD",
"low_estimate": 18286.81,
"market_estimate": 14069.58,
"high_estimate": 31790.08
},
"cardmarket": {
"currency": "EUR",
"low_estimate": 11034.06,
"avg_estimate": 12255.16,
"trend_estimate": 652.2
},
"note": "One copy per card (base finish). TCGPlayer prices in USD, CardMarket prices in EUR. Cross-currency comparison not available."
},
"breakdown_by_rarity": {
"M": {
"count": 90,
"total_cost": 4775.17
},
"T": {
"count": 15,
"total_cost": 0
},
"C": {
"count": 71,
"total_cost": 30.63
},
"R": {
"count": 134,
"total_cost": 1681.23
},
"U": {
"count": 124,
"total_cost": 56.88
},
"L": {
"count": 35,
"total_cost": 19.74
}
}
}GET /mtg/analytics/top-cards
{
"metric": "price",
"source": "tcg",
"top_cards": [
{
"id": "mtg_b12d1c3adb12f714026300db18765591e37f7dd47cacf8cf89e5629aa6d55d188c39a0e63173fa43b8708aff",
"card_info": {
"name": "Smaug the Magnificent (Borderless)",
"clean_name": "Smaug the Magnificent Borderless",
"set_name": "The Hobbit",
"set_code": "HOB",
"set_id": "24683",
"card_number": "249",
"rarity": "M",
"card_type": null,
"product_type": "card",
"variants": [
"Foil"
],
"card_text": null,
"p": "4",
"t": "3",
"sub_type": "Legendary Creature — Dragon"
},
"current_price": 24500
},
{
"id": "mtg_26dcb8609b2fa021e26275d3499b7285040f5369a7f3a65e5b060a78ba625682b13416d8e411",
"card_info": {
"name": "Time Walk",
"clean_name": "Time Walk",
"set_name": "Unlimited Edition",
"set_code": "2ED",
"set_id": "115",
"card_number": null,
"rarity": "R",
"card_type": null,
"product_type": "card",
"variants": [
"Normal"
],
"card_text": "Take an extra turn after this one.",
"sub_type": "Sorcery"
},
"current_price": 5719.99
},
{
"id": "mtg_57bae78cb8333bb2eeae88837fdc4effdbb2484ca38e6809bac1b402b458ecb45a630b2de42c",
"card_info": {
"name": "Ancestral Recall",
"clean_name": "Ancestral Recall",
"set_name": "Unlimited Edition",
"set_code": "2ED",
"set_id": "115",
"card_number": null,
"rarity": "R",
"card_type": null,
"product_type": "card",
"variants": [
"Normal"
],
"card_text": "Draw 3 cards or force opponent to draw 3 cards.",
"sub_type": "Instant"
},
"current_price": 4999.95
}
]
}GET /mtg/analytics/top-cards?metric=growth&source=cm
{
"metric": "growth",
"source": "cm",
"top_cards": [
{
"id": "mtg_8520f6fbf81704e0fb46e01d98c7c7638da8abf6935f5c521b4087185b688a6c39fd22156233fb",
"card_info": {
"name": "Sandstorm Eidolon",
"clean_name": "Sandstorm Eidolon",
"set_name": "Dissension",
"set_code": "DIS",
"set_id": "28",
"card_number": "70",
"rarity": "C",
"card_type": null,
"product_type": "card",
"variants": [
"Normal",
"Foil"
],
"card_text": "R, Sacrifice Sandstorm Eidolon: Target creature can't block this turn.\r\n<br>\r\nWhenever you play a multicolored spell, you may return Sandstorm Eidolon from your graveyard to your hand.",
"p": "2",
"t": "2",
"sub_type": "Creature — Spirit"
},
"current_price": 14.43,
"change_7d": "+753.8%"
},
{
"id": "mtg_b59d5b2762759eb692e40e10c8eabe055a4ffc833efc2f5929d218f12611cb03054fdcfe0e2e",
"card_info": {
"name": "Aurora Eidolon",
"clean_name": "Aurora Eidolon",
"set_name": "Dissension",
"set_code": "DIS",
"set_id": "28",
"card_number": "1",
"rarity": "C",
"card_type": null,
"product_type": "card",
"variants": [
"Normal",
"Foil"
],
"card_text": "W, Sacrifice Aurora Eidolon: Prevent the next 3 damage that would be dealt to target creature or player this turn.\r\n<br>\r\nWhenever you play a multicolored spell, you may return Aurora Eidolon from your graveyard to your hand.",
"p": "2",
"t": "2",
"sub_type": "Creature — Spirit"
},
"current_price": 15.87,
"change_7d": "+479.2%"
},
{
"id": "aaf47baa7bfde1c553a099b9f510e8f716667f2e8314a889624547b3a902202d",
"card_info": {
"name": "Black Border Foil Filler Card",
"clean_name": "Black Border Foil Filler Card",
"set_name": "Filler Cards",
"set_code": "filler",
"set_id": "-35",
"card_number": null,
"rarity": null,
"card_type": null,
"product_type": "card",
"variants": [],
"card_text": null
},
"current_price": 49.48,
"change_7d": "+451.0%"
}
]
}GET /mtg/sets/trending
{
"period": "7d",
"trending_sets": [
{
"set_name": "WCD 2002: Brian Kibler",
"set_code": "wc02bk",
"set_id": "-24",
"card_count": 10,
"cardmarket": {
"price_change": "+112.1%",
"avg_price_current": 8.04,
"avg_price_previous": 3.79,
"top_movers": [
{
"name": "Plains",
"change": "+189.6%"
},
{
"name": "Plains",
"change": "+168.7%"
},
{
"name": "Forest",
"change": "+147.9%"
}
]
},
"tcgplayer": null
},
{
"set_name": "Alpha Edition",
"set_code": "LEA",
"set_id": "7",
"card_count": 295,
"cardmarket": {
"price_change": "+78.9%",
"avg_price_current": 930.31,
"avg_price_previous": 519.91,
"top_movers": [
{
"name": "Timetwister",
"change": "+191.4%"
},
{
"name": "Ancestral Recall",
"change": "+189.7%"
},
{
"name": "Mox Emerald",
"change": "+178.7%"
}
]
},
"tcgplayer": {
"status": "work_in_progress",
"message": "TCGPlayer price history not yet available"
}
}
]
}Data Notes
normal, foil); a finish CardMarket does not price is left out, so cardmarket.prices can be empty. updated_at is when that price last changed, not when it was last checked: a stable price keeps an older date. product_type. Cards and sealed products are told apart using TCGPlayer data, then CardTrader. Tokens, emblems and oversized cards count as "card". For the very few products whose type cannot be determined, product_type is null, not "card". set_id exist only on CardMarket: their cards have 64-character IDs and tcgplayer: null. cardmarket.product_url is absent for some cards; the card data is still valid. Sets with no cards are not listed: they appear as soon as cards are added. Error Handling
The API uses standard HTTP status codes to indicate success or failure:
Request successful
Invalid parameters or malformed request
Missing or invalid API key
Paid endpoint without the required plan or an active trial: Pro for Pokémon and One Piece, Ultra for the other games. The body includes redeem_url (trial not activated yet) or upgrade_url (trial expired).
Card, set or image does not exist
The API only accepts GET requests.
/sets/:setCode/image: the set code matches several sets. Retry with ?language= or the numeric set_id.
Rate limit exceeded
Something went wrong on our side
An image could not be fetched from its source. Temporary: retry later.
Errors on game endpoints (/mtg, /ygo, …)
| Status | error | When |
|---|---|---|
| 400 | Invalid or corrupted card ID | ID is neither <game>_… nor 64 hex, or belongs to another game |
| 400 | Query parameter required | q missing, shorter than 2 or longer than 100 characters |
| 400 | Search query rejected | q contains SQL-like text: rephrase it |
| 400 | Invalid or corrupted card ID + did_you_mean | The ID belongs to another game (e.g. a lor_… ID on /mtg/cards): did_you_mean has the right path |
| 404 | Set not found + suggestions | No set with that set_id/set_code. If you wrote the set name, suggestions lists the matching sets with their set_id |
| 404 | Card not found, Image not found | Card or image does not exist |
| 502 | Image not available | Image source temporarily unreachable |
| 503 | Search temporarily unavailable | The search took too long under load. Retry after the seconds in the Retry-After header, or use more specific words |
Error Response Format
Every error has error (short, stable) and message (what to fix). When the API can tell what you meant, did_you_mean contains the corrected path:
// GET /magic/sets
{
"error": "Endpoint not found",
"message": "Did you mean /mtg/sets?",
"did_you_mean": "/mtg/sets",
"docs": "https://pokewallet.io/api-docs"
}Authentication errors (401) also include an example request. A trailing slash is ignored: /sets/ and /sets are the same endpoint.
Best Practices
Use Environment Variables
Never hardcode API keys. Always use environment variables or secure secret management.
Handle Rate Limits Gracefully
Implement exponential backoff when you receive a 429 response. Check rate limit headers to avoid hitting limits.
Cache Results
Cache API responses when appropriate to reduce requests and improve performance.
Use Search Endpoint
Always use /search for the most complete and accurate data.
Monitor Your Usage
Check your dashboard regularly to track API usage and avoid unexpected rate limit hits.
Error Handling
Always implement proper error handling for network failures, rate limits, and invalid responses.
Need Help?
If you have questions or run into issues, we're here to help!