Skip to main content

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:

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
  • 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

Build Something

Reference

Need Help?