REST reference
Plain JSON over HTTPS, one filter grammar for every game. Every endpoint has its own page with every parameter, default, response field and error, and real responses from the live API.
Base URL
https://api.tcggraph.com/v1HTTPS only. Every response body is JSON, errors included; the exceptions are a 304, which has no body, and a 405 for a method a path does not take, which has an empty one. The machine-readable description of everything on these pages is the OpenAPI 3.1 document at https://api.tcggraph.com/openapi.json, generated from the same source as these pages; Clients shows how to generate a typed client from it.
curl "https://api.tcggraph.com/v1/cards?game=pokemon&name=charizard&limit=1" \
-H "Authorization: Bearer $TCGGRAPH_API_KEY"Authentication
Send your key as Authorization: Bearer tcg_live_..., or as ?api_key= where a header cannot be set; the header wins when both are sent. A missing or unknown key is a 401. Details, including origin restrictions for keys used in a browser, are on Authentication.
Versioning
There is one version, v1, in the path. There is no version header and no per-key pinning: every key gets the API as it is today. New fields, endpoints and enum values are added to v1, so ignore fields you do not know rather than failing on them. What changed and when is in the changelog.
Conventions
// 2xx
{ "data": /* an object or an array */, "meta": { /* paging, echoes, provenance */ } }
// 4xx and 5xx
{ "error": { "code": "invalid_request", "message": "...", "details": { /* field, allowed, ... */ } } }- Amounts are numbers in major units of the stated
currency:8.44is €8.44. Nothing is converted between currencies. - Timestamps are ISO 8601 in UTC (
2026-09-23T04:12:09.000Z); dates areYYYY-MM-DD. - Null and empty: a field is
nullonly where its type says so, and a list with nothing in it is[]. Every documented field is present unless its row says it is absent unless it applies. - Ids are stable strings (
pkm_sv3pt5_6). If a set is re-keyed, the old id keeps answering on every by-id endpoint withmeta.movedFrom. - Nothing is silently ignored: an unknown filter or parameter, or a value outside a parameter’s list, is a
400naming it with the values that would have worked. Endpoints that take no query parameters (the games, a card by id, the webhook and watch lists) ignore them. - Caching: responses carry an
ETag. Send it back asIf-None-Matchand an unchanged resource is a304with no body, costing 0 credits. A304carries onlyETagandCache-Control; the credit headers are on your next200. - CORS is open to every origin, so the API can be called from a browser; restrict the key to your origins when you do.
Endpoints
Games
| Endpoint | What it does | Credits |
|---|---|---|
| GET/v1/games | Every supported game, with what is indexed for it right now. | 1 |
| GET/v1/games/{slug} | One game, with its gameData schema: the fields every card of the game carries and you can filter on. | 1 |
Sets
| Endpoint | What it does | Credits |
|---|---|---|
| GET/v1/sets | Sets across every game, newest first. | 1 |
| GET/v1/sets/{code} | One set and a page of its cards. | 2 |
Cards
| Endpoint | What it does | Credits |
|---|---|---|
| GET/v1/cards | Search and filter the catalogue. Every filter is optional and they combine with AND. | 2 |
| GET/v1/cards/{id} | One printing by id, in any language. | 1 |
Prices
| Endpoint | What it does | Credits |
|---|---|---|
| GET/v1/prices/{cardId} | Every current quote for one card: marketplace, graded and per-language, in one call. | 1 |
| GET/v1/prices/{cardId}/history | One quote over time: a marketplace series, or a graded slot. | 3 |
| GET/v1/prices/{cardId}/sales | What copies actually sold for on Cardmarket, day by day. | 3 |
Webhooks
| Endpoint | What it does | Credits |
|---|---|---|
| POST/v1/webhooks | Register an HTTPS endpoint for signed event deliveries. | 0 |
| GET/v1/webhooks | Every endpoint registered on the account. | 0 |
| GET/v1/webhooks/{id} | One endpoint and its last 20 deliveries. | 0 |
| DELETE/v1/webhooks/{id} | Stop deliveries and remove the endpoint. | 0 |
Price watches
| Endpoint | What it does | Credits |
|---|---|---|
| POST/v1/watches | Fire price.threshold.crossed when one quote of one card crosses a price. | 0 |
| GET/v1/watches | Every watch on the account, and how many the plan allows. | 0 |
| DELETE/v1/watches/{id} | Stop watching. | 0 |
Card recognition (POST /v1/scan and the camera socket) is metered separately and documented on Card recognition. GraphQL is one request for 2 credits, on GraphQL.
Objects
Filtering
/v1/cards and /v1/sets/{code} take the same filters as query parameters, combined with AND. The named ones (name, set, rarity, …) mean the same thing in every game; anything else is a field of the game’s gameData, which each game declares and GET /v1/games/{slug} returns as schema. A name that is neither is a 400 with a suggestion where one is obvious (q suggests name). How each filter matches, exactly, is on List cards.
These are run against the live API every time the reference is rebuilt; the count is what each returned.
| Request | What it shows | Matches |
|---|---|---|
| /cards?game=pokemon&name=charizard | Part of the English name, any case. | 158 |
| /cards?game=pokemon&text=discard | Part of the rules text. | 15 |
| /cards?game=pokemon&rarity=double rare | The whole rarity, any case. | 514 |
| /cards?game=pokemon&set=151&collectorNumber=006/165 | One printing: set by name, number as printed. | 1 |
| /cards?game=one-piece&collectorNumber=OP05-119 | A printed number shared by parallels: the base card and every parallel of it. | 11 |
| /cards?game=pokemon&artist=mitsuhiro arita | The whole artist credit. | 723 |
| /cards?game=pokemon&source=cardmarket&minPrice=50&maxPrice=500 | €50 to €500 on Cardmarket. | 1,141 |
| /cards?game=pokemon®ion=NA&sort=-price | Dearest first, in USD on TCGplayer. | 26,466 |
| /cards?game=pokemon&language=ja&name=pikachu | Japanese printings, searched by English name. | 397 |
| /cards?game=pokemon&language=fr&name=dracaufeu | French printings, searched by the name printed on them. | 103 |
| /cards?game=one-piece&language=ja&name=ルフィ | Japanese printings by their Japanese name. | 154 |
| /cards?game=pokemon&types=fire&minHp=300 | A list field and a numeric bound from gameData. | 71 |
| /cards?game=yugioh&attribute=light&atk=3000 | Scalar gameData fields, any case. | 324 |
| /cards?game=yugioh&banlist.tcg=forbidden | A dotted path into an object field. | 475 |
| /cards?game=disney-lorcana&ink=amethyst&inkable=false | Booleans are matched as text. | 135 |
| /cards?game=pokemon&sort=-gradedPrice&grader=psa&grade=10 | Dearest PSA 10 first. | 26,466 |
Price filters read one marketplace, cardmarket unless source or region says otherwise, and are in its currency: minPrice=50 is €50 on Cardmarket and $50 on TCGplayer. Every card still returns all of its quotes; meta.priceSource says which one the filter read. Prices covers the markets.
Set codes are unique within a game and language, not across the catalogue: OP16 is a One Piece set and a Yu-Gi-Oh! set, and Pokémon sv3 is a different set in English and Japanese. Name the game when you name a set.
Pagination
Lists take page (from 1) and limit (1 to 100, default 20) and return meta.page, meta.limit, meta.totalCount, meta.totalPages and meta.hasMore. Loop until hasMore is false. Order is stable across pages: every card sort breaks ties on the id, and every set sort on the code. Each page is billed as one call, so limit=100 is the cheapest way through a long list.
async function* allCards(params) {
for (let page = 1; ; page++) {
const query = new URLSearchParams({ ...params, page: String(page), limit: "100" });
const response = await fetch(`https://api.tcggraph.com/v1/cards?${query}`, {
headers: { Authorization: `Bearer ${process.env.TCGGRAPH_API_KEY}` },
});
const body = await response.json();
if (!response.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
yield* body.data;
if (!body.meta.hasMore) return;
}
}
for await (const card of allCards({ game: "pokemon", set: "sv3pt5" })) console.log(card.id);Credits and limits
Each endpoint’s cost is in the table above and on its page, and every 2xx response says what it cost in X-Credits-Cost. A 4xx or a 304 costs nothing and carries no credit headers. Each key is also limited per minute, and each account per day; both answer 429 with Retry-After. Rate limits & credits has the numbers per plan and every header.
Game-specific fields
Each game’s page lists its gameData fields, their types and a real value:
- Pokémon Trading Card Game: 13 fields
- Magic: The Gathering: 11 fields
- One Piece Card Game: 9 fields
- Yu-Gi-Oh! Trading Card Game: 11 fields
- Disney Lorcana: 10 fields
- Star Wars: Unlimited: 9 fields
- Digimon Card Game: 10 fields
- Grand Archive: 9 fields
- Dragon Ball Super Card Game: 13 fields