Querying Conventions
Which query parameters each resource accepts, why some are bare and some are bracketed, how to search on a partial name, and the one failure mode that costs new integrators the most time.
Why this page exists​
The per-endpoint tables in the API reference list the parameters each endpoint accepts, but they cannot tell you the things that only become obvious once you have called two endpoints and noticed they disagree. The parameter names are not uniform across resources, partial-name search needs a prefix nobody guesses, and an unrecognised parameter is ignored rather than rejected — so a typo returns a plausible-looking answer instead of an error.
This page states those conventions in one place.
Parameters by resource​
There is no single filtering grammar across the API. Some filters are bare parameters, some are bracketed under filter[...], and pagination is not spelled the same way everywhere. Copy the row you need rather than generalising from another resource.
/v1/players​
| Parameter | Shape | Notes |
|---|---|---|
first_name | bare | Filter by first name |
last_name | bare | Filter by last name |
full_name | bare | Searches across both first and last name |
limit | bare | Page size, max 100, default 25 |
include | bare | Comma-separated relationships |
Players have no filter[...] parameters at all, and they paginate on limit — not per_page.
/v1/sets​
| Parameter | Shape | Notes |
|---|---|---|
name | bare | Filter by set name |
genre | bare | A genre UUID, not a genre name |
year | bare | A year UUID, not a literal year like 1989 |
parent_id | bare | Returns the child sets of the given parent |
scope | bare | root or all. root lists only sets without a parent; all lists every set. Defaults to root, except that a name query with no scope searches every set for backward compatibility — pass scope=root to keep a name search root-only. Ignored when parent_id is supplied |
filter[status] | bracketed | published or draft |
per_page | bare | Page size, max 100, default 25 |
limit | bare | Deprecated alias for per_page — prefer per_page |
order_by | bare | One of name, created_at, updated_at, card_count |
sort | bare | asc or desc |
include | bare | Comma-separated relationships |
Sets are the only resource that mixes both shapes: everything is bare except filter[status].
year and genre take UUIDs?year=1989 does not filter to 1989. The year parameter expects the UUID of a year record, so a literal year is silently ignored and you get an unfiltered page back. Resolve the UUID from /v1/years (and /v1/genres) first, then pass it.
/v1/cards​
| Parameter | Shape | Notes |
|---|---|---|
filter[set_id] | bracketed | Set UUID |
filter[player_id] | bracketed | Player UUID — every card the player appears on, directly or via a player-team |
page | bare | Page number |
per_page | bare | Page size, max 100, default 25 |
include | bare | One or more of set, oncard, attributes, children |
Cards are the mirror image of players: both filters are bracketed, and pagination is per_page / page — not limit, and not JSON:API's page[limit] / page[number].
Cards accept no order_by or sort. Ordering is a /v1/sets feature only.
/v1/sets/{set}/checklist​
| Parameter | Shape | Notes |
|---|---|---|
format | bare | full (default) or compact |
page | bare | Page number, starts at 1 |
per_page | bare | Page size, max 100. Omit it and the whole checklist is returned |
The checklist response has an unusual shape — cards arrive in the included array rather than nested under data. That is covered in Build a rainbow.
/v2/sets/{set}/checklist​
| Parameter | Shape | Notes |
|---|---|---|
format | bare | full (default) or compact |
page | bare | Page number, starts at 1 |
per_page | bare | Page size, max 100. Omit it and the whole checklist is returned |
include | bare | Comma-separated, one or more of set, oncard, attributes. Defaults to set |
The /v2 variant takes the same format, page and per_page parameters as /v1, but inverts the response shape: cards are the primary data array and the set is reachable through include=set. Unlike /v1, this endpoint does accept include.
Quick reference​
| Resource | Filters | Page size | Page number |
|---|---|---|---|
/v1/players | bare | limit | — |
/v1/sets | bare, plus filter[status] | per_page | — |
/v1/cards | bracketed | per_page | page |
/v1/sets/{set}/checklist | — | per_page | page |
/v2/sets/{set}/checklist | — | per_page | page |
Partial matching with the like: prefix​
String filters are exact and case-insensitive by default. This is the single most common reason a search that should obviously match returns nothing:
# Exact match — returns 0 rows, because no set is named exactly "Prizm"
curl "https://api.tradingcardapi.com/v1/sets?name=Prizm" \
-H "Authorization: Bearer YOUR_TOKEN"
For a partial match, prefix the value with like::
# Partial match — returns every set whose name contains "Prizm"
curl "https://api.tradingcardapi.com/v1/sets?name=like:Prizm" \
-H "Authorization: Bearer YOUR_TOKEN"
The prefix goes on the value, not the parameter name. It is declared on three filters — the name parameter of /v1/sets, /v1/attributes and /v1/genres:
curl "https://api.tradingcardapi.com/v1/attributes?name=like:Auto" \
-H "Authorization: Bearer YOUR_TOKEN"
It does not apply to string filters generally. The /v1/players and /v1/teams name filters use a different mechanism and are exact-match only, so ?last_name=like:Griff is an exact search for the literal string like:Griff and returns nothing. Pass the real value on those endpoints.
Note the difference from a wildcard syntax: there are no * characters. ?name=*Prizm* is not a partial-match expression — it is an exact search for a name that literally contains asterisks, and it returns nothing.
like: is declared per-parameter in the published OpenAPI spec, on the name parameter of /v1/sets, /v1/attributes and /v1/genres — the API reference carries the exact wording for each. If a filter's reference entry does not mention the prefix, it is not supported there and the value is matched exactly.
Inside a like: value, % and _ are matched literally rather than acting as SQL wildcards. ?name=like:% therefore searches for names containing a percent sign — it is not a way to list everything. To enumerate the whole set catalog, use ?scope=all instead.
The default /v1/sets listing shows root sets only​
With neither name nor parent_id supplied, /v1/sets returns only sets whose parent_id is null — the root sets. Every parallel, insert, autograph subset and variation is a child of some root set, so none of them appear in the default listing.
This matters more than it sounds. The default listing is a few hundred rows, heavily weighted toward older base sets, and reading it as "the catalog" understates both the depth and the modern coverage of the data by a wide margin. What you are seeing is the top level of a tree, not the tree.
To walk down a level, pass the parent's id:
# 1. Find the base set
curl "https://api.tradingcardapi.com/v1/sets?name=like:Prizm&per_page=25" \
-H "Authorization: Bearer YOUR_TOKEN"
# 2. List its children — parallels, inserts, autograph subsets, variations
curl "https://api.tradingcardapi.com/v1/sets?parent_id=BASE_SET_UUID&per_page=100" \
-H "Authorization: Bearer YOUR_TOKEN"
To list the whole catalog in one pass — root sets and every child alongside them — pass scope=all:
# Every set, not just the root sets
curl "https://api.tradingcardapi.com/v1/sets?scope=all&per_page=100" \
-H "Authorization: Bearer YOUR_TOKEN"
scope accepts root or all, and is ignored when parent_id is supplied — that parameter always returns the given parent's children.
name searches are not root-only by defaultroot is the default for a plain listing, but a name query with no scope searches every set, not just root sets — the API keeps that behaviour for backward compatibility, so a name search does not silently miss parallels and inserts. Pass scope=root explicitly when you want a name search confined to root sets.
The response tells you which scope it applied​
Every /v1/sets listing carries a meta.scope object alongside meta.pagination, so the scope behind a total never has to be inferred:
{
"meta": {
"pagination": { "total": 273, "per_page": 25 },
"scope": {
"applied": "root_sets_only",
"description": "Only sets without a parent are listed, so parallels, inserts, autograph subsets and variations are omitted. This is a default scope, not a limit on your account. Add ?scope=all to list every set, or ?parent_id={id} to list one set's children."
}
}
}
applied is one of:
| Value | Means |
|---|---|
root_sets_only | The default listing — sets with no parent |
all_sets | scope=all was supplied |
child_sets_of_parent | parent_id was supplied |
description says the same thing in prose, including how to widen the scope. Read meta.scope.applied before drawing a conclusion from meta.pagination.total: under the default scope that total counts root sets, not the catalog, and it is not a cap on what your account can see.
Each child carries is_parallel, is_insert, is_autograph and is_variation so you can tell what kind of release it is. There is no filter parameter for those flags — read them off the child rows you get back. See Set Types for what each flag means, and Build a rainbow for the full walk.
Finding a player's cards​
Two steps: a bare-filter lookup against /v1/players, then a bracketed-filter call against /v1/cards. The player name filters are exact-match only — like: is not supported on them — so pass the name as it is stored:
# 1. Look the player up by name — exact match, so pass the whole name
curl "https://api.tradingcardapi.com/v1/players?full_name=Ken%20Griffey%20Jr." \
-H "Authorization: Bearer YOUR_TOKEN"
# 2. Fetch their cards
curl "https://api.tradingcardapi.com/v1/cards?filter[player_id]=PLAYER_UUID&per_page=25" \
-H "Authorization: Bearer YOUR_TOKEN"
filter[player_id] returns every card the player appears on, both directly and through a player-team relationship.
per_page small on player lookupsPlayer-card lookups are the slowest read on the API today, and the cost scales with page size. Start with a small per_page and increase it only if the latency is acceptable for your use case.
Unknown parameters are ignored, not rejected​
This is the one to internalise. If you send a parameter an endpoint does not recognise, the API does not return a 400 — it drops the parameter and answers the request as though you had not sent it.
# docs-lint: intentional-invalid-parameter
# filter[year] is not a parameter on /v1/cards.
# This does not error. It returns the first page of ALL cards.
curl "https://api.tradingcardapi.com/v1/cards?filter[year]=1989" \
-H "Authorization: Bearer YOUR_TOKEN"
You get 200 OK, a well-formed JSON:API body and a page of real cards — which is exactly what a correctly-filtered response looks like. Nothing in the response says the filter was discarded.
Practical defences:
- Check the shape of the result, not just the status code. If a filter that should narrow the set returns a suspiciously round number of rows, or
meta.totalmatches the unfiltered total, the filter did not apply. - Sanity-check a new filter against a value you know is rare. If the count does not change, the parameter name is wrong.
- Copy parameter names from this page or the API reference rather than inferring them from another resource — the naming genuinely is not uniform, and a name that works on
/v1/cardsmay be silently inert on/v1/players.
A related consequence: because unrecognised parameters cost nothing, a stale integration written against a guessed parameter name can run for months returning unfiltered data without ever raising an error.
Next steps​
- Build a rainbow — walk a base set's children and join a checklist end to end
- Set Types — what
is_parallel,is_insert,is_variationandis_autographmean - API Reference — the generated per-endpoint parameter tables
- Making Your First Request — response structure and error handling