Skip to main content

Making Your First Request

Now that you have authentication set up, let's make your first API call to the Trading Card API. This guide will walk you through fetching card data and understanding the response structure.

๐ŸŽฏ What You'll Learnโ€‹

  • How to structure API requests
  • Understanding JSON:API response format
  • Working with pagination
  • Basic filtering and sorting
  • Error handling

๐Ÿš€ Quick Testโ€‹

Let's start with a simple request to fetch cards:

curl -X GET "https://api.tradingcardapi.com/v1/cards?per_page=5" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/vnd.api+json" \
-H "Content-Type: application/vnd.api+json"
Replace YOUR_ACCESS_TOKEN

Make sure to replace YOUR_ACCESS_TOKEN with the actual token you received in the authentication step.

Parameter names are not uniform across resources

/v1/cards paginates on per_page and takes bracketed filters; /v1/players paginates on limit and takes bare filters. Worse, an unrecognised parameter is ignored rather than rejected, so a wrong name returns a normal-looking unfiltered page instead of an error. Querying Conventions has the per-resource reference โ€” read it before you guess a parameter name.

๐Ÿ“Š Understanding the Responseโ€‹

Basic Response Structureโ€‹

All API responses follow the JSON:API specification:

{
"data": [
{
"type": "cards",
"id": "01234567-89ab-cdef-0123-456789abcdef",
"attributes": {
"name": "1989 Topps Ken Griffey Jr. #336",
"number": "336",
"serial_number": null,
"image_uuid": "550e8400-e29b-41d4-a716-446655440000",
"title": "Rookie Card",
"notes": null,
"has_player": true,
"has_team": false,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"url": "https://api.tradingcardapi.com/v1/cards/01234567-89ab-cdef-0123-456789abcdef"
},
"relationships": {
"set": {
"data": {
"type": "sets",
"id": "fedcba98-7654-3210-fedc-ba9876543210"
}
},
"oncard": {
"data": [
{
"type": "players",
"id": "abcdef01-2345-6789-abcd-ef0123456789"
}
]
},
"attributes": {
"data": []
}
},
"links": {
"self": "https://api.tradingcardapi.com/v1/cards/01234567-89ab-cdef-0123-456789abcdef"
}
}
],
"meta": {
"total": 1247831,
"per_page": 5,
"current_page": 1,
"last_page": 249567,
"from": 1,
"to": 5
},
"links": {
"first": "https://api.tradingcardapi.com/v1/cards?per_page=5&page=1",
"last": "https://api.tradingcardapi.com/v1/cards?per_page=5&page=249567",
"next": "https://api.tradingcardapi.com/v1/cards?per_page=5&page=2"
}
}

Response Componentsโ€‹

ComponentDescription
dataArray of resource objects (cards in this case)
metaMetadata about the response (pagination info, totals)
linksNavigation links for pagination
includedRelated resources when using includes

๐Ÿ” Exploring Card Dataโ€‹

Card Attributesโ€‹

Each card has these primary attributes:

{
"name": "1989 Topps Ken Griffey Jr. #336",
"number": "336",
"serial_number": null,
"image_uuid": "550e8400-e29b-41d4-a716-446655440000",
"title": "Rookie Card",
"notes": null,
"has_player": true,
"has_team": false,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z",
"url": "https://api.tradingcardapi.com/v1/cards/01234567-89ab-cdef-0123-456789abcdef"
}
AttributeTypeDescription
namestringAuto-generated card name
numberstringCard number within the set
serial_numberstring/nullSerial number for limited cards
image_uuidstring/nullUUID reference for the card image
titlestring/nullSpecial title or designation, e.g. "Rookie Card"
notesstring/nullAdditional notes about the card
has_playerbooleanTrue if the card has an associated player or player/team
has_teambooleanTrue if the card has an associated team or player/team
created_atstringCreation timestamp (ISO 8601)
updated_atstringLast update timestamp (ISO 8601)
urlstringAPI URL for this card resource

That is the complete attribute set โ€” there are no others. If you are modelling a card in your own code, model those eleven fields.

has_player / has_team are only computed in bulk

Both flags are always present on a card response, but they are only computed by the set checklist endpoint. Everywhere else they are left at their false default, so outside the checklist a false means "not computed" rather than "no relationship". Read the oncard relationship instead when you need a definitive answer.

Where is the card's year?

There isn't one. Release year belongs to the set, not the card, and it is a relationship rather than an attribute โ€” fetch it with GET /v1/sets/{id}?include=year and read it off the included years resource. Note that the Year resource's own year attribute is a string ("1989"), not an integer.

curl -X GET "https://api.tradingcardapi.com/v1/sets/SET_UUID?include=year" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/vnd.api+json"

The same applies to a card: resolve the card's set relationship first, then include year on that set. See Data Models for the generated per-resource reference.

Card Relationshipsโ€‹

Cards relate to other resources:

  • set: The card set this card belongs to
  • oncard: Entities that appear on the card (players, teams, etc.)
  • attributes: Special attributes (autographs, variations, etc.)

Use the include parameter to fetch related data in one request:

curl -X GET "https://api.tradingcardapi.com/v1/cards?include=set,oncard&per_page=5" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/vnd.api+json"

On /v1/cards, include accepts set, oncard, attributes and children.

This returns the card data plus related sets and oncard entities in the included section:

{
"data": [...],
"included": [
{
"type": "sets",
"id": "fedcba98-7654-3210-fedc-ba9876543210",
"attributes": {
"name": "1989 Topps Baseball",
"title": "Series 1",
"description": "Complete baseball set from Topps",
"card_count": 792,
"current_card_count": 792
}
},
{
"type": "players",
"id": "abcdef01-2345-6789-abcd-ef0123456789",
"attributes": {
"name": "Ken Griffey Jr.",
"first_name": "Ken",
"last_name": "Griffey Jr.",
"position": "Outfield"
}
}
]
}

card_count is the set's declared size; current_card_count is how many cards the API actually holds for it. The set's release year is not here โ€” it is a separate relationship, so add year to the include list on a /v1/sets request to get it.

๐ŸŽ›๏ธ Filtering and Sortingโ€‹

Basic Filteringโ€‹

/v1/cards accepts exactly two filters, both bracketed โ€” by set and by player:

# Every card in a set
curl -X GET "https://api.tradingcardapi.com/v1/cards?filter[set_id]=SET_UUID" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/vnd.api+json"

# Every card a player appears on
curl -X GET "https://api.tradingcardapi.com/v1/cards?filter[player_id]=PLAYER_UUID" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/vnd.api+json"

Filtering by name or year happens on the resources that own those concepts, and those filters are bare, not bracketed:

# Find a set by partial name โ€” note the `like:` prefix on the value
curl -X GET "https://api.tradingcardapi.com/v1/sets?name=like:Prizm" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/vnd.api+json"

# Find a player by name
curl -X GET "https://api.tradingcardapi.com/v1/players?full_name=like:Griffey" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/vnd.api+json"

Two conventions worth committing to memory now:

  • String filters are exact by default. ?name=Prizm matches only a set whose name is exactly "Prizm". Prefix the value with like: for a partial match. There are no * wildcards.
  • ?year= on /v1/sets takes a year UUID, not a literal year โ€” resolve it from /v1/years first. The same is true of genre.

See Querying Conventions for the full per-resource reference.

Sortingโ€‹

Ordering is a /v1/sets feature. /v1/cards and /v1/players accept no sort parameters at all โ€” sending sort to either is silently ignored:

# Sets ordered by card count, largest first
curl -X GET "https://api.tradingcardapi.com/v1/sets?order_by=card_count&sort=desc" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/vnd.api+json"

# Sets ordered by name, A-Z (the default)
curl -X GET "https://api.tradingcardapi.com/v1/sets?order_by=name&sort=asc" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/vnd.api+json"

order_by accepts name, created_at, updated_at or card_count; sort accepts asc or desc. There is no -field prefix syntax and no multi-field sorting.

๐Ÿ“„ Paginationโ€‹

Basic Paginationโ€‹

Control how many results you get. On /v1/cards the parameters are per_page and page:

# Get 25 cards per page
curl -X GET "https://api.tradingcardapi.com/v1/cards?per_page=25" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/vnd.api+json"

# Get page 3 with 10 cards per page
curl -X GET "https://api.tradingcardapi.com/v1/cards?per_page=10&page=3" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Accept: application/vnd.api+json"

per_page is capped at 100 and defaults to 25.

/v1/players uses limit, not per_page

Page size is spelled differently per resource: /v1/cards and /v1/sets take per_page, while /v1/players takes limit (also capped at 100). JSON:API's page[limit] / page[number] form is not accepted anywhere โ€” and because unknown parameters are ignored rather than rejected, sending it silently gives you the default page size.

The response includes navigation links:

{
"links": {
"first": "https://api.tradingcardapi.com/v1/cards?per_page=10&page=1",
"prev": "https://api.tradingcardapi.com/v1/cards?per_page=10&page=2",
"next": "https://api.tradingcardapi.com/v1/cards?per_page=10&page=4",
"last": "https://api.tradingcardapi.com/v1/cards?per_page=10&page=124784"
}
}

๐Ÿ’ป Code Examplesโ€‹

PHPโ€‹

<?php

// Assuming you have the TradingCardAPI class from the authentication guide

$api = new TradingCardAPI('your_client_id', 'your_client_secret');
$api->authenticate();

// Get a page of cards
$recentCards = $api->getCards([
'per_page' => 10
]);

// Get every card in a set, with set information included
$setCards = $api->getCards([
'filter[set_id]' => $setUuid,
'include' => 'set',
'per_page' => 25
]);

// Search for Ken Griffey Jr. cards: resolve the player, then filter by id
$players = $api->getPlayers(['full_name' => 'like:Griffey']);
$playerId = $players['data'][0]['id'];

$griffeyCards = $api->getCards([
'filter[player_id]' => $playerId,
'include' => 'set,oncard',
'per_page' => 25
]);

foreach ($griffeyCards['data'] as $card) {
echo "Card: " . $card['attributes']['name'] . "\n";
echo "Number: " . $card['attributes']['number'] . "\n";
// Release year lives on the set, not the card โ€” include `year` on a
// /v1/sets request to resolve it.
echo "Title: " . ($card['attributes']['title'] ?? '-') . "\n\n";
}

JavaScriptโ€‹

// Assuming you have the TradingCardAPI class from the authentication guide

const api = new TradingCardAPI('your_client_id', 'your_client_secret');
await api.authenticate();

// Get a page of cards
const recentCards = await api.getCards({
'per_page': 10
});

// Get every card in a set, with set information included
const setCards = await api.getCards({
'filter[set_id]': setUuid,
'include': 'set',
'per_page': 25
});

// Search for Ken Griffey Jr. cards: resolve the player, then filter by id
const players = await api.getPlayers({ 'full_name': 'like:Griffey' });
const playerId = players.data[0].id;

const griffeyCards = await api.getCards({
'filter[player_id]': playerId,
'include': 'set,oncard',
'per_page': 25
});

griffeyCards.data.forEach(card => {
console.log(`Card: ${card.attributes.name}`);
console.log(`Number: ${card.attributes.number}`);
// Release year lives on the set, not the card โ€” include `year` on a
// /v1/sets request to resolve it.
console.log(`Title: ${card.attributes.title ?? '-'}\n`);
});

Pythonโ€‹

# Assuming you have the TradingCardAPI class from the authentication guide

api = TradingCardAPI('your_client_id', 'your_client_secret')
api.authenticate()

# Get a page of cards
recent_cards = api.get_cards({
'per_page': 10
})

# Get every card in a set, with set information included
set_cards = api.get_cards({
'filter[set_id]': set_uuid,
'include': 'set',
'per_page': 25
})

# Search for Ken Griffey Jr. cards: resolve the player, then filter by id
players = api.get_players({'full_name': 'like:Griffey'})
player_id = players['data'][0]['id']

griffey_cards = api.get_cards({
'filter[player_id]': player_id,
'include': 'set,oncard',
'per_page': 25
})

for card in griffey_cards['data']:
print(f"Card: {card['attributes']['name']}")
print(f"Number: {card['attributes']['number']}")
# Release year lives on the set, not the card โ€” include `year` on a
# /v1/sets request to resolve it.
print(f"Title: {card['attributes']['title'] or '-'}\n")

โŒ Error Handlingโ€‹

Common Error Responsesโ€‹

Errors follow the JSON:API error shape:

{
"errors": [
{
"status": "404",
"title": "Not Found",
"detail": "No card matches the given identifier"
}
]
}
An unknown query parameter is not an error

This is the most expensive thing to learn the hard way. Sending a parameter an endpoint does not recognise does not produce a 400 โ€” the parameter is dropped and the request is answered as though you never sent it. You get 200 OK, a well-formed body, and a page of real data that looks exactly like a correctly-filtered response.

So a typo'd or guessed filter fails silently, and an integration built on one can run for months returning unfiltered data. Verify a new filter actually narrows the result โ€” compare meta.total against the unfiltered call โ€” rather than trusting the status code. Querying Conventions covers this in full.

Handling Errors in Codeโ€‹

try {
const cards = await api.getCards({ 'filter[set_id]': setUuid });
} catch (error) {
if (error.response && error.response.data.errors) {
error.response.data.errors.forEach(err => {
console.error(`Error ${err.status}: ${err.detail}`);
});
}
}

๐Ÿงช Testing Your Requestsโ€‹

Using the API Explorerโ€‹

Visit our interactive API explorer to test requests without writing code:

  1. Navigate to the Cards endpoint
  2. Add parameters like filtering and sorting
  3. Execute the request and see the response
  4. Copy the generated code to your application

Common Test Scenariosโ€‹

# 1. Basic connectivity test
curl -X GET "https://api.tradingcardapi.com/v1/cards?per_page=1"

# 2. Test with authentication
curl -X GET "https://api.tradingcardapi.com/v1/cards?per_page=1" \
-H "Authorization: Bearer YOUR_TOKEN"

# 3. Test filtering โ€” compare meta.total against the unfiltered call above
curl -X GET "https://api.tradingcardapi.com/v1/cards?filter[set_id]=SET_UUID&per_page=5" \
-H "Authorization: Bearer YOUR_TOKEN"

# 4. Test includes
curl -X GET "https://api.tradingcardapi.com/v1/cards?include=set&per_page=3" \
-H "Authorization: Bearer YOUR_TOKEN"

๐ŸŽฏ Next Stepsโ€‹

Great! You've made your first API request. Now explore:

  1. Querying conventions โ†’ - Per-resource parameters, partial matching, and the pitfalls above
  2. Build a rainbow โ†’ - Walk a set's parallels end to end
  3. Code examples โ†’ - Learn typical API usage patterns
  4. API documentation โ†’ - Deep dive into available endpoints
  5. Build a card tracker โ†’ - Complete tutorial
  6. Explore all endpoints โ†’ - Full API reference

๐Ÿ’ก Pro Tipsโ€‹

  • Start small: Begin with simple requests and gradually add complexity
  • Use includes: Fetch related data in single requests to improve performance
  • Cache responses: Store frequently accessed data to reduce API calls
  • Monitor rate limits: Keep track of your usage to avoid throttling
Rate Limiting

Learn about API rate limits, response headers, and best practices in our Rate Limits Guide.

Ready to build something amazing? Let's explore code examples!