Send Image Inputs

Su Suriya Ravichandran Updated 10 Nov 2026
6 min read ·Lesson 114 of 200

Claude Can See Images

Claude is a multimodal model: in the same request, it can read text and look at images. You can ask it to describe a photo, read a handwritten note, compare two screenshots, or check whether a chart matches its caption. The image is not converted into text first. Claude reads it directly, which is why images are useful for tasks such as document understanding, quality checks, and accessibility descriptions.

Images are sent as content blocks in a user message (Lesson 16), alongside text blocks:

{
    "role": "user",
    "content": [
        {"type": "image", "source": {...}},           # The image
        {"type": "text", "text": "What is in this?"}  # The question
    ],
}

Lesson 111 introduces the basic pattern. Lessons 112 and 113 cover the two most common ways to supply an image: by URL and as base64 data. Lesson 116 covers the Files API, which lets you upload an image once and reuse it.


Supported Formats and Sizes

According to the current vision documentation:

AspectCurrent guidance
FormatsJPEG, PNG, GIF, and WebP
Size, base64 data sent directly to the API10 MB, measured as the base64-encoded size
Size, base64 data on Amazon Bedrock or Google Cloud5 MB, measured as the base64-encoded size
ResolutionImages are resized for processing: 1568 px on the longest edge for standard models, and 2576 px for Claude 4.7 and later models that support high resolution

Two details matter in practice:

  • The 10 MB limit applies to the base64 text, which is about one third larger than the original file. An image of about 7.5 MB becomes roughly 10 MB once encoded.
  • Very large images are resized anyway. Sending a 6000-pixel photo gains nothing over a 2576-pixel version for most tasks, and costs more to transfer. Resize images before sending them when the task does not need full detail.

Image input also uses tokens. Larger images generally cost more tokens, so the same resizing that saves bandwidth also reduces cost. Measure with count_tokens() (Lesson 46) when you need exact numbers.


A Helper Module

The examples in Lessons 111–120 share a small helper module, media_utils.py. It uses only the standard library, so it works without extra packages. It provides:

  • make_png(), which creates a small PNG in memory, so examples can run without image files;
  • detect_media_type(), which identifies the format from the file's first bytes; and
  • encode_base64(), which converts bytes into the text form the API expects.
"""media_utils.py: small helpers for images and PDFs, using only the standard library."""

import base64
import struct
import zlib

MAX_IMAGE_BASE64_BYTES = 10 * 1024 * 1024
IMAGE_SIGNATURES = [
    (b"\x89PNG\r\n\x1a\n", "image/png"),
    (b"\xff\xd8\xff", "image/jpeg"),
    (b"GIF87a", "image/gif"),
    (b"GIF89a", "image/gif"),
]


def _chunk(kind: bytes, data: bytes) -> bytes:
    checksum = zlib.crc32(kind + data) & 0xFFFFFFFF
    return struct.pack(">I", len(data)) + kind + data + struct.pack(">I", checksum)


def make_png(width: int, height: int, rgb: tuple[int, int, int]) -> bytes:
    """A solid-colour PNG. Each scanline begins with filter byte 0 (no filtering)."""
    row = b"\x00" + bytes(rgb) * width
    header = struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0)   # 8-bit RGB
    return (b"\x89PNG\r\n\x1a\n"
            + _chunk(b"IHDR", header)
            + _chunk(b"IDAT", zlib.compress(row * height))
            + _chunk(b"IEND", b""))


def detect_media_type(data: bytes) -> str | None:
    """Identify an image from its first bytes, not from its file name."""
    for signature, media_type in IMAGE_SIGNATURES:
        if data.startswith(signature):
            return media_type
    if data[:4] == b"RIFF" and data[8:12] == b"WEBP":
        return "image/webp"
    return None


def encode_base64(data: bytes) -> str:
    return base64.b64encode(data).decode("ascii")

Why check the bytes, not the file name: a file called photo.jpg can contain anything. The first bytes, called the file's signature, identify the real format. Trusting the extension can send the API a mislabeled file and produce a confusing error.


Practical Example: Your First Image Question

This example creates a solid red image in memory and asks Claude what color it is.

"""first_image.py: send an image in a message and ask about it."""

from anthropic import Anthropic

from media_utils import encode_base64, make_png

client = Anthropic()

image = make_png(64, 64, (220, 40, 40))        # A 64x64 solid red square

message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=512,
    messages=[{
        "role": "user",
        "content": [
            {"type": "image", "source": {
                "type": "base64",
                "media_type": "image/png",
                "data": encode_base64(image),
            }},
            {"type": "text", "text": "What color is this image? Answer in one word."},
        ],
    }],
)

print("".join(block.text for block in message.content if block.type == "text").strip())

Expected output:

Red

How It Works

  1. make_png(64, 64, (220, 40, 40)) builds a PNG file in memory. It is a real, valid image, so the example needs no files on disk.
  2. encode_base64() converts the bytes into text, and the image block tells the API the format with media_type.
  3. The image block comes first, then the question. Placing the image before the text is the common pattern, and it reads naturally: "here is an image, now answer about it."
  4. The answer is read by block type, the same way as any other text response (Lessons 41 and 42).
  5. The question constrains the answer ("one word"), which makes the output easy to check.

Common Mistakes

Sending the Wrong Media Type

What happens: the API rejects the image, or behaves unexpectedly.

Fix: detect the format from the bytes (detect_media_type()), and send that value.

Forgetting the Base64 Size Overhead

What happens: images that seem under 10 MB are rejected, because their encoded form is larger.

Fix: check the length of the encoded string against the limit.

Sending Huge Images Unchanged

What happens: slow requests, higher token costs, and no gain in detail beyond the model's resolution.

Fix: resize images to the task's needs before encoding (Lesson 113).

Asking Before the Image Block

What happens: the question may be answered without reference to the image, because the text arrives first.

Fix: place image blocks before the question that refers to them.


Best Practices

  • Detect the format from the bytes, and reject anything unsupported before sending it.
  • Resize large images to the detail the task needs.
  • Put images before the questions about them.
  • Constrain answers with explicit formats such as "one word" or a JSON schema.
  • Measure token cost for image-heavy workloads.
  • Keep images out of logs unless you have a clear need and appropriate protections, since images may contain personal data.

Exercise

  1. Change the color to (40, 90, 220) and ask for the color's name. Does the answer change as you expect?
  2. Ask the same question with the text block first, then the image block. Compare the answers over several runs.
  3. Use detect_media_type() on the bytes of make_png(...), and on bytes that start with b"GIF89a". What does each return?
  4. Explain why the base64 size limit is checked against the encoded text rather than the original bytes.
Show answers for steps 3 and 4

Step 3: the PNG returns image/png, because its signature is \x89PNG\r\n\x1a\n. The GIF89a bytes return image/gif. Bytes that match no signature return None, which your code should treat as an unsupported file.

Step 4: the API receives the encoded text, and the documented limit is measured on that base64 size. Base64 represents every three bytes as four characters, so the encoded form is about one third larger. Checking the original size would allow files that the API then rejects.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Send Image Inputs and get answers drawn from it.

Signed-in readers only.