Skip to content
pokemontcgapi.com
Documentation

Authentication

API keys, the two key types, where to put them, and how to survive a leak.

One header. No OAuth dance, no token exchange, no expiring access tokens to refresh.

auth.sh
curl -s "https://api.pokemontcgapi.com/v1/sets/sv3" \
  -H "X-Api-Key: ptcg_live_9f2c1d8b4a7e3f60"

Authorization: Bearer <key> is accepted as an exact alias, for clients whose HTTP layer only speaks bearer tokens.

Two kinds of key

Which one you need is decided by where the code runs, not by what it does. A secret key in a browser bundle is public the moment it ships, whatever your intentions were.

Rowptcg_live_ptcg_pub_
Runs inYour serverA browser
ScopesEverything the plan allowsCatalogue reads only
Referrer allow-listNot applicableRequired — requests from other origins get 403 ORIGIN_NOT_ALLOWED
PricesYesCurrent prices only, never history or bulk
If it leaksRevoke immediatelyLow blast radius; still revoke

CORS is open, and that is not the same thing

Every origin may call this API directly from a browser: no proxy of your own to build, deploy and keep patched. But CORS protects your user’s browser, not your key. Key safety comes from the ptcg_pub_ type and its referrer allow-list.

Where the key goes

  • Header, always. There is no ?api_key= query parameter, on purpose: query strings end up in access logs, in referrer headers and in browser history.
  • One key per deployment. Staging, production and each developer get their own, so revoking one does not stop the others.
  • Never in a repository. A key in git is a key in every clone and every fork, forever. If one gets committed, revoke it and issue a new one — rotation is additive and costs you nothing.

When authentication fails

StatusCodeMeaning
401MISSING_API_KEYNo key on a route that needs one.
401INVALID_API_KEYNo account matches. Usually a truncated copy-paste.
401KEY_REVOKEDThe key was revoked. Issue a new one.
401KEY_EXPIREDThe key had an expiry and it has passed.
403INSUFFICIENT_SCOPEReal key, wrong scopes — typically a browser key on a priced route.
403ORIGIN_NOT_ALLOWEDBrowser key used from an origin outside its allow-list.

Do not retry a 401

None of these get better on their own. Retrying a 401 in a loop burns your rate limit and produces nothing — treat the whole family as fatal, surface it, and stop.

Rotating a key

Rotation is additive, so it never needs a maintenance window:

1. Create a second key in the dashboard.
2. Deploy it. Both keys are live and both count against the same account quota.
3. Confirm the old key has stopped being used (dashboard shows last-used per key).
4. Revoke the old key.

A leaked key is an emergency, and downtime is the correct outcome

Revoke first, redeploy second. A revoked key returns 401 KEY_REVOKED immediately everywhere — that is the point.

How keys are stored

We keep the first 16 characters in clear text, indexed, and an Argon2id hash of the whole key peppered with a secret that lives in the application environment rather than the database. The prefix makes lookup a single indexed row instead of hashing every candidate; the pepper means a stolen database dump is not enough to verify a key against.

The consequence for you: we cannot recover a lost key, only issue a new one.

view this page as markdown