Use Image URLs

Su Suriya Ravichandran Updated 10 Nov 2026
5 min read ·Lesson 108 of 200

Referencing an Image by URL

Instead of sending image data, you can give Claude a URL, and the API fetches the image itself:

{
    "type": "image",
    "source": {
        "type": "url",
        "url": "https://platform.claude.com/docs/images/vision-example.jpg",
    },
}

The request stays small, because it contains only the link. This is convenient for public images, such as product photos on a website, diagrams in documentation, or images that already live in a content management system.


When to Use a URL, and When Not To

SituationBest source type
A public image that will not changeurl
An image uploaded by a userbase64 or file_id, never a URL the user supplies
The same image used in many requestsfile_id (Lesson 116)
A private image behind a loginNot possible by URL; send base64 or upload (Lessons 113 and 115)

A URL is a reference, not a copy. Three consequences follow:

  1. The content can change. The image at a URL may be replaced tomorrow. If you need to reproduce a result, keep the image itself, or a file ID.
  2. The image must be reachable. The URL must be public, and the request fails if the server cannot fetch it.
  3. You do not control what is fetched. If you accept arbitrary URLs from users, anyone can point your application at any image on the web, including content you would not want to process or store.

Restricting URLs

When URLs come from users or from external content, restrict them before sending. An allowlist of trusted hosts is the simplest safeguard:

from urllib.parse import urlparse

ALLOWED_HOSTS = {"platform.claude.com", "images.example.com"}


def check_image_url(url: str) -> str:
    parts = urlparse(url)
    if parts.scheme != "https":
        raise ValueError("image URLs must use https")
    if parts.hostname not in ALLOWED_HOSTS:
        raise ValueError(f"images may only come from approved hosts, not {parts.hostname!r}")
    return url

An allowlist is stricter than a blocklist. A blocklist tries to list everything bad and inevitably misses something; an allowlist accepts only what you have approved.


Practical Example: Describing an Image from a URL

This example checks the URL against the allowlist, then asks Claude about the image.

"""url_image.py: ask about an image referenced by an approved URL."""

from urllib.parse import urlparse

from anthropic import Anthropic

client = Anthropic()
ALLOWED_HOSTS = {"platform.claude.com"}
IMAGE_URL = "https://platform.claude.com/docs/images/vision-example.jpg"


def check_image_url(url: str) -> str:
    parts = urlparse(url)
    if parts.scheme != "https":
        raise ValueError("image URLs must use https")
    if parts.hostname not in ALLOWED_HOSTS:
        raise ValueError(f"images may only come from approved hosts, not {parts.hostname!r}")
    return url


def ask_about_url(url: str, question: str) -> str:
    message = client.messages.create(
        model="claude-opus-5-5",
        max_tokens=1024,
        messages=[{"role": "user", "content": [
            {"type": "image", "source": {"type": "url", "url": check_image_url(url)}},
            {"type": "text", "text": question},
        ]}],
    )
    return "".join(block.text for block in message.content if block.type == "text").strip()


if __name__ == "__main__":
    print(ask_about_url(IMAGE_URL, "Describe this image in two sentences."))

    try:
        check_image_url("https://example.com/cat.jpg")
    except ValueError as error:
        print("Rejected:", error)

Example output (the description depends on the image at the URL):

<A two-sentence description of the image>
Rejected: images may only come from approved hosts, not 'example.com'

How It Works

  1. check_image_url() runs before any request is made. It requires https, and checks the host against the allowlist, so a rejected URL never reaches the API.
  2. urlparse() splits the URL into its parts, and parts.hostname gives the host without any port or credentials. Comparing parts is safer than searching the string, because a string search can be fooled by URLs such as https://platform.claude.com.evil.example/.
  3. The URL source is used exactly like the base64 source, only the source dictionary differs.
  4. The second call shows the rejection path, which is what a real application would do with a user-supplied URL: refuse it with a clear message.

Common Mistakes

Passing User URLs Straight Through

What happens: users can make your application process arbitrary images from the web.

Fix: allowlist the hosts, or accept uploads instead.

Checking URLs by Substring

What happens: "platform.claude.com" in url accepts https://platform.claude.com.attacker.example/.

Fix: compare the parsed host name exactly.

Assuming the URL Always Works

What happens: links break, return errors, or point to pages instead of images.

Fix: handle request failures with clear errors (Lesson 120), and store images you need to keep.

Relying on a URL for Reproducibility

What happens: the same request gives different results after the image changes.

Fix: keep the image, or its file ID, alongside your results.


Best Practices

  • Prefer uploads or base64 for images that users provide.
  • Use an allowlist of hosts, and require https.
  • Compare parsed hostnames, never substrings.
  • Store the image, not just its link, when results must be reproducible.
  • Handle fetch failures as ordinary errors.

Exercise

  1. Add a second approved host and test a URL on each host.
  2. Try http://platform.claude.com/... (without the s). What happens, and why does the check reject it?
  3. Write a test with https://platform.claude.com@evil.example/image.jpg. Which host does urlparse() report, and does the check reject the URL?
  4. Explain why a URL reference is not suitable for an image that a user uploaded to your application.
Show answers for steps 3 and 4

Step 3: urlparse() reports evil.example as the hostname. The part before the @ is interpreted as user information, not as the host. Because the check compares the parsed hostname, the URL is rejected. A substring check would wrongly accept it. This is exactly why comparing parsed parts is the safer approach.

Step 4: a URL reference depends on the uploaded file remaining reachable at that address, which is not guaranteed. The file may be deleted, moved behind a login, or replaced. Also, the URL would have to be public, which conflicts with the privacy expectations of a user's upload. Base64 or the Files API keeps the image under your control.

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 Use Image URLs and get answers drawn from it.

Signed-in readers only.