Claude Image Base64 Decode Failed — Encoding Fix (2026) | AI Error Hub
Anthropic Claude Vision Severity: Medium HTTP 400

Claude Image Base64 Decode Failed

Anthropic can't decode your image data. Common causes: forgot to strip data:image/png;base64, prefix, wrong media_type declared, invalid padding, or sent raw bytes instead of base64 string. All fixable in seconds.

Error code: invalid_request_error HTTP: 400 Category: Vision Last tested: Nov 15, 2026
Quick Fix (TL;DR)

The data field must be a pure base64 string — no data:image/png;base64, prefix, no whitespace, no line breaks. Verify with: base64.b64decode(data, validate=True). Set media_type to match the actual image format (JPEG → image/jpeg, not image/jpg).

The Error Messages You're Seeing

Response body variants
HTTP/1.1 400 Bad Request

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "Could not decode image from base64 data"
  }
}

Variants:
"messages.0.content.0.source.data: Invalid base64 encoding"
"Invalid image data — magic bytes do not match declared media_type"
"messages.0.content.0.source.data: Value must be a string"
"Image decoded but format is not supported"

What Actually Causes This Error

42%
Left data URL prefix in the data fieldCopying from HTML/JS: data:image/png;base64,iVBORw0KGgo... — the prefix must be stripped.
28%
Wrong media_type declaredSaid image/jpeg but sent PNG bytes. Magic byte mismatch = decode failure.
14%
Sent bytes instead of base64 stringPython bytes object serializes to JSON as an array or fails. Must decode to str first.
10%
Whitespace/newlines in base64Some libraries wrap base64 at 76 chars. Anthropic accepts, but standardize by stripping.
6%
Missing or wrong paddingBase64 length must be multiple of 4. Missing = padding at end causes silent corruption.

Fixes That Work (Tested Nov 2026)

1Correct Base64 Preparation (Python)

Python · robust base64 prep
import base64 import re import imghdr import mimetypes def prepare_image_source(input_data): """Return {type, media_type, data} for Claude image content block. Accepts: file path, bytes, base64 str, or data URL.""" # Case 1: bytes if isinstance(input_data, bytes): raw = input_data # Case 2: file path elif isinstance(input_data, str) and not input_data.startswith(("data:", "iVBOR", "/9j/")): with open(input_data, "rb") as f: raw = f.read() # Case 3: data URL — strip prefix elif input_data.startswith("data:"): # data:image/png;base64,iVBORw0KGgo... header, b64 = input_data.split(",", 1) raw = base64.b64decode(b64) # Case 4: bare base64 string else: clean = re.sub(r'\s', '', input_data) # strip whitespace # Fix missing padding clean += '=' * (-len(clean) % 4) raw = base64.b64decode(clean, validate=True) # Detect format from magic bytes fmt = imghdr.what(None, h=raw) if fmt not in ("jpeg", "png", "gif", "webp"): raise ValueError(f"Unsupported format: {fmt}") media_type = f"image/{fmt}" # Note: "jpeg" not "jpg" for media_type # Re-encode as clean base64 string b64_str = base64.b64encode(raw).decode("ascii") return { "type": "base64", "media_type": media_type, "data": b64_str } # Use it image_source = prepare_image_source("photo.png") response = client.messages.create( model="claude-sonnet-4-5", max_tokens=1024, messages=[{ "role": "user", "content": [ {"type": "image", "source": image_source}, {"type": "text", "text": "Describe this."} ] }] )

2Strip Data URL Prefix (Browser/Node)

TypeScript · handle FileReader output
async function fileToBase64(file: File): Promise<{data: string, mediaType: string}> { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const result = reader.result as string; // FileReader returns: data:image/png;base64,iVBOR... // Split off the prefix const [header, data] = result.split(","); const mediaType = header.match(/^data:([^;]+)/)?.[1] || "image/jpeg"; // Normalize media_type const normalized = mediaType === "image/jpg" ? "image/jpeg" : mediaType; resolve({ data, mediaType: normalized }); }; reader.onerror = reject; reader.readAsDataURL(file); }); } const { data, mediaType } = await fileToBase64(file); // data has NO prefix, ready for Claude
Common data URL gotchas

1. FileReader.readAsDataURL() ALWAYS returns with prefix — must strip
2. Browser canvas.toDataURL() also includes prefix
3. Some CDNs return images as data URLs when queried via fetch — check!
4. Media type image/jpg is technically invalid — use image/jpeg

3Validate Before Sending

Python · pre-flight validation
def validate_image_source(source): """Raise informative error if image_source is malformed.""" assert source["type"] == "base64", "Only base64 shown here" media_type = source["media_type"] assert media_type in {"image/jpeg", "image/png", "image/gif", "image/webp"}, \ f"Invalid media_type: {media_type}" data = source["data"] assert not data.startswith("data:"), \ "Strip 'data:image/...;base64,' prefix from data field" # Verify valid base64 try: decoded = base64.b64decode(data, validate=True) except Exception as e: raise ValueError(f"Invalid base64: {e}") # Verify magic bytes match declared type magic_map = { "image/jpeg": lambda b: b.startswith(b"\xff\xd8\xff"), "image/png": lambda b: b.startswith(b"\x89PNG"), "image/gif": lambda b: b[:3] == b"GIF", "image/webp": lambda b: b[8:12] == b"WEBP", } if not magic_map[media_type](decoded): raise ValueError( f"Magic bytes do not match {media_type}. " f"Detected: {imghdr.what(None, h=decoded)}" ) # Size check assert len(data) < 5_000_000, f"Base64 too large: {len(data)}" return True # Use before API call validate_image_source(image_source) response = client.messages.create(...)

Preventing This Error Going Forward

  • Always run base64 through validate=True. Catches issues early.
  • Detect media_type from magic bytes. Don't trust file extensions or user input.
  • Never trust FileReader output directly. Always strip the data URL prefix.
  • Use image/jpeg, not image/jpg. The RFC-standard media type.
  • Validate on the client too. Fail fast before making expensive API calls.
AR
Tested by Ahmed R.
Researcher · AI Error Hub
Last verified: Nov 15, 2026 · SDK: anthropic-sdk-python 0.42

Frequently Asked Questions

File extensions and MIME types are different. The IANA-registered MIME type is image/jpeg. Most APIs accept both, but standards-compliant servers (like Anthropic) may reject image/jpg. Always use image/jpeg.

Not in the Messages API — JSON requires string data. If you want to avoid base64 overhead, use the Files API (beta) which accepts multipart uploads. Otherwise, base64 is required and adds ~33% size.

EXIF is stripped or ignored during Claude's processing. Rotation from EXIF metadata is NOT applied — some images may appear sideways to Claude. Apply EXIF rotation before uploading if it matters (Pillow: ImageOps.exif_transpose(img)).

No, not directly. Rasterize SVG to PNG or JPEG client-side first. Libraries: cairosvg (Python), @resvg/resvg-js (Node). Text in SVG becomes pixels — Claude reads it via OCR, not as text.

Not supported. Convert to JPEG. Python: pillow-heif plugin. Node: heic-convert. Server-side conversion recommended — don't ship HEIC to browsers either (Safari-only support).