Skip to main content

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​

ParameterShapeNotes
first_namebareFilter by first name
last_namebareFilter by last name
full_namebareSearches across both first and last name
limitbarePage size, max 100, default 25
includebareComma-separated relationships

Players have no filter[...] parameters at all, and they paginate on limit — not per_page.

/v1/sets​

ParameterShapeNotes
namebareFilter by set name
genrebareA genre UUID, not a genre name
yearbareA year UUID, not a literal year like 1989
parent_idbareReturns the child sets of the given parent
scopebareroot 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]bracketedpublished or draft
per_pagebarePage size, max 100, default 25
limitbareDeprecated alias for per_page — prefer per_page
order_bybareOne of name, created_at, updated_at, card_count
sortbareasc or desc
includebareComma-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​

ParameterShapeNotes
filter[set_id]bracketedSet UUID
filter[player_id]bracketedPlayer UUID — every card the player appears on, directly or via a player-team
pagebarePage number
per_pagebarePage size, max 100, default 25
includebareOne 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​

ParameterShapeNotes
formatbarefull (default) or compact
pagebarePage number, starts at 1
per_pagebarePage 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​

ParameterShapeNotes
formatbarefull (default) or compact
pagebarePage number, starts at 1
per_pagebarePage size, max 100. Omit it and the whole checklist is returned
includebareComma-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​

ResourceFiltersPage sizePage number
/v1/playersbarelimit—
/v1/setsbare, plus filter[status]per_page—
/v1/cardsbracketedper_pagepage
/v1/sets/{set}/checklist—per_pagepage
/v2/sets/{set}/checklist—per_pagepage

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.

The prefix is declared in the spec

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 default

root 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:

ValueMeans
root_sets_onlyThe default listing — sets with no parent
all_setsscope=all was supplied
child_sets_of_parentparent_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.

Keep per_page small on player lookups

Player-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.total matches 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/cards may 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​