{"openapi":"3.1.0","info":{"title":"TCG Card API","version":"0.4.9","description":"Card **data** for the Pokémon Trading Card Game — sets, cards, printings, images-by-address — served fast from SQLite.\n\n## Authentication\nEvery endpoint except `/v1/health`, `/v1/openapi.json` and `/docs` requires a token: `Authorization: Bearer ptcg_live_…` (or `X-API-Key: ptcg_live_…` for clients that cannot set Authorization).\n\n## Costs and quotas\nRequests are **weighted** against a monthly allowance: a card or set lookup costs 1, the scanner index 5, the bulk `catalog.db` download 100. The manifest (`/v1/catalog.json`), `/v1/me`, `/v1/languages` and **all images cost 0** — poll for updates and hotlink pictures freely, forever. A `304 Not Modified` on `catalog.db` is also free. Every plan additionally has a per-minute burst cap.\n\nEvery response carries `X-Quota-Limit`, `X-Quota-Used`, `X-Quota-Remaining`, `X-Quota-Resets` and `X-Quota-Period` headers, so a client sees the wall before hitting it.\n\n## The cheap pattern for apps\nPull `/v1/catalog.db` once (cost 100), serve lookups from your own copy, poll `/v1/catalog.json` (free) for version changes, and send `If-None-Match` when re-pulling — unchanged means a free 304.\n\n---\n*Independent fan project. Not produced by, endorsed by, or affiliated with Nintendo, Creatures Inc., GAME FREAK inc., or The Pokémon Company. Card facts are used descriptively to identify cards; no card artwork is sold.*"},"servers":[{"url":"https://api.pkmnmasterset.com"}],"components":{"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"ptcg_live_… token"},"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key"}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]},"Card":{"type":"object","properties":{"id":{"type":"string","description":"Globally unique card id, e.g. \"base1-4\"."},"setId":{"type":"string"},"number":{"type":"string","description":"The card’s number within its set (printed number, so a string)."},"name":{"type":"string"},"rarity":{"type":["string","null"]},"category":{"type":["string","null"],"description":"Pokemon, Trainer, Energy — or null."},"dex":{"type":"array","items":{"type":"integer"},"description":"National dex numbers on the card (empty for trainers/energy)."},"types":{"type":"array","items":{"type":"string"}},"hp":{"type":["integer","null"]},"illustrator":{"type":["string","null"]},"variants":{"type":"array","items":{"type":"string"},"description":"Every printing this card exists in."},"images":{"type":"object","description":"Image addresses. Never bytes: image URLs point at the public bucket (or this API’s /v1/images redirect when signing is enabled).","properties":{"low":{"type":["string","null"]},"high":{"type":["string","null"]}}},"printings":{"type":"array","items":{"type":"object","properties":{"variant":{"type":"string","description":"Machine key: normal, holo, reverse, firstEdition, or a custom printing key."},"label":{"type":["string","null"],"description":"Human label for custom printings (\"Cracked Ice Holo\"); null for standard variants."},"images":{"type":"object","description":"Image addresses. Never bytes: image URLs point at the public bucket (or this API’s /v1/images redirect when signing is enabled).","properties":{"low":{"type":["string","null"]},"high":{"type":["string","null"]}}}}},"description":"Printings with their own dedicated scans/labels."}}},"SetSummary":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"releaseDate":{"type":["string","null"]},"logo":{"type":["string","null"]},"cardCount":{"type":"object","properties":{"official":{"type":"integer"},"total":{"type":"integer"}}}}},"Printing":{"type":"object","properties":{"variant":{"type":"string","description":"Machine key: normal, holo, reverse, firstEdition, or a custom printing key."},"label":{"type":["string","null"],"description":"Human label for custom printings (\"Cracked Ice Holo\"); null for standard variants."},"images":{"type":"object","description":"Image addresses. Never bytes: image URLs point at the public bucket (or this API’s /v1/images redirect when signing is enabled).","properties":{"low":{"type":["string","null"]},"high":{"type":["string","null"]}}}}}}},"security":[{"bearer":[]},{"apiKey":[]}],"paths":{"/v1/health":{"get":{"summary":"Liveness, version, and cluster view","x-cost":0,"security":[],"description":"The one truly open endpoint — monitors should not need a key to ask \"alive?\".","responses":{"200":{"description":"Node identity, running service version, catalog version, and (when clustered) what each peer last reported.","content":{"application/json":{"schema":{"type":"object"},"example":{"ok":true,"catalog":true,"node":"east","service":"0.4.9","version":12,"sourceConfigured":true,"auth":true,"checkedAt":"2026-08-09T12:00:00.000Z"}}}}}}},"/v1/me":{"get":{"summary":"Who this token is, and how much it has left","x-cost":0,"responses":{"200":{"description":"The token’s plan and this month’s spend, summed across all nodes.","content":{"application/json":{"schema":{"type":"object"},"example":{"node":"east","name":"My app","plan":"free","monthlyLimit":1000,"burstLimit":60,"period":"2026-08","used":42,"remaining":958,"resetsAt":"2026-09-01T00:00:00.000Z","created":"2026-08-09T00:00:00.000Z"}}}},"401":{"description":"No token, or an unrecognised one. Send `Authorization: Bearer <token>` (or `X-API-Key`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"402":{"description":"Monthly allowance spent. `X-Quota-Resets` says when it refills; nothing is charged for refused requests.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"403":{"description":"The token has been revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"429":{"description":"Burst cap exceeded (per-minute). `Retry-After` is set, in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}}}}},"/v1/catalog.json":{"get":{"summary":"The catalog manifest — poll this to know when to update","x-cost":0,"responses":{"200":{"description":"Version and counts of the published catalog. Costs nothing — poll freely.","content":{"application/json":{"schema":{"type":"object"},"example":{"version":12,"cards":19000,"sets":160,"printings":31000,"contentHash":"ab12…","fetchedAt":"2026-08-09T12:00:00.000Z","checkedAt":"2026-08-09T12:05:00.000Z"}}}},"401":{"description":"No token, or an unrecognised one. Send `Authorization: Bearer <token>` (or `X-API-Key`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"402":{"description":"Monthly allowance spent. `X-Quota-Resets` says when it refills; nothing is charged for refused requests.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"403":{"description":"The token has been revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"429":{"description":"Burst cap exceeded (per-minute). `Retry-After` is set, in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}}}}},"/v1/catalog.db":{"get":{"summary":"The whole catalog, as one SQLite file","x-cost":100,"description":"Byte-for-byte the published master (tables: sets, cards, printings, meta). Send `If-None-Match` with the last `ETag` — an unchanged catalog answers `304` at cost 0.","responses":{"200":{"description":"The SQLite database (`application/octet-stream`), with an `ETag`."},"304":{"description":"You already have it. Free."},"401":{"description":"No token, or an unrecognised one. Send `Authorization: Bearer <token>` (or `X-API-Key`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"402":{"description":"Monthly allowance spent. `X-Quota-Resets` says when it refills; nothing is charged for refused requests.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"403":{"description":"The token has been revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"429":{"description":"Burst cap exceeded (per-minute). `Retry-After` is set, in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}}}}},"/v1/languages":{"get":{"summary":"Which languages are published","x-cost":0,"responses":{"200":{"description":"Language codes with data.","content":{"application/json":{"schema":{"type":"object"},"example":{"languages":["en","ja"]}}}},"401":{"description":"No token, or an unrecognised one. Send `Authorization: Bearer <token>` (or `X-API-Key`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"402":{"description":"Monthly allowance spent. `X-Quota-Resets` says when it refills; nothing is charged for refused requests.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"403":{"description":"The token has been revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"429":{"description":"Burst cap exceeded (per-minute). `Retry-After` is set, in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}}}}},"/v1/sets":{"get":{"summary":"Every set, in release order","x-cost":1,"parameters":[{"name":"lang","in":"query","required":false,"description":"Language code (en, ja, de, …). Defaults to en. /v1/languages lists what is published.","schema":{"type":"string","default":"en"}}],"responses":{"200":{"description":"Set summaries.","content":{"application/json":{"schema":{"type":"object","properties":{"language":{"type":"string"},"sets":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"releaseDate":{"type":["string","null"]},"logo":{"type":["string","null"]},"cardCount":{"type":"object","properties":{"official":{"type":"integer"},"total":{"type":"integer"}}}}}}}},"example":{"language":"en","sets":[{"id":"base1","name":"Base Set","releaseDate":"1999-01-09","logo":"https://…/en/images/base1/logo.png","cardCount":{"official":102,"total":102}}]}}}},"401":{"description":"No token, or an unrecognised one. Send `Authorization: Bearer <token>` (or `X-API-Key`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"402":{"description":"Monthly allowance spent. `X-Quota-Resets` says when it refills; nothing is charged for refused requests.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"403":{"description":"The token has been revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"429":{"description":"Burst cap exceeded (per-minute). `Retry-After` is set, in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}}}}},"/v1/sets/{id}":{"get":{"summary":"One set, with every card and printing in it","x-cost":1,"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"base1"},{"name":"lang","in":"query","required":false,"description":"Language code (en, ja, de, …). Defaults to en. /v1/languages lists what is published.","schema":{"type":"string","default":"en"}}],"responses":{"200":{"description":"The set summary plus its full card list.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"No token, or an unrecognised one. Send `Authorization: Bearer <token>` (or `X-API-Key`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"402":{"description":"Monthly allowance spent. `X-Quota-Resets` says when it refills; nothing is charged for refused requests.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"403":{"description":"The token has been revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"404":{"description":"Set not found.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"429":{"description":"Burst cap exceeded (per-minute). `Retry-After` is set, in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}}}}},"/v1/cards":{"get":{"summary":"Search cards","x-cost":1,"parameters":[{"name":"lang","in":"query","required":false,"description":"Language code (en, ja, de, …). Defaults to en. /v1/languages lists what is published.","schema":{"type":"string","default":"en"}},{"name":"name","in":"query","schema":{"type":"string"},"description":"Substring match on the card name."},{"name":"set","in":"query","schema":{"type":"string"},"description":"Restrict to one set id."},{"name":"rarity","in":"query","schema":{"type":"string"},"description":"Exact rarity."},{"name":"type","in":"query","schema":{"type":"string"},"description":"Exact type (Fire, Water, …)."},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"perPage","in":"query","schema":{"type":"integer","default":100,"maximum":250}}],"responses":{"200":{"description":"Paged results; each card also carries `setName`.","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"page":{"type":"integer"},"perPage":{"type":"integer"},"more":{"type":"boolean"},"cards":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Globally unique card id, e.g. \"base1-4\"."},"setId":{"type":"string"},"number":{"type":"string","description":"The card’s number within its set (printed number, so a string)."},"name":{"type":"string"},"rarity":{"type":["string","null"]},"category":{"type":["string","null"],"description":"Pokemon, Trainer, Energy — or null."},"dex":{"type":"array","items":{"type":"integer"},"description":"National dex numbers on the card (empty for trainers/energy)."},"types":{"type":"array","items":{"type":"string"}},"hp":{"type":["integer","null"]},"illustrator":{"type":["string","null"]},"variants":{"type":"array","items":{"type":"string"},"description":"Every printing this card exists in."},"images":{"type":"object","description":"Image addresses. Never bytes: image URLs point at the public bucket (or this API’s /v1/images redirect when signing is enabled).","properties":{"low":{"type":["string","null"]},"high":{"type":["string","null"]}}},"printings":{"type":"array","items":{"type":"object","properties":{"variant":{"type":"string","description":"Machine key: normal, holo, reverse, firstEdition, or a custom printing key."},"label":{"type":["string","null"],"description":"Human label for custom printings (\"Cracked Ice Holo\"); null for standard variants."},"images":{"type":"object","description":"Image addresses. Never bytes: image URLs point at the public bucket (or this API’s /v1/images redirect when signing is enabled).","properties":{"low":{"type":["string","null"]},"high":{"type":["string","null"]}}}}},"description":"Printings with their own dedicated scans/labels."}}}}}}}}},"401":{"description":"No token, or an unrecognised one. Send `Authorization: Bearer <token>` (or `X-API-Key`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"402":{"description":"Monthly allowance spent. `X-Quota-Resets` says when it refills; nothing is charged for refused requests.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"403":{"description":"The token has been revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"429":{"description":"Burst cap exceeded (per-minute). `Retry-After` is set, in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}}}}},"/v1/cards/{id}":{"get":{"summary":"One card, with its printings","x-cost":1,"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"example":"base1-4"},{"name":"lang","in":"query","required":false,"description":"Language code (en, ja, de, …). Defaults to en. /v1/languages lists what is published.","schema":{"type":"string","default":"en"}}],"responses":{"200":{"description":"The card.","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Globally unique card id, e.g. \"base1-4\"."},"setId":{"type":"string"},"number":{"type":"string","description":"The card’s number within its set (printed number, so a string)."},"name":{"type":"string"},"rarity":{"type":["string","null"]},"category":{"type":["string","null"],"description":"Pokemon, Trainer, Energy — or null."},"dex":{"type":"array","items":{"type":"integer"},"description":"National dex numbers on the card (empty for trainers/energy)."},"types":{"type":"array","items":{"type":"string"}},"hp":{"type":["integer","null"]},"illustrator":{"type":["string","null"]},"variants":{"type":"array","items":{"type":"string"},"description":"Every printing this card exists in."},"images":{"type":"object","description":"Image addresses. Never bytes: image URLs point at the public bucket (or this API’s /v1/images redirect when signing is enabled).","properties":{"low":{"type":["string","null"]},"high":{"type":["string","null"]}}},"printings":{"type":"array","items":{"type":"object","properties":{"variant":{"type":"string","description":"Machine key: normal, holo, reverse, firstEdition, or a custom printing key."},"label":{"type":["string","null"],"description":"Human label for custom printings (\"Cracked Ice Holo\"); null for standard variants."},"images":{"type":"object","description":"Image addresses. Never bytes: image URLs point at the public bucket (or this API’s /v1/images redirect when signing is enabled).","properties":{"low":{"type":["string","null"]},"high":{"type":["string","null"]}}}}},"description":"Printings with their own dedicated scans/labels."}}},"example":{"id":"base1-4","setId":"base1","number":"4","name":"Charizard","rarity":"Rare Holo","category":"Pokemon","dex":[6],"types":["Fire"],"hp":120,"illustrator":"Mitsuhiro Arita","variants":["holo","firstEdition"],"images":{"low":"https://…/low.webp","high":"https://…/high.webp"},"printings":[]}}}},"401":{"description":"No token, or an unrecognised one. Send `Authorization: Bearer <token>` (or `X-API-Key`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"402":{"description":"Monthly allowance spent. `X-Quota-Resets` says when it refills; nothing is charged for refused requests.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"403":{"description":"The token has been revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"404":{"description":"Card not found.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"429":{"description":"Burst cap exceeded (per-minute). `Retry-After` is set, in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}}}}},"/v1/scan-index":{"get":{"summary":"Perceptual-hash fingerprints for on-device card scanning","x-cost":5,"parameters":[{"name":"lang","in":"query","required":false,"description":"Language code (en, ja, de, …). Defaults to en. /v1/languages lists what is published.","schema":{"type":"string","default":"en"}}],"responses":{"200":{"description":"The algorithm name and one fingerprint row per card: `[cardId, hash]`.","content":{"application/json":{"schema":{"type":"object","properties":{"algo":{"type":"string"},"cards":{"type":"array","items":{"type":"array"}}}}}}},"401":{"description":"No token, or an unrecognised one. Send `Authorization: Bearer <token>` (or `X-API-Key`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"402":{"description":"Monthly allowance spent. `X-Quota-Resets` says when it refills; nothing is charged for refused requests.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"403":{"description":"The token has been revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"404":{"description":"No index published for that language.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"429":{"description":"Burst cap exceeded (per-minute). `Retry-After` is set, in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}}}}},"/v1/images/{key}":{"get":{"summary":"A card image, by address — answers with a redirect","x-cost":0,"description":"Always cost 0, by standing policy — images are never metered or sold. The answer is a `302` to where the bytes live; this API never serves image bytes itself.","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string"},"example":"en/images/base1/4/low.webp"}],"responses":{"302":{"description":"Redirect to the image."},"400":{"description":"Not an image path.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"401":{"description":"No token, or an unrecognised one. Send `Authorization: Bearer <token>` (or `X-API-Key`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"402":{"description":"Monthly allowance spent. `X-Quota-Resets` says when it refills; nothing is charged for refused requests.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"403":{"description":"The token has been revoked.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}},"429":{"description":"Burst cap exceeded (per-minute). `Retry-After` is set, in seconds.","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"What went wrong, in a sentence."}},"required":["error"]}}}}}}}}}