Uploading Card Images: Step-by-Step Tutorial
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
- Verify the API is operational (https://status.tradingcardapi.com)
- 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
- Community - Connect with other developers
- API Status - Check https://status.tradingcardapi.com for service status