Premier Card Grading Partner API (v1)
Population data for every card PCG has graded ā which sets exist, how many of each card have been graded, and the full grade breakdown per card ā and lookup of a single completed certification by its cert number.
Base URL https://app.premiercardgrading.com/partner/v1
This document is the contract. If anything here disagrees with the API, the API is right and this document is wrong. Please tell us so we can fix it.
Authentication
Every request needs an API key, which Premier Card Grading issues to you.
Authorization: Bearer pcg_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxUse that exact header. The scheme is matched case-insensitively, and a key sent without the
Bearer prefix is also accepted, but neither of those is guaranteed, so send the standard form.
Confirm your key works with a call to /me:
curl -H "Authorization: Bearer $PCG_API_KEY" \
"https://app.premiercardgrading.com/partner/v1/me"const res = await fetch(
"https://app.premiercardgrading.com/partner/v1/me",
{ headers: { Authorization: `Bearer ${process.env.PCG_API_KEY}` } }
);
const data = await res.json();import os, requests
res = requests.get(
"https://app.premiercardgrading.com/partner/v1/me",
headers={"Authorization": f"Bearer {os.environ['PCG_API_KEY']}"},
)
data = res.json()var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://app.premiercardgrading.com/partner/v1/me"))
.header("Authorization", "Bearer " + System.getenv("PCG_API_KEY"))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());Your key is shown once, when it is created. We do not store the key itself. We keep only a hash of
it and its first 16 characters, which is the non-secret key_prefix shown by /me. We cannot read
a key back to you, so if it is lost we issue a new one. Treat a key like a password: keep it on your
server, and never put it in a browser, a mobile app, or anywhere a customer can see it.
You can hold several keys at once, which is how you rotate without downtime. Ask us for a new key, deploy it, then ask us to revoke the old one. If a key leaks, tell us and we will revoke it. A revoked key stops working on its next request.
Call GET /partner/v1/me to confirm which account and key you are using. It is the quickest way to
check a key works before you build anything else.
Quick start
Every call is a GET with your key in the Authorization header. Pick your language once with
the tabs; the choice applies to every example on the page. Catalog endpoints need the
CATALOG_READ scope. Certification lookup needs CERT_READ. /me works with any valid key, so
you can see which scopes you hold.
Confirm your key and see which account you are:
curl -H "Authorization: Bearer $PCG_API_KEY" \
"https://app.premiercardgrading.com/partner/v1/me"const res = await fetch(
"https://app.premiercardgrading.com/partner/v1/me",
{ headers: { Authorization: `Bearer ${process.env.PCG_API_KEY}` } }
);
const data = await res.json();import os, requests
res = requests.get(
"https://app.premiercardgrading.com/partner/v1/me",
headers={"Authorization": f"Bearer {os.environ['PCG_API_KEY']}"},
)
data = res.json()var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://app.premiercardgrading.com/partner/v1/me"))
.header("Authorization", "Bearer " + System.getenv("PCG_API_KEY"))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());List graded sets, filtered by game and paged:
curl -H "Authorization: Bearer $PCG_API_KEY" \
"https://app.premiercardgrading.com/partner/v1/sets?gameCode=PKM&limit=50"const res = await fetch(
"https://app.premiercardgrading.com/partner/v1/sets?gameCode=PKM&limit=50",
{ headers: { Authorization: `Bearer ${process.env.PCG_API_KEY}` } }
);
const data = await res.json();import os, requests
res = requests.get(
"https://app.premiercardgrading.com/partner/v1/sets?gameCode=PKM&limit=50",
headers={"Authorization": f"Bearer {os.environ['PCG_API_KEY']}"},
)
data = res.json()var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://app.premiercardgrading.com/partner/v1/sets?gameCode=PKM&limit=50"))
.header("Authorization", "Bearer " + System.getenv("PCG_API_KEY"))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());Fetch one set on its own:
curl -H "Authorization: Bearer $PCG_API_KEY" \
"https://app.premiercardgrading.com/partner/v1/sets/4098"const res = await fetch(
"https://app.premiercardgrading.com/partner/v1/sets/4098",
{ headers: { Authorization: `Bearer ${process.env.PCG_API_KEY}` } }
);
const data = await res.json();import os, requests
res = requests.get(
"https://app.premiercardgrading.com/partner/v1/sets/4098",
headers={"Authorization": f"Bearer {os.environ['PCG_API_KEY']}"},
)
data = res.json()var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://app.premiercardgrading.com/partner/v1/sets/4098"))
.header("Authorization", "Bearer " + System.getenv("PCG_API_KEY"))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());Fetch the cards in a set, each with its full population breakdown:
curl -H "Authorization: Bearer $PCG_API_KEY" \
"https://app.premiercardgrading.com/partner/v1/sets/4098/cards?limit=50"const res = await fetch(
"https://app.premiercardgrading.com/partner/v1/sets/4098/cards?limit=50",
{ headers: { Authorization: `Bearer ${process.env.PCG_API_KEY}` } }
);
const data = await res.json();import os, requests
res = requests.get(
"https://app.premiercardgrading.com/partner/v1/sets/4098/cards?limit=50",
headers={"Authorization": f"Bearer {os.environ['PCG_API_KEY']}"},
)
data = res.json()var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://app.premiercardgrading.com/partner/v1/sets/4098/cards?limit=50"))
.header("Authorization", "Bearer " + System.getenv("PCG_API_KEY"))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());Look up one completed certification by the number printed on the slab. Leading zeros are optional.
This call needs the CERT_READ scope.
curl -H "Authorization: Bearer $PCG_API_KEY" \
"https://app.premiercardgrading.com/partner/v1/certs/000202027"const res = await fetch(
"https://app.premiercardgrading.com/partner/v1/certs/000202027",
{ headers: { Authorization: `Bearer ${process.env.PCG_API_KEY}` } }
);
const data = await res.json();import os, requests
res = requests.get(
"https://app.premiercardgrading.com/partner/v1/certs/000202027",
headers={"Authorization": f"Bearer {os.environ['PCG_API_KEY']}"},
)
data = res.json()var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
.uri(URI.create("https://app.premiercardgrading.com/partner/v1/certs/000202027"))
.header("Authorization", "Bearer " + System.getenv("PCG_API_KEY"))
.build();
var response = client.send(request, HttpResponse.BodyHandlers.ofString());Endpoints
GET/sets
Every set with at least one graded card.
| Parameter | Type | Notes |
|---|---|---|
category |
string | Exact match, case-insensitive. Often absent. See Data notes. |
year |
string | Four-digit year. |
gameCode |
string | PCG game code, for example PKM or fab. |
offset |
integer | Default 0. |
limit |
integer | Default 50, maximum 200. |
{
"set_count": 3633,
"offset": 0,
"limit": 2,
"sets": [
{
"set_id": 4098,
"set_name": "LSS Pre Grade Promos",
"game_code": "fab",
"category": "TCG",
"year": "2023",
"language": null,
"total_graded": 7296,
"distinct_cards": 123
}
]
}set_count is the total number of sets matching your filters, not the size of this page. It means
the same thing whether or not you paginate.
total_graded and distinct_cards on this endpoint come from a catalog-wide roll-up. The two
single-set endpoints below count each set on their own. All of these figures are cached and
refreshed in the background, per server instance, so keep three things in mind:
- Any figure can lag the live population. Usually by a few minutes, and by up to half an hour on an instance that has not been asked about a set recently.
- Neither endpoint is reliably fresher than the other, so a difference between them does not tell you which is newer.
- Two identical requests can return different numbers, because we run several instances and each caches on its own.
What always holds within a single response is that /sets/{setId}/cards agrees with itself: its
total_graded is the sum over the cards in that response. If you need one consistent view of the
whole catalog, do a single pass rather than comparing the endpoints against each other.
GET/sets/{setId}
One set, with the same fields as an entry in sets[]. Returns 404 if the set has no graded cards.
This is judged against the same cached roll-up as the listing, so a set that has just had its first
card graded can still return 404 until that instance refreshes, and a set whose grades were
withdrawn can return 200 with zero counts for a short while.
GET/sets/{setId}/cards
The set, plus a page of its graded cards.
| Parameter | Type | Notes |
|---|---|---|
offset |
integer | Default 0. |
limit |
integer | Default 50, maximum 200. |
{
"set_id": 4098,
"set_name": "LSS Pre Grade Promos",
"game_code": "fab",
"category": "TCG",
"year": "2023",
"language": null,
"total_graded": 7296,
"distinct_cards": 123,
"card_count": 123,
"offset": 0,
"limit": 1,
"cards": [
{
"card_id": 1042328,
"card_name": "Scabskin Leathers",
"card_number": "FAB003",
"finish": "Golden Cold Foil",
"rarity": "Legendary",
"variant": null,
"language": "English",
"total_graded": 40,
"pop_report": {
"authentic": 40,
"1": 0, "1.5": 0, "2": 0, "2.5": 0, "3": 0, "3.5": 0, "4": 0, "4.5": 0,
"5": 0, "5.5": 0, "6": 0, "6.5": 0, "7": 0, "7.5": 0, "8": 0, "8.5": 0,
"9": 0, "9.5": 0,
"10": 0, "gem_mint_10": 0, "pristine": 0, "flawless": 0
}
}
]
}The set-level fields are repeated at the top level so one call gives you everything about the set and
its cards. card_count is how many graded cards this endpoint can list for the set, and cards is
one page of them. distinct_cards is how many cards the population figures were computed over. These
two numbers are the same except for a short window after a card has been moved out of a set or removed
from the catalog, when the population still counts grades the listing can no longer show.
GET/certs/{certNumber}
One completed certification. {certNumber} is the number printed on the label: a numeric id,
optionally left-padded with zeros to nine digits (000202027 and 202027 are the same cert).
Needs the CERT_READ scope. A key that only has CATALOG_READ receives 403
insufficient_scope.
Returns 404 not_found when the number is unknown, the card is still in grading, or it was never
given a final grade. Those three cases are indistinguishable on purpose.
Images are the catalog stock photos of the card, not photographs of the physical slab. front or
back is null when the catalog has no image on that side.
pop_report and total_graded are the worldwide population of this catalog card, using the same
assembler and invariant as /sets/{setId}/cards. They can lag the live population in the same way
the set endpoints can.
{
"cert_number": "000202027",
"grade": "Pristine",
"final_grade": 10.0,
"centering": 10.0,
"corners": 10.0,
"edges": 10.0,
"surface": 9.5,
"card_id": 1042328,
"card_name": "Charizard",
"card_number": "4/102",
"finish": "Holofoil",
"rarity": "Holo Rare",
"variant": "1st Edition",
"language": "English",
"set_id": 4098,
"set_name": "Base Set",
"game_code": "PKM",
"year": "1999",
"completed_on": "2026-03-12T00:00:00Z",
"images": {
"front": "https://example.invalid/front.png",
"back": "https://example.invalid/back.png"
},
"total_graded": 40,
"pop_report": {
"authentic": 0,
"1": 0, "1.5": 0, "2": 0, "2.5": 0, "3": 0, "3.5": 0, "4": 0, "4.5": 0,
"5": 0, "5.5": 0, "6": 0, "6.5": 0, "7": 0, "7.5": 0, "8": 0, "8.5": 0,
"9": 0, "9.5": 0,
"10": 0, "gem_mint_10": 0, "pristine": 3, "flawless": 0
}
}grade is the label printed on the slab (Pristine, Gem Mint 10, Flawless, Authentic, or a
numeric such as 8.5). final_grade is the numeric value. A 10.0 still splits into four named
buckets in pop_report; this one card's own bucket is the one that matches its four sub-grades.
GET/me
{
"account_id": 12,
"account_name": "Acme Cards",
"company": "Acme Ltd",
"key_prefix": "pcg_live_a1b2c3d",
"key_label": "production",
"scopes": ["CATALOG_READ", "CERT_READ"],
"allowed_regions": ["us"],
"submission_enabled_by_region": {"us": false},
"enabled_features": ["catalog", "certs"],
"limits": [
{"name": "read_requests_per_minute", "value": 600, "limit_scope": "per_pod"},
{"name": "write_requests_per_minute", "value": 30, "limit_scope": "per_pod"},
{"name": "webhook_verify_test_replay_per_minute", "value": 6, "limit_scope": "per_pod"},
{"name": "max_items_per_order", "value": 100, "limit_scope": "account"},
{"name": "max_webhook_endpoints", "value": 5, "limit_scope": "account"}
]
}An account HQ has not granted any office returns empty allowed_regions and
submission_enabled_by_region; its catalog and cert access is unchanged.
scopes is the list of what this key may call:
| Scope | Endpoints |
|---|---|
CATALOG_READ |
/sets, /sets/{setId}, /sets/{setId}/cards |
CERT_READ |
/certs/{certNumber} |
ORDER_READ |
order, item, manifest and shipment reads (not yet generally enabled) |
ORDER_WRITE |
order submit and cancel (not yet generally enabled) |
WEBHOOKS_READ |
webhook delivery history (not yet generally enabled) |
WEBHOOKS_WRITE |
webhook endpoint lifecycle (not yet generally enabled) |
/me itself requires no scope. Existing keys were issued with CATALOG_READ only; certification
lookup needs a key that carries CERT_READ. Regional discovery (/regions, submission options,
intake address) also works with any valid key, but only for offices HQ has granted that account.
Empty grants do not default to an office. A test (sandbox) office appears only to an account HQ has
granted it to, so an integration can be exercised end to end before going live. ORDER_* and WEBHOOKS_* are issued only when HQ
enables a marketplace integration; they are not implied by catalog or cert scopes.
/me also returns allowed_regions, submission_enabled_by_region, enabled_features and
limits. Inactive and sentinel offices are omitted. enabled_features is catalog / certs /
orders / webhooks from the key's scopes. orders needs ORDER_READ with any granted office,
or ORDER_WRITE with a submission-enabled one.
per_pod limits are counted per server instance and are therefore approximate; see
Rate limits for which are counted per key and which per account.
GET/regions
Returns the offices this account may use. Optional country_code is a two-letter office country
filter (use GB, not UK), not a nearest-office picker. It is case-insensitive and surrounding
whitespace is ignored. Anything else, including a supplied but empty value, is 400; a code that
matches none of your granted offices is 200 with data: [].
{
"data": [
{
"region": "us",
"office_id": "7",
"office_name": "PCG Example Office",
"country_code": "US",
"submission_enabled": true,
"intake_available": true
}
]
}intake_available is true only when submissions are enabled for this account there and the
office has an approved, complete intake address right now.
GET/regions/{region}/submission-options
Offerings HQ has enabled for this account and region, plus the public workflow stage keys. The
region must be granted; it is never inferred. Each offering includes declared_value_required,
declared_value_currency and declared_value_limit (JSON null when HQ has not set a cap).
An inactive office, or one whose workflow publishes no stages, is
503 submission_configuration_unavailable.
{
"region": "us",
"submission_enabled": true,
"billing": {"mode": "CONTRACT", "agreement_reference": "fixture-agreement"},
"offerings": [{
"code": "standard-traditional",
"input_mode": "traditional",
"priority_code": "Standard",
"slab_types": ["Normal", "Player"],
"declared_value_required": true,
"declared_value_currency": "USD",
"declared_value_limit": 1000.00,
"return_targets": ["END_CUSTOMER"]
}],
"allowed_return_country_codes": ["US"],
"max_items": 100,
"workflow_stages": [
{"stage_key": "INTAKE", "is_done": false, "is_cancelled": false},
{"stage_key": "GRADING", "is_done": false, "is_cancelled": false},
{"stage_key": "DONE", "is_done": true, "is_cancelled": false},
{"stage_key": "REVIEW", "is_done": false, "is_cancelled": false},
{"stage_key": "CANCELLED", "is_done": false, "is_cancelled": true}
]
}billing is null until HQ has recorded an agreement for this account and region.
workflow_stages lists the region's published stages in order, then its review stage (when the
workflow has one) and its cancellation stage. New stage keys may appear; rely on is_done / is_cancelled, not on a fixed list of
keys. A workflow change can take up to 30 seconds to appear here.
allowed_return_country_codes lists the ISO countries a graded card may be returned to under
this grant: the office's own country plus any HQ has added. An empty list means no return
destination is currently allowed.
GET/regions/{region}/intake-address
The approved PCG ship-to address for checkout grading upsell. Available before any order exists
and does not create one. Discovery responses are Cache-Control: no-store.
{
"region": "us",
"office_id": "7",
"office_name": "PCG Example Office",
"address_id": "701",
"address_revision": "ia_3f1cā¦",
"recipient": {
"name": "PCG Grading Intake",
"company": "Premier Card Grading",
"phone": "+12025550123",
"email": "[email protected]"
},
"address": {
"line1": "100 Example Intake Street",
"line2": null,
"city": "Example City",
"administrative_area": "CA",
"postal_code": "90001",
"country_code": "US"
},
"shipping_instructions": "Include the PCG submission manifest inside the parcel.",
"reference_instructions": "Use the accepted submission_reference in the carrier reference field.",
"updated_at": "2026-09-21T00:00:00Z"
}Optional fields are null, never invented. When it cannot be served:
| Status | error |
When |
|---|---|---|
403 |
region_not_allowed |
The region is not granted to this account |
403 |
submissions_disabled |
The region is granted for reads only |
503 |
submission_configuration_unavailable |
The office is inactive |
503 |
intake_address_unavailable |
No approved intake, intake switched off by HQ, or the approved address is deactivated or incomplete |
address_revision is an opaque token; send it back unchanged when creating an order. It changes
whenever HQ changes the approval or the address itself is edited, and updated_at is the later of
those two edits.
Every response includes X-PCG-Request-Id; quote it when you contact us about a failed request.
Mutating partner bodies are capped at 256 KiB, counted as they are read, so a chunked body is
capped too.
Submitted card identity is capped at 255 characters for names/set/game/finish/rarity, 128 for card number, and 64 for language. Declared value is a non-negative decimal string with at most 12 integral digits and 2 fractional digits.
The grade scale
PCG's scale runs from 1 to 10 in half steps, plus authentic for cards that were authenticated
but not given a numeric grade.
A final grade of 10 is not a single grade. It splits into four buckets, decided by the four sub-grades (centering, corners, edges, surface):
| Bucket | Meaning |
|---|---|
10 |
A 10 whose sub-grades match none of the patterns below |
gem_mint_10 |
Two sub-grades at 10, two at 9.5 |
pristine |
Three sub-grades at 10, one at 9.5 |
flawless |
All four sub-grades at 10 |
The four are mutually exclusive, so no card is counted twice. If you are used to a single flat 10,
note that collapsing these buckets undercounts the top of the market. A Flawless is much rarer and
more valuable than a Gem Mint 10.
The pop_report keys, in order, are:
authentic, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5,
10, gem_mint_10, pristine, flawlessEvery key is always present, so a 0 means "no copies at this grade", not "we did not report this
grade".
Only an exact 10.0 splits into the four named buckets. Any value off the standard ladder, historical
or otherwise, is reported under its own key (for example "10.5" or "0.5") rather than rounded
into a neighbour or dropped. Read pop_report as a map and loop over its keys rather than assuming
exactly these 23.
The invariant you can rely on
card.total_graded == sum(card.pop_report.values())The same invariant holds for a cert lookup (cert.total_graded and cert.pop_report). If you ever
see it fail, that is a bug, so please report it.
Populations are global
A population is counted worldwide. PCG grades in several regions, and a card graded in New Zealand belongs to the same population as one graded in the United States. There is no per-region population and no region parameter.
Pagination
Both list endpoints use offset paging. limit defaults to 50 and is capped at 200; asking for
more gives you 200 rather than an error. A limit of zero or less gives you the default 50, and a
negative offset is treated as 0.
The order is stable and part of the contract, since offset paging depends on it. Sets are ordered by
set_name, then by set_id. Cards are ordered by the leading number in card_number read as a
number (so 2 comes before 10), then by that leading part as text, then by card_id.
To walk everything, page until you have seen set_count (or card_count) rows:
offset=0
while :; do
page=$(curl -sS -w '\n%{http_code}' -H "Authorization: Bearer $PCG_API_KEY" \
"https://app.premiercardgrading.com/partner/v1/sets?limit=200&offset=$offset")
status=$(tail -n1 <<<"$page"); body=$(sed '$d' <<<"$page")
# Check the status first. An error response has no .sets, so ".sets | length" reads as 0, and a
# 429 or 500 would otherwise end the loop looking like a finished sync.
[ "$status" = 200 ] || { echo "HTTP $status. Retry, honouring Retry-After." >&2; exit 1; }
count=$(jq '.sets | length' <<<"$body")
[ "$count" -eq 0 ] && break
jq -c '.sets[]' <<<"$body"
offset=$((offset + count))
doneThe catalog changes while you page, because grading is continuous. For a full sync, prefer a single pass and accept that later pages are slightly fresher than earlier ones.
Rate limits
The limit is 600 requests per minute, counted separately for each key. The number itself is a single setting shared across all keys, so raising it for you raises it for everyone. Tell us if you need more and we will discuss it, but build against 600.
Every successful response tells you how much of the budget is left:
X-RateLimit-Remaining: 587When you go over, you get 429 with a Retry-After header in seconds. Wait that long before
retrying.
The limit is enforced per server instance, so you may get somewhat more than 600 before being throttled. Do not rely on that headroom. It is not a guarantee and it will tighten.
Writes (POST, PUT, PATCH, DELETE) do not count against the 600. They have their own budget
of 30 per minute shared by every key on the account, and webhook verify, test and replay calls
will have one of 6 per minute per account. These are per server instance as well, and /me
reports all three under limits. No partner write route is generally available yet.
Repeated failed authentication from one source is limited separately and more strictly. After a few
rejected keys in quick succession you will get 429 (rate_limited) instead of 401 until the
minute rolls over, and that applies even to a valid key sent from the same source in that window. You
will not hit this in normal use; it exists to absorb credential stuffing. If you are rotating a key,
deploy the new one before retrying rather than looping on the old one.
Errors
Failures return JSON with an error code and a human-readable message.
| Status | error |
What it means |
|---|---|---|
400 |
bad_request |
A parameter we could not read, for example a set id or cert number that is not a number |
401 |
invalid_credential |
Missing, malformed, unknown, or revoked key |
403 |
account_suspended |
Your key is valid, but the account is suspended. Contact us. |
403 |
insufficient_scope |
This key does not carry the scope the endpoint needs |
403 |
region_not_allowed |
The region is not granted to this account |
403 |
submissions_disabled |
The region is granted for reads only |
404 |
not_found |
No such set, the set has no graded cards, or no completed certification with that number |
413 |
payload_too_large |
A write body over 256 KiB |
429 |
rate_limited |
Over the rate limit. Honour Retry-After. |
500 |
internal_server_error |
A problem on our side. Retry, and tell us if it persists. |
503 |
submission_configuration_unavailable |
The office cannot take submissions right now |
503 |
intake_address_unavailable |
The office has no approved intake address right now |
{ "error": "invalid_credential", "message": "Provide a valid API key as: Authorization: Bearer <key>." }Those two fields are the whole body. Branch on error, show or log message, and do not expect
anything else.
There are three exceptions, all from the web framework rather than the API itself, and all reached only by a malformed request:
- A real path called with the wrong HTTP method (every endpoint here is
GET) is rejected during routing, before our code runs. You get a bare405with an empty body. The one exception is a write body over 256 KiB, which is refused with the enveloped413before routing. - An
Acceptheader we cannot satisfy gives a bare406with an empty body, because the response is always JSON. - A path that does not exist under
/partner/v1returns404in the platform's own format once your key is valid and carriesCATALOG_READ; a valid key without it gets403 insufficient_scope. Without a valid key it is401in the standard envelope, because authentication is checked before routing.
Send a GET with Accept: application/json to a documented path and you will only ever see the
standard envelope.
A revoked key and an unknown key both return invalid_credential. We do not confirm whether a key
ever existed.
Data notes
Honest answers to questions integrators ask:
categoryis oftennull. It is set for some games and not others. Where present it is the game's category (for exampleTCG), and it falls back to the set's own category when the game has none, so two sets in the same game can differ. It is not a sport. Do not make it a required filter or a primary grouping.yearis derived from a free-text release-date field. It is the first19xxor20xxin that text. Anything outside 1900 to 2099 reads asnull, as does text with no year. We would rather give you nothing than a guess. Theyearfilter is a plain string match against that derived value, so an unmatched value simply returns no results rather than an error.languageon the set is oftennullwhile the cards carry a value. Read language at the card level.distinct_cardscounts cards that have been graded, not the number of cards in the set. It is never larger than the set, and equal only when every card in the set has been graded.card_nameis the card's name, which for sports is the player. PCG also grades trading-card games, where "player" does not apply.finishis the closest field to what sports collectors call a parallel, andrarityandvariantsit alongside it.
Versioning
The path carries the major version. Within v1 we may add fields and endpoints, so parse defensively
and ignore anything you do not recognise. A change that would break a working integration, such as
removing or renaming a field or changing a type or a grade bucket, goes into v2, and we will tell
you before it ships.
Support
For questions, a higher rate limit, a leaked key, or anything in this document that does not match what the API does, contact your Premier Card Grading representative.