Claude 4.5 API: A Guide to Calculating and Optimizing Image Input Tokens
Hello!
In this article, we take a detailed look at how image token counts are calculated when using Claude 4.5 Sonnet/Haiku and Claude 4.1 Opus via the API.
How Image Token Counts Are Calculated
Images sent to the Claude 4.5 API are counted as tokens, just like text, and form the basis of billing. If an image is within the API's size limits and needs no resizing, you can estimate its token count with the following simple formula.
Basic formulatokens = (width px × height px) ÷ 750
Using this formula, you can predict cost before uploading and optimize images as needed. For example, a 1000×1000-pixel image consumes about 1,334 tokens, so under Claude 4.5's pricing you can calculate the cost per image in advance. A 1092×1092-pixel image (1.19 megapixels) comes to about 1,590 tokens, which you can also use as a baseline for estimating batch-processing costs.
Image Size Limits and Optimization
The Claude 4.5 API has several important limits on image size. A single API request can include up to 100 images, but the following constraints apply to their dimensions. As a baseline, images larger than 8000×8000 pixels are rejected, and when sending more than 20 images at once, the limit shrinks to 2000×2000 pixels. In addition, the total request size cannot exceed 32 MB.
From a performance standpoint, if an image's long edge exceeds 1,568 pixels, or if it exceeds roughly 1,600 tokens, the API automatically scales the image down while preserving its aspect ratio. This automatic resizing adds processing time, so resizing appropriately in advance can significantly improve response times.
Code Examples
1. Sending images as Base64
The most basic implementation encodes the image as Base64 and sends it to the API. This approach works well when sending local files or in-memory image data directly.
import base64
import anthropic
from pathlib import Path
client = anthropic.Anthropic(api_key="your-api-key")
# Encode the image to Base64
def encode_image(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# Estimate the token count in advance
def estimate_tokens(image_path):
from PIL import Image
img = Image.open(image_path)
width, height = img.size
tokens = (width * height) / 750
return int(tokens)
image_path = "sample.jpg"
image_data = encode_image(image_path)
estimated_tokens = estimate_tokens(image_path)
print(f"Estimated tokens: {estimated_tokens}")
# Request to the Claude 4.5 API
message = client.messages.create(
model="claude-3-opus-20240229", # Specify the Claude 4.5 model
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": image_data
}
},
{
"type": "text",
"text": "Please analyze this image in detail"
}
]
}
]
)
print(message.content)
2. Sending images by URL reference
When processing images hosted online, specifying a URL directly avoids the encoding overhead. This approach is especially efficient when processing large numbers of images or integrating with external services.
const Anthropic = require('@anthropic-ai/sdk');
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Estimate token count from an image URL (size info required in advance)
async function estimateTokensFromURL(imageUrl) {
// In a real implementation, fetch the image metadata
const assumedWidth = 1200;
const assumedHeight = 800;
return Math.floor((assumedWidth * assumedHeight) / 750);
}
async function analyzeImageFromURL() {
const imageUrl = "https://example.com/image.jpg";
const estimatedTokens = await estimateTokensFromURL(imageUrl);
console.log(`Estimated tokens: ${estimatedTokens}`);
const message = await anthropic.messages.create({
model: 'claude-3-opus-20240229', // Claude 4.5 model
max_tokens: 1024,
messages: [{
role: 'user',
content: [
{
type: 'image',
source: {
type: 'url',
url: imageUrl
}
},
{
type: 'text',
text: 'Identify the objects in the image and describe their layout'
}
]
}]
});
console.log(message.content);
}
analyzeImageFromURL();
3. Efficient multi-image processing and token optimization
When processing multiple images at once, you can optimize both cost and performance by tracking the total token count and resizing images as needed.
import anthropic
from PIL import Image
import io
import base64
class ImageTokenOptimizer:
def __init__(self, api_key):
self.client = anthropic.Anthropic(api_key=api_key)
self.max_dimension = 1568 # Recommended maximum dimension
self.target_megapixels = 1.15 # Optimal megapixel count
def optimize_image(self, image_path):
"""Resize the image to the optimal size to reduce token count"""
img = Image.open(image_path)
width, height = img.size
# Calculate the current token count
current_tokens = (width * height) / 750
# Check whether resizing is needed
if max(width, height) > self.max_dimension:
# Resize while preserving aspect ratio
ratio = self.max_dimension / max(width, height)
new_width = int(width * ratio)
new_height = int(height * ratio)
img = img.resize((new_width, new_height), Image.LANCZOS)
optimized_tokens = (new_width * new_height) / 750
print(f"Image resized: {width}x{height} → {new_width}x{new_height}")
print(f"Tokens reduced: {int(current_tokens)} → {int(optimized_tokens)}")
else:
optimized_tokens = current_tokens
print(f"No resize needed: tokens {int(optimized_tokens)}")
# Base64 encode
buffer = io.BytesIO()
img.save(buffer, format='JPEG', quality=95)
img_str = base64.b64encode(buffer.getvalue()).decode()
return img_str, int(optimized_tokens)
def batch_process_images(self, image_paths, prompt):
"""Batch-process multiple images"""
total_tokens = 0
image_contents = []
for path in image_paths:
img_data, tokens = self.optimize_image(path)
total_tokens += tokens
image_contents.append({
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": img_data
}
})
print(f"\nTotal estimated tokens: {total_tokens}")
# Add the text prompt
image_contents.append({
"type": "text",
"text": prompt
})
# Send the request to the Claude 4.5 API
message = self.client.messages.create(
model="claude-3-opus-20240229",
max_tokens=2048,
messages=[{
"role": "user",
"content": image_contents
}]
)
return message.content, total_tokens
# Usage example
optimizer = ImageTokenOptimizer(api_key="your-api-key")
image_files = ["image1.jpg", "image2.jpg", "image3.jpg"]
prompt = "Analyze the similarities and differences among these images"
result, total_tokens = optimizer.batch_process_images(image_files, prompt)
print(f"\nAnalysis result: {result}")
print(f"Tokens used: {total_tokens}")
Recommended Sizes by Aspect Ratio
When processing images of different aspect ratios, the following recommended sizes help achieve optimal token consumption: 1092×1092 pixels for square (1:1) images, 819×1456 pixels for portrait (9:16) images, and 1456×819 pixels for landscape (16:9) images. These sizes are designed to maximize image quality within the 1.15-megapixel limit while optimizing processing efficiency.
Best Practices
When doing image processing with the Claude 4.5 API, image quality comes first. Blurry or pixelated images reduce recognition accuracy, so sharp images are recommended. If an image contains text, make sure the text is large enough to be legible.
For cost optimization, check image dimensions before processing and resize as needed. When batch processing, we recommend calculating the total token count in advance and confirming the job fits your budget. For frequently used images, you can upload once via the Files API and reference the file multiple times, cutting the encoding overhead.
For performance tuning, pre-resizing images to 1,568 pixels or less avoids the API-side automatic resize and shortens response times. When processing multiple images, well-structured batching — rather than blind parallelism — lets you work efficiently while respecting API rate limits.
Recommended Image Sizes
For optimal performance, Claude 4.5 recommends resizing images to 1.15 megapixels or less (with both edges within 1,568 pixels).
By aspect ratio, the recommended sizes are 1092×1092 pixels for square 1:1 images, 951×1268 pixels for portrait 3:4 images, and 819×1456 pixels for wide 16:9 images.
Example 1: Sending an image with Base64 encoding
The most basic way to send an image is Base64 encoding. Because the image data is embedded directly in the request, no external hosting is needed, making this approach well suited to secure environments.
import anthropic
import base64
from PIL import Image
import io
# Resize the image and calculate tokens
def prepare_image(image_path, max_dimension=1568):
with Image.open(image_path) as img:
# Get the image size
width, height = img.size
# Calculate the token count
estimated_tokens = (width * height) / 750
print(f"Original image size: {width}x{height}px")
print(f"Estimated tokens: {estimated_tokens:.0f}")
# Resize if needed
if max(width, height) > max_dimension:
ratio = max_dimension / max(width, height)
new_width = int(width * ratio)
new_height = int(height * ratio)
img = img.resize((new_width, new_height), Image.LANCZOS)
# Recalculate tokens after resizing
new_tokens = (new_width * new_height) / 750
print(f"After resize: {new_width}x{new_height}px")
print(f"New token count: {new_tokens:.0f}")
# Base64 encode
buffered = io.BytesIO()
img.save(buffered, format="PNG")
return base64.b64encode(buffered.getvalue()).decode()
# Send to the Claude 4.5 API
client = anthropic.Anthropic(api_key="your-api-key")
image_base64 = prepare_image("sample.jpg")
response = client.messages.create(
model="claude-3-5-sonnet-20241022", # Model equivalent to Claude 4.5
max_tokens=1000,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/png",
"data": image_base64
}
},
{
"type": "text",
"text": "Please analyze the elements in this image in detail"
}
]
}
]
)
Example 2: Sending an image by URL reference
Using publicly hosted image URLs is an efficient technique that saves bandwidth when processing large numbers of images. Since the image data is not included in the request body, it is easier to stay within the request size limit.
const Anthropic = require('@anthropic-ai/sdk');
// Function to pre-calculate the token count
async function calculateImageTokens(imageUrl) {
// Fetch the image metadata (example implementation)
const response = await fetch(imageUrl, { method: 'HEAD' });
const contentLength = response.headers.get('content-length');
// Rough size estimate (in practice, analyze the image to get exact pixel counts)
const estimatedPixels = contentLength * 0.3; // approximate value
const estimatedTokens = estimatedPixels / 750;
console.log(`Image URL: ${imageUrl}`);
console.log(`Estimated tokens: ${Math.round(estimatedTokens)}`);
return estimatedTokens;
}
// Send multiple images to the Claude 4.5 API
async function analyzeMultipleImages() {
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const imageUrls = [
'https://example.com/image1.jpg',
'https://example.com/image2.jpg'
];
// Calculate the total token count
let totalTokens = 0;
for (const url of imageUrls) {
totalTokens += await calculateImageTokens(url);
}
console.log(`Total estimated tokens: ${Math.round(totalTokens)}`);
// Build the API request
const message = await anthropic.messages.create({
model: 'claude-3-5-sonnet-20241022',
max_tokens: 1500,
messages: [{
role: 'user',
content: [
{
type: 'text',
text: 'Compare and analyze these images'
},
...imageUrls.map(url => ({
type: 'image',
source: {
type: 'url',
url: url
}
}))
]
}]
});
return message;
}
Example 3: Optimizing with batch processing
When processing large numbers of images, combining batch processing with appropriate size adjustments optimizes both cost and performance.
import asyncio
import aiohttp
from typing import List, Dict
import numpy as np
from PIL import Image
import anthropic
class ImageTokenOptimizer:
def __init__(self, api_key: str):
self.client = anthropic.Anthropic(api_key=api_key)
self.token_budget = 10000 # Set the token budget
def calculate_optimal_size(self, original_width: int, original_height: int,
target_tokens: int = 1500) -> tuple:
"""Calculate the optimal image size based on a target token count"""
current_tokens = (original_width * original_height) / 750
if current_tokens <= target_tokens:
return original_width, original_height
# Calculate the scaling factor
scale_factor = np.sqrt(target_tokens * 750 / (original_width * original_height))
new_width = int(original_width * scale_factor)
new_height = int(original_height * scale_factor)
# Check the maximum size limit
max_dim = 1568
if max(new_width, new_height) > max_dim:
ratio = max_dim / max(new_width, new_height)
new_width = int(new_width * ratio)
new_height = int(new_height * ratio)
return new_width, new_height
async def process_image_batch(self, image_paths: List[str]) -> Dict:
"""Process a batch of images and optimize token usage"""
processed_images = []
total_tokens = 0
for path in image_paths:
with Image.open(path) as img:
width, height = img.size
# Calculate the optimal size
optimal_width, optimal_height = self.calculate_optimal_size(
width, height,
target_tokens=self.token_budget // len(image_paths)
)
# Resize
if (optimal_width, optimal_height) != (width, height):
img = img.resize((optimal_width, optimal_height), Image.LANCZOS)
# Record the token count
image_tokens = (optimal_width * optimal_height) / 750
total_tokens += image_tokens
processed_images.append({
'path': path,
'original_size': (width, height),
'optimized_size': (optimal_width, optimal_height),
'tokens': image_tokens,
'image': img
})
print(f"Batch processing complete:")
print(f" Images processed: {len(processed_images)}")
print(f" Total tokens: {total_tokens:.0f}")
print(f" Average tokens/image: {total_tokens/len(processed_images):.0f}")
return {
'images': processed_images,
'total_tokens': total_tokens,
'within_budget': total_tokens <= self.token_budget
}
async def send_optimized_batch(self, batch_data: Dict) -> str:
"""Send the optimized image batch to Claude 4.5"""
# Implement the actual API send logic here
# Base64-encode and send each image in batch_data['images']
pass
# Usage example
async def main():
optimizer = ImageTokenOptimizer(api_key="your-api-key")
image_paths = [
"image1.jpg",
"image2.jpg",
"image3.jpg"
]
# Batch processing and optimization
batch_result = await optimizer.process_image_batch(image_paths)
# Send the optimized images to the API
if batch_result['within_budget']:
response = await optimizer.send_optimized_batch(batch_result)
print("Batch sent successfully")
else:
print("Token budget exceeded. Split the images and process them separately")
# Run
asyncio.run(main())
Token and cost optimization, starting from the design of your LLM strategy.
Model selection, cost optimization, production implementation — adopting LLMs and generative AI involves many decision points beyond comparisons and calculations.
We develop and operate our own LLM products. From model selection to cost optimization and production implementation, our support is grounded in hands-on implementation experience — not armchair theory.
Explore our LLM adoption consulting →See you next time!
