Multi-Platform Portability & Model Upgrades

Image Source Types Across Platforms: URL and file_id vs Base64 Only

The image block has three source types on the Claude API. On Bedrock and Vertex AI it effectively has one. A small adapter keeps your vision code identical across all of them.

Claude 3P 101 · Updated July 2026 · Unofficial guide

On the first-party Claude API, an image content block accepts three source types: base64 (bytes inline in the request), url (the platform fetches the image server-side), and file (a Files API file_id from a prior upload). Per Anthropic's vision documentation, Amazon Bedrock and Google Cloud currently support base64 only. Claude Platform on AWS, as the same-day-parity surface, supports the full set; Microsoft Foundry documents image support, with the Files API path limited to Hosted-on-Anthropic deployments (see the Files API portability gap).

Why the convenient sources exist

URL sources save you from downloading and re-uploading images your system references anyway (product photos on a CDN, say), and file_id sources keep request bodies small when the same image recurs across requests — helpful because you can hit the 32 MB request-size limit well before the per-request image-count limit. Losing both on Bedrock and Vertex means your application becomes the fetcher and the encoder.

The adapter layer

The clean pattern is a normalization function that converts any internal image reference into the richest source type the current platform supports, falling back to base64 by fetching and encoding:

import base64, mimetypes, urllib.request

def image_block(ref: str, platform: str) -> dict:
    """ref: an https URL or local path; platform: 'anthropic',
    'claude_aws', 'bedrock', 'vertex', 'foundry'."""
    if platform in ("anthropic", "claude_aws") and ref.startswith("https://"):
        return {"type": "image", "source": {"type": "url", "url": ref}}
    if ref.startswith("https://"):
        data = urllib.request.urlopen(ref).read()      # fetch ourselves
    else:
        data = open(ref, "rb").read()
    media = mimetypes.guess_type(ref)[0] or "image/jpeg"
    return {"type": "image", "source": {"type": "base64",
            "media_type": media, "data": base64.b64encode(data).decode()}}

In production you'd add a timeout, retries, and content-type validation from the HTTP response rather than the filename — but the shape is the point: prompt-building code asks for "an image block for this reference" and never mentions source types.

Limits that differ once you go inline

Fetching and inlining moves you under stricter ceilings, and they differ by platform:

ConstraintClaude APIBedrockVertex AI
Max image size (base64)10 MB5 MB5 MB
Request payload limit32 MB20 MB30 MB
Images per request600 (100 on 200K-context models)Vertex documents up to 100 images per request

Supported formats are JPEG, PNG, GIF, and WebP everywhere; animations aren't supported (only the first frame is used), and maximum dimensions are 8000×8000 pixels. Two cost notes worth wiring into the adapter: images are billed in 28×28-pixel patches (ceil(width/28) × ceil(height/28) visual tokens — a 1000×1000 image costs 1,296 tokens), and high-resolution models (Fable 5, Opus 4.8/4.7, Sonnet 5) accept up to a 2576-pixel long edge and can use roughly 3x more visual tokens per image than standard-resolution models. Downscaling before encoding — which the adapter is perfectly placed to do — cuts both payload size and token cost. Also mind Anthropic's many-image rule: requests with more than 20 image/document blocks impose a stricter 2000-pixel per-dimension limit.

Rule of thumb: base64 is the only image source that works on every platform, so it's the fallback your adapter must always be able to produce. URL and file_id are optimizations to layer on where the platform supports them — never the assumption.

Practical placement notes

Whatever the source type, Claude works best with images placed before text within the message content — keep that ordering in the prompt builder, not the adapter. And if your image set is large and repetitive on a platform with Files API support, prefer file_ids to keep payloads small; on Bedrock and Vertex, the equivalent play is caching the encoded base64 in your own store so you fetch and encode each source image once, not per request.

One more reason the fetch-and-encode path matters even on platforms that accept URL sources: a url source is fetched by the platform, server-side. Images that live behind your VPN, on internal hosts, or behind authentication aren't reachable that way, so internal assets go through your own fetch-and-encode path everywhere — the URL source is really for publicly reachable images only. Also note that inline image uploads are ephemeral: the bytes aren't stored beyond the request, which is friendly for data-handling reviews but means there's nothing to reuse server-side on the base64 path — reuse lives entirely in your adapter's cache.

Where to go next

The image inputs reference covers block anatomy and token math; the vision resize strategy quantifies the downscaling win; and the PDF source adapter applies this same pattern to document blocks.

Sources