Skip to main content

Upload Card Images

Image coverage is currently zero

Card images are uploaded by API consumers — no image library ships with the catalog — and no image data is currently populated. Every card's image_uuid is null today, and ?include=images returns no included resources. Read this page as a description of the interface, not of available content.

See data coverage for the canonical statement of what is and is not in the catalog.

Learn how to upload, manage, and display card images using the Trading Card API.

🎯 What You'll Learn​

By the end of this tutorial, you'll be able to:

  • Prepare and validate card images for upload
  • Upload front and back images for a trading card
  • Retrieve and verify uploaded images
  • Display images responsively with automatic thumbnails
  • Update or delete images when needed
  • Troubleshoot common upload errors

📋 Prerequisites​

Before you begin, make sure you have:

Quick Start

The fastest way to get started is with a Personal Access Token (PAT). You can generate one from your account dashboard and use it immediately without OAuth setup.

Step 1: Prepare Your Image​

Before uploading, ensure your images meet the requirements:

File Format​

Supported formats:

  • JPEG (.jpg, .jpeg) - Recommended for photos
  • PNG (.png) - Recommended for scans with transparency
  • WebP (.webp) - Modern format with excellent compression

Size Limits​

  • Maximum file size: 10 MB
  • Maximum dimensions: 4000 x 4000 pixels
  • Recommended dimensions: 600-1200 pixels on the longest side

Image Quality Tips​

For best results:

  • Use high-quality scans or photos (300+ DPI for scans)
  • Ensure good lighting with minimal glare or shadows
  • Center the card in the frame with minimal background
  • Keep the card straight and level (not tilted)
  • For photos, use a neutral background (white, gray, or black)

Front vs. Back Images​

You can upload one front and one back image per card:

  • Front (image_type: "front") - The main face of the card
  • Back (image_type: "back") - The reverse side
Unique Constraint

Each card can have only one front image and one back image. Uploading a new image of the same type will replace the existing one.

Step 2: Upload Front Image​

Now that your image is ready, let's upload it! Choose your preferred language below:

PHP​

<?php

require_once 'vendor/autoload.php';

use TradingCardAPI\Client;

// Initialize client with your PAT
$client = new Client([
'personalAccessToken' => getenv('TRADING_CARD_API_PAT'),
'baseUrl' => 'https://api.tradingcardapi.com'
]);

// Path to your card image
$imagePath = '/path/to/card-front.jpg';
$cardId = '550e8400-e29b-41d4-a716-446655440000';

try {
// Validate file exists
if (!file_exists($imagePath)) {
throw new Exception("Image file not found: {$imagePath}");
}

// Check file size (10MB limit)
$fileSize = filesize($imagePath);
if ($fileSize > 10 * 1024 * 1024) {
throw new Exception("File too large: " . round($fileSize / 1024 / 1024, 2) . "MB (max 10MB)");
}

// Upload the image
$response = $client->cardImages()->upload($imagePath, [
'card_id' => $cardId,
'image_type' => 'front'
]);

echo "✅ Front image uploaded successfully!\n";
echo "Image ID: " . $response['data']['id'] . "\n";
echo "Uploaded at: " . $response['data']['attributes']['created_at'] . "\n";
echo "File size: " . round($response['data']['attributes']['file_size'] / 1024, 2) . " KB\n";
echo "Dimensions: " . $response['data']['attributes']['width'] . "x" .
$response['data']['attributes']['height'] . "\n";

// Thumbnails are automatically generated
echo "\nThumbnails:\n";
foreach ($response['data']['attributes']['variants'] as $size => $variant) {
echo "- {$size}: {$variant['width']}x{$variant['height']} - {$variant['url']}\n";
}

} catch (Exception $e) {
echo "❌ Upload failed: " . $e->getMessage() . "\n";
}

JavaScript​

const TradingCardAPI = require('trading-card-api');
const fs = require('fs');
const FormData = require('form-data');

// Initialize client with your PAT
const client = new TradingCardAPI({
personalAccessToken: process.env.TRADING_CARD_API_PAT,
baseUrl: 'https://api.tradingcardapi.com'
});

const imagePath = '/path/to/card-front.jpg';
const cardId = '550e8400-e29b-41d4-a716-446655440000';

async function uploadFrontImage() {
try {
// Validate file exists
if (!fs.existsSync(imagePath)) {
throw new Error(`Image file not found: ${imagePath}`);
}

// Check file size (10MB limit)
const stats = fs.statSync(imagePath);
if (stats.size > 10 * 1024 * 1024) {
throw new Error(`File too large: ${(stats.size / 1024 / 1024).toFixed(2)}MB (max 10MB)`);
}

// Create form data
const formData = new FormData();
formData.append('file', fs.createReadStream(imagePath));
formData.append('data', JSON.stringify({
type: 'card_images',
attributes: {
card_id: cardId,
image_type: 'front'
}
}));

// Upload the image
const response = await client.cardImages.create(formData);

console.log('✅ Front image uploaded successfully!');
console.log(`Image ID: ${response.data.id}`);
console.log(`Uploaded at: ${response.data.attributes.created_at}`);
console.log(`File size: ${(response.data.attributes.file_size / 1024).toFixed(2)} KB`);
console.log(`Dimensions: ${response.data.attributes.width}x${response.data.attributes.height}`);

// Thumbnails are automatically generated
console.log('\nThumbnails:');
for (const [size, variant] of Object.entries(response.data.attributes.variants)) {
console.log(`- ${size}: ${variant.width}x${variant.height} - ${variant.url}`);
}

} catch (error) {
console.error('❌ Upload failed:', error.message);
}
}

uploadFrontImage();

Python​

import os
from trading_card_api import Client

# Initialize client with your PAT
client = Client(
personal_access_token=os.getenv('TRADING_CARD_API_PAT'),
base_url='https://api.tradingcardapi.com'
)

image_path = '/path/to/card-front.jpg'
card_id = '550e8400-e29b-41d4-a716-446655440000'

try:
# Validate file exists
if not os.path.exists(image_path):
raise FileNotFoundError(f"Image file not found: {image_path}")

# Check file size (10MB limit)
file_size = os.path.getsize(image_path)
if file_size > 10 * 1024 * 1024:
raise ValueError(f"File too large: {file_size / 1024 / 1024:.2f}MB (max 10MB)")

# Upload the image
with open(image_path, 'rb') as image_file:
response = client.card_images.upload(
file=image_file,
card_id=card_id,
image_type='front'
)

print("✅ Front image uploaded successfully!")
print(f"Image ID: {response['data']['id']}")
print(f"Uploaded at: {response['data']['attributes']['created_at']}")
print(f"File size: {response['data']['attributes']['file_size'] / 1024:.2f} KB")
print(f"Dimensions: {response['data']['attributes']['width']}x{response['data']['attributes']['height']}")

# Thumbnails are automatically generated
print("\nThumbnails:")
for size, variant in response['data']['attributes']['variants'].items():
print(f"- {size}: {variant['width']}x{variant['height']} - {variant['url']}")

except Exception as e:
print(f"❌ Upload failed: {str(e)}")

cURL​

#!/bin/bash

# Your API credentials
TRADING_CARD_API_PAT="your_pat_here"
IMAGE_PATH="/path/to/card-front.jpg"
CARD_ID="550e8400-e29b-41d4-a716-446655440000"

# Validate file exists
if [ ! -f "$IMAGE_PATH" ]; then
echo "❌ Image file not found: $IMAGE_PATH"
exit 1
fi

# Check file size (10MB = 10485760 bytes)
FILE_SIZE=$(stat -f%z "$IMAGE_PATH" 2>/dev/null || stat -c%s "$IMAGE_PATH" 2>/dev/null)
if [ "$FILE_SIZE" -gt 10485760 ]; then
echo "❌ File too large: $(($FILE_SIZE / 1024 / 1024))MB (max 10MB)"
exit 1
fi

# Upload the image
curl -X POST "https://api.tradingcardapi.com/v1/card-images" \
-H "Authorization: Bearer $TRADING_CARD_API_PAT" \
-F "file=@$IMAGE_PATH" \
-F "data={\"type\":\"card_images\",\"attributes\":{\"card_id\":\"$CARD_ID\",\"image_type\":\"front\"}}"

# The response will include:
# - Image ID and metadata
# - Automatically generated thumbnails in 3 sizes (small, medium, large)
# - CDN URLs with version parameters

✓ Verify Your Upload​

After uploading, you should see a JSON response containing:

  • Image ID - The unique identifier for your uploaded image
  • File metadata - Size, dimensions, format
  • Thumbnail variants - Automatically generated in small, medium, and large sizes
  • CDN URLs - Global delivery URLs with version parameters
Success!

If you see thumbnail URLs in the response, your image was uploaded successfully and thumbnails were auto-generated.

Step 3: Upload Back Image​

Uploading the back image follows the exact same process, just change image_type from "front" to "back":

Quick Example (PHP)​

<?php

$response = $client->cardImages()->upload('/path/to/card-back.jpg', [
'card_id' => $cardId,
'image_type' => 'back' // Changed from 'front' to 'back'
]);

echo "✅ Back image uploaded successfully!\n";
echo "Image ID: " . $response['data']['id'] . "\n";

Quick Example (JavaScript)​

// Change image_type to 'back'
formData.append('data', JSON.stringify({
type: 'card_images',
attributes: {
card_id: cardId,
image_type: 'back' // Changed from 'front' to 'back'
}
}));

const response = await client.cardImages.create(formData);
console.log('✅ Back image uploaded successfully!');

Quick Example (Python)​

# Change image_type to 'back'
response = client.card_images.upload(
file=open('/path/to/card-back.jpg', 'rb'),
card_id=card_id,
image_type='back' # Changed from 'front' to 'back'
)

print("✅ Back image uploaded successfully!")
One Per Type

Remember: Each card can have only one front and one back image. If you upload a new image with the same image_type, it will replace the existing one.

Step 4: Retrieve Image Information​

Now that your images are uploaded, let's retrieve the card with its images included:

Fetch Card with Images​

<?php

// Get the card with images included
$response = $client->cards()->get($cardId, [
'include' => 'images'
]);

$card = $response['data'];
echo "Card: " . $card['attributes']['name'] . "\n";
echo "Number: " . $card['attributes']['number'] . "\n";
echo "Image UUID: " . $card['attributes']['image_uuid'] . "\n\n";

// Access included images
if (!empty($response['included'])) {
foreach ($response['included'] as $included) {
if ($included['type'] === 'card_images') {
echo "Image: " . $included['attributes']['side'] . "\n";
echo "Dimensions: " . $included['attributes']['width'] . "x" .
$included['attributes']['height'] . "\n";
echo "CDN URL: " . $included['attributes']['image_url'] . "\n";

// Thumbnail variants
echo "Thumbnails:\n";
foreach ($included['attributes']['variants'] as $size => $variant) {
echo " - {$size}: {$variant['url']}\n";
}
echo "\n";
}
}
}

JavaScript Example​

// Get the card with images included
const response = await client.cards.get(cardId, {
'include': 'images'
});

const card = response.data;
console.log(`Card: ${card.attributes.name}`);
console.log(`Number: ${card.attributes.number}`);
console.log(`Image UUID: ${card.attributes.image_uuid}\n`);

// Access included images
if (response.included) {
response.included
.filter(item => item.type === 'card_images')
.forEach(image => {
console.log(`Image: ${image.attributes.side}`);
console.log(`Dimensions: ${image.attributes.width}x${image.attributes.height}`);
console.log(`CDN URL: ${image.attributes.image_url}`);

console.log('Thumbnails:');
for (const [size, variant] of Object.entries(image.attributes.variants)) {
console.log(` - ${size}: ${variant.url}`);
}
console.log('');
});
}

Python Example​

# Get the card with images included
response = client.cards.get(card_id, include='images')

card = response['data']
print(f"Card: {card['attributes']['name']}")
print(f"Number: {card['attributes']['number']}")
print(f"Image UUID: {card['attributes']['image_uuid']}\n")

# Access included images
if 'included' in response:
for included in response['included']:
if included['type'] == 'card_images':
print(f"Image: {included['attributes']['side']}")
print(f"Dimensions: {included['attributes']['width']}x{included['attributes']['height']}")
print(f"CDN URL: {included['attributes']['image_url']}")

print("Thumbnails:")
for size, variant in included['attributes']['variants'].items():
print(f" - {size}: {variant['url']}")
print()

Understanding the Response​

The response includes:

  • Card data - With image_uuid field (reference to primary image)
  • Included images - Full CardImage resources with:
    • side - "front" or "back"
    • image_url - CDN URL for the full-size image
    • variants - Three thumbnail sizes (small, medium, large)
    • width and height - Original dimensions
    • file_size - Size in bytes

Step 5: Display Images Responsively​

Use the automatically generated thumbnails for optimal performance:

Basic HTML Display​

<!-- Display card front image with responsive thumbnails -->
<img
src="https://cdn.tradingcardapi.com/images/abc123/medium.jpg?v=1234567890"
srcset="
https://cdn.tradingcardapi.com/images/abc123/small.jpg?v=1234567890 400w,
https://cdn.tradingcardapi.com/images/abc123/medium.jpg?v=1234567890 800w,
https://cdn.tradingcardapi.com/images/abc123/large.jpg?v=1234567890 1200w
"
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
alt="Ken Griffey Jr. - 1989 Upper Deck #1"
loading="lazy"
/>

Generate srcset from Response (PHP)​

<?php

function generateResponsiveImage($image, $cardName) {
$variants = $image['attributes']['variants'];

$srcset = implode(', ', [
$variants['small']['url'] . ' ' . $variants['small']['width'] . 'w',
$variants['medium']['url'] . ' ' . $variants['medium']['width'] . 'w',
$variants['large']['url'] . ' ' . $variants['large']['width'] . 'w'
]);

return sprintf(
'<img src="%s" srcset="%s" sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px" alt="%s" loading="lazy" />',
$variants['medium']['url'],
$srcset,
htmlspecialchars($cardName)
);
}

// Usage
foreach ($images as $image) {
echo generateResponsiveImage($image, $card['attributes']['name']);
}

React Component Example​

function CardImage({ image, cardName }) {
const { variants } = image.attributes;

return (
<img
src={variants.medium.url}
srcSet={`
${variants.small.url} ${variants.small.width}w,
${variants.medium.url} ${variants.medium.width}w,
${variants.large.url} ${variants.large.width}w
`}
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
alt={cardName}
loading="lazy"
/>
);
}

Why Use Responsive Images?​

  • Faster loading - Smaller files on mobile devices
  • Bandwidth savings - Serve appropriately sized images
  • Better UX - Quick display with lazy loading
  • CDN optimized - Global delivery from edge locations
Learn More

For advanced responsive image techniques, see the Responsive Card Images guide.

Step 6: Update or Delete Images​

Update Image Metadata​

You can update image metadata (like the side/type):

<?php

$imageId = '01234567-89ab-cdef-0123-456789abcdef';

$response = $client->cardImages()->update($imageId, [
'image_type' => 'back' // Change from front to back
]);

echo "Image updated successfully!\n";

Replace an Image​

To replace an image, simply upload a new one with the same image_type. The old image will be automatically replaced:

<?php

// This will replace the existing front image
$response = $client->cardImages()->upload('/path/to/new-front.jpg', [
'card_id' => $cardId,
'image_type' => 'front' // Same type = replacement
]);

echo "Front image replaced successfully!\n";

Delete an Image​

<?php

$imageId = '01234567-89ab-cdef-0123-456789abcdef';

$client->cardImages()->delete($imageId);

echo "Image deleted successfully!\n";
Permanent Deletion

Deleting an image is permanent and cannot be undone. The image files will be removed from the CDN and cannot be recovered.

Step 7: Common Errors & Troubleshooting​

File Too Large​

Error: 422 Unprocessable Entity - File size exceeds maximum

Solution:

  • Resize your image to meet the 10MB limit
  • Compress the image using tools like ImageOptim or TinyPNG
  • Convert to WebP format for better compression
# Example: Resize with ImageMagick
convert input.jpg -resize 1200x1200\> -quality 85 output.jpg

Invalid Dimensions​

Error: 422 Unprocessable Entity - Image dimensions exceed maximum

Solution:

  • Images must be 4000x4000 pixels or smaller
  • Resize the image to fit within these dimensions
# Example: Resize to max 4000px
convert input.jpg -resize 4000x4000\> output.jpg

Wrong MIME Type​

Error: 422 Unprocessable Entity - Invalid file type

Solution:

  • Ensure file is JPEG, PNG, or WebP
  • Check file extension matches actual format
  • Re-save the image in a supported format

Duplicate Image Type​

Error: 422 Unprocessable Entity - Card already has a front/back image

Solution: This isn't actually an error! When you upload a new image with the same image_type, it automatically replaces the existing one. If you see this error, it means you're using an old API version. Update to v0.7.0 or later.

Authentication Errors​

Error: 401 Unauthorized - Invalid or missing authentication token

Solution:

  • Verify your PAT or OAuth token is correct
  • Check that the token hasn't expired
  • Ensure the Authorization: Bearer <token> header is included
<?php

// Verify your client is initialized correctly
$client = new Client([
'personalAccessToken' => getenv('TRADING_CARD_API_PAT'), // Check this value
'baseUrl' => 'https://api.tradingcardapi.com'
]);

Card Not Found​

Error: 404 Not Found - Card not found

Solution:

  • Verify the card ID is correct
  • Ensure the card exists in your account
  • Check that you have permission to access the card

Network/CDN Issues​

Error: 503 Service Unavailable or timeout errors

Solution:

  • Check your internet connection
  • Check the Support page for known service issues
  • Retry the request after a brief delay
  • For large files, increase your timeout settings

Next Steps​

Congratulations! You've learned how to upload, retrieve, and display card images. Here's where to go next:

Go Deeper​

Build Something​

Reference​

Need Help?​

  • Support - Get help and report issues