GET, except POST /v1/scan. Every price is
USD, and an integer number of cents, never a decimal.
Base URL
/v1 needs a key. See Authentication.
Responses
Every read answers with the same two keys, and so does every failure. Exactly one of them is ever non-null, so checking either one tells you about the other. A detail endpoint puts the object indata:
data as well. next_cursor sits inside the envelope
rather than beside it, so data and error are the only two keys any response has:
next_cursor is null on the last page.
Errors
Every failure has the same shape. Branch oncode:
message is always present and never empty. It is written for a human reading a log, and its
wording is not part of the contract.
POST /v1/scan adds codes of its own. They are listed on Scan a card.
Paging
List endpoints page by cursor, not by offset, so rows do not shift under you between pages.
A
limit above the maximum is clamped rather than refused, and a value that is not a number
falls back to the default.
To walk a whole expansion, keep calling until next_cursor comes back null. Three cards at a
time:
q changes what a page contains, not how you walk it: rows come back best-match
first, each carrying a score, and the same cursor keeps working.
Searching
There is no search endpoint. Every list takesq and searches its own resource:
cards by name or collector number, series,
expansions and products by name. q composes with that
list’s own filters, so ?q=charizard&expansion_id=base1 searches inside one expansion.
You get back a ranked set of candidates, not one resolved id. See List cards for
how the ranking and its ties behave.
Attaching related data
Some endpoints takeinclude with a comma-separated list. On List cards it
accepts variants, metadata and change. Without it the key is absent, not null and not
[], so
a response stays byte-identical to what it was before the field existed. A value the endpoint
does not recognise is dropped rather than refused.
Caching
/v1/* responses carry an ETag. Send it back as If-None-Match and an unchanged resource
answers 304 Not Modified with no body, which is cheaper to receive and cheaper to parse. It is
not cheaper against your rate limit: the limiter counts the request before the
304 is produced, so a conditional request that comes back unchanged costs exactly as much of
your budget as a full 200 would.
Versioning
The version is in the path./v1 is current, and a breaking change would arrive as /v2 rather
than by altering /v1 under you. Adding a field to a response is not breaking, so parse
defensively and ignore keys you do not recognise.
Service health
GET /health is unauthenticated and reports whether the data being served is current:
status is ok or degraded. Degraded means some corner of the catalog is staler than it
should be. The API keeps answering either way.
feeds is the part you can act on. Both catalog and tcgplayer report their own status, when
each last synced successfully as synced_at, and the newest observed_on each holds.
observed_on is always null for catalog, which carries no prices.
Those last two answer different questions, and the gap between them is the useful part. A recent
synced_at beside an old observed_on means nothing newer has been published for that feed,
which is a different problem from a feed that has not synced at all.
degraded while every feed reads ok is a real state, and a quiet one: what you are being
served is current, and something behind it needs a look.
last_updated is the most recent successful sync across every feed: the last moment any served
data could have changed. It is null before the first sync.