Upload Card Images
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:
- ✅ API Authentication - Either a Personal Access Token (PAT) or OAuth credentials
- ✅ Card ID - The UUID of the card you want to add images to (get this from listing or creating cards)
- ✅ Image Files - Front and/or back images of your card ready to upload
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
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
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!")
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_uuidfield (reference to primary image) - Included images - Full CardImage resources with:
side- "front" or "back"image_url- CDN URL for the full-size imagevariants- Three thumbnail sizes (small, medium, large)widthandheight- Original dimensionsfile_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
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";
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
- Working with Images - Complete API reference for Card Images
- Responsive Card Images - Advanced display techniques
- CDN Integration - Performance optimization and caching
Build Something
- Building a Card Tracker - See images in a complete application
- Collection Management - Manage cards and images together
Reference
- API Endpoints - Complete Card Images endpoint documentation
- Code Examples - More code snippets in your preferred language
- Error Handling - Understanding API errors
Need Help?
- Support - Get help and report issues