tcgapi
Official Python SDK for tcgapi.dev — a unified pricing API for 89+ trading card games, including:
- Pokémon TCG (English + Japanese)
- Magic: The Gathering
- Yu-Gi-Oh!
- Lorcana
- One Piece Card Game
- Flesh and Blood
- Star Wars Unlimited
- Digimon, Dragon Ball Super, Riftbound, Union Arena, Final Fantasy TCG, Weiss Schwarz, Cardfight!! Vanguard, and dozens more.
Real-time market prices, full price history, fuzzy search, bulk lookups, and exports — all from one HTTP API. Sync and async clients, fully typed with Pydantic.
Install
pip install tcgapi
Requires Python 3.9+.
Quickstart
Get a free API key at tcgapi.dev/dashboard (100 requests/day, no credit card).
from tcgapi import TCGApi
tcg = TCGApi(api_key="tcg_live_...") # or set TCGAPI_KEY env var
# Look up a single card
card = tcg.cards.get(123456)
print(card.data.name)
# Get every printing's current price
prices = tcg.cards.prices(123456)
for p in prices.data:
print(f"{p.printing}: ${p.market_price}")
If api_key is omitted, the client reads from TCGAPI_KEY.
Examples
Search across every game
results = tcg.search.cards(
"charizard",
game="pokemon",
sort="price_desc",
per_page=20,
)
for card in results.data:
print(card.name, card.set_name, card.market_price)
Iterate without pagination boilerplate
for card in tcg.search.iter("lightning", game="magic"):
# walks meta.has_more automatically — caps at the API's 200/page max
...
Browse sets
games = tcg.games.list()
pokemon_sets = tcg.games.sets("pokemon")
surging_sparks = next(
(s for s in pokemon_sets.data if "Surging Sparks" in s.name), None
)
if surging_sparks:
cards = tcg.sets.cards(surging_sparks.id, sort="price_desc")
print(f"{surging_sparks.name}: {cards.meta.total} cards")
Bulk price lookup (Pro+)
# Auto-chunks if you pass more than 500 IDs.
bulk = tcg.bulk.prices([1, 2, 3, ...]) # thousands ok
print(f"Got prices for {len(bulk.data)} card-printings")
Per-condition prices (Pro+)
# Price floors per (printing, condition) — Near Mint, Lightly Played, etc.
conditions = tcg.cards.conditions(13217, printing="Holofoil")
for row in conditions.data:
# median_with_shipping is robust to $0.01 junk listings — prefer it when pricing inventory
print(f"{row.printing} / {row.condition}: ${row.median_with_shipping}")
print(conditions.meta.cached, conditions.meta.condition_counts)
# Bulk variant — up to 500 cards per call, served from the nightly cache.
# Cards without condition data yet are absent; fetch them once via
# tcg.cards.conditions() to warm them.
bulk_conditions = tcg.bulk.conditions([13217, 13218, 13219])
Top movers
movers = tcg.prices.top_movers(
game="pokemon",
direction="up",
period="7d",
limit=10,
)
for m in movers.data:
print(f"{m.name} ({m.set_name}): +{m.price_change}% — ${m.market_price}")
How old is a market price?
Every price row carries market_price_as_of: the UTC date TCGPlayer last reported
that printing's market price. It is older than last_updated_at when a printing
has stopped selling — the last price is kept and dated rather than dropped.
prices = tcg.cards.prices(123456)
for p in prices.data:
print(p.printing, p.market_price, "as of", p.market_price_as_of)
Price history (Hobby+)
# Window scales with your tier: free=7d, hobby=30d, starter=90d, pro/business=full.
history = tcg.cards.history(123456, range="year")
for point in history.data:
print(point.date, point.market_price)
Async client
The async API mirrors the sync surface verbatim:
import asyncio
from tcgapi import AsyncTCGApi
async def main():
async with AsyncTCGApi() as tcg:
resp = await tcg.search.cards("charizard", game="pokemon")
async for card in tcg.search.iter("dragon", game="magic"):
print(card.name)
asyncio.run(main())
Identify a card from a photo
identify.photo() reads one photo of a card, slab or sealed product and returns ranked
catalog candidates. It uses one scan per photo, separate from your daily request
limit: every account gets 25 free scans, then prepaid packs from the dashboard.
from tcgapi import TCGApi
with TCGApi() as tcg:
result = tcg.identify.photo("card.jpg", include_prices=True)
if result.data.status == "matched":
top = result.data.candidates[0]
print(top.name, top.set.name, top.prices)
elif result.data.status == "retake":
print("Retake:", result.data.message)
print("scans left:", result.meta.scans_remaining)
Accepts raw bytes, a path, or an open binary file. A 402 with code
SCAN_BALANCE_EMPTY means the balance is used up. AsyncTCGApi has the same method.
identify.usage() returns the balance, scans per day for the last 30 days by outcome and
the trial and pack purchases. It is free: no scan, no daily quota.
usage = tcg.identify.usage().data
print(usage.balance, usage.days[-1].scans, usage.days[-1].matched)
Rate limits
Every successful response carries the live rate-limit budget:
resp = tcg.games.list()
print(resp.rate_limit)
# RateLimit(daily_limit=10000, daily_remaining=9871, daily_reset='2026-04-29T00:00:00.000Z')
When you exceed the daily limit you'll get a typed RateLimitError:
from tcgapi import RateLimitError
try:
tcg.cards.get(123)
except RateLimitError as err:
print(f"Hit the limit — retry in {err.retry_after}s")
Other error classes: AuthError (401), TierError (403), NotFoundError (404), TcgApiError (anything else). All extend Exception.
Tiers
| Plan | Daily requests | History | Bulk endpoints | Sales velocity |
|---|---|---|---|---|
| Free | 100 | 7 days | — | — |
| Hobby ($9.99/mo) | 1,000 | 30 days | — | — |
| Starter ($19.99/mo) | 2,500 | 90 days | Limited | — |
| Pro ($49.99/mo) | 10,000 | Full | Yes | Yes |
| Business ($99.99/mo) | 50,000 | Full | Yes | Yes |
Sales velocity = sales_volume, avg_sales_price, and sales_as_of on price responses (Pro+). The fields are absent below Pro.
Or pay per request via x402 — no signup, USDC on Base or Solana.
API reference
Full endpoint reference: tcgapi.dev/api OpenAPI spec: tcgapi.dev/openapi.yaml Quickstart guide: tcgapi.dev/quickstart
License
MIT
Metadata
Release files for tcgapi 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tcgapi-0.5.0.tar.gz | 14.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| tcgapi-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.6 kB
Release files / tcgapi-0.5.0.tar.gz
| Download URL | tcgapi-0.5.0.tar.gz |
|---|---|
| Size | 14.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e9b525733813a9ddc55317bb5557bbee091ebbe2bfa9bb877d12f627ab751107
|
|
BLAKE2b-256 checksum How to use checksums |
02aa1c8c2d6c3a745f2a5ea86dcaad68f3922b110e391b28bd88302d7dde5378
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / tcgapi-0.5.0-py3-none-any.whl
| Download URL | tcgapi-0.5.0-py3-none-any.whl |
|---|---|
| Size | 21.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
596801523eb788bb8f1bc0fc05c26a7603e0c7fd997676eed9777f4883466f3f
|
|
BLAKE2b-256 checksum How to use checksums |
1fc28d80c0cf40e6f972187bb5a2092ded84b8964dd8b3ce5491d17458cd0cbc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log