◉CLAUDE LABEN
●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/API & SDK
⬡ API & SDK/2026-05-29中級

Claude API に画像 URL を渡すと invalid_request_error が出るときの切り分け

Claude API の Messages で `source.type: url` を指定した画像が invalid_request_error で落ちるとき、原因は URL スキーム・MIME・サイズ・到達性のどれかにほぼ収束します。診断順に整理します。

claude-api83vision8image-inputtroubleshooting61anthropic12

iOS/Android アプリの運用で、商品画像や生成アートのサムネイルを Claude API に投げて分類・キャプション付けする処理を書いていると、ある日ふいに 400 invalid_request_error: Could not process image で落ち始めることがあります。直前まで動いていたコードが、画像を URL 渡しに切り替えた瞬間からエラーになる、という相談を最近よくいただきます。

私自身、壁紙系アプリの自動分類パイプラインを base64 から URL 形式に置き換えたとき、最初の 3 時間ぶん(約 800 枚)が全部 invalid_request_error で弾かれて凍りつきました。原因は意外と単純で、画像配信側の CDN が一部リージョンで 302 を返していた、というだけのものでした。

このタイプのエラーは原因が「URL スキーム・MIME・サイズ・到達性」のいずれかに収束することが多く、順序立てて切り分ければ 10 分以内に潰せます。2014 年から個人アプリ開発を続けて累計 5,000 万 DL を超え、AdMob 月収 100 万円規模で運用してきた現場感も交えて、診断手順を整理します。

出るエラーの代表的な文面

URL ソースで画像を渡したとき、API が返してくるエラーは主に次の 3 系統です。

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "messages.0.content.0.source.url: Could not process image https://example.com/photo.jpg"
  }
}
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "messages.0.content.0.source.url: Image must be a valid HTTPS URL"
  }
}
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "Image exceeds 5 MB size limit"
  }
}

文面で原因をある程度絞れます。Could not process は「URL は読めたが中身がダメ」、must be a valid HTTPS URL は「URL そのものが弾かれている」、exceeds X MB は「容量超過」という棲み分けです。

切り分けの起点:URL を curl してから API に渡す

エラーメッセージだけ眺めても確証は得られないので、最初に必ずやっておきたいのが「Anthropic 側で見えている URL の挙動を、自分の端末からも再現する」ことです。

# ヘッダーだけ取得して内容を確認
curl -sIL -A "Anthropic-Image-Fetcher/1.0" "https://example.com/photo.jpg"

出てきた HTTP/2 200 の有無、Content-Type、Content-Length、リダイレクトチェーンの長さ、この 4 つを最初に確認します。これが揃わない限り、API 側がいくら正しい呼び出しでも通せません。

原因①: URL スキームが HTTP のまま

Claude API の画像 URL ソースは HTTPS 必須 です。CMS や自前サーバから画像を引いていると、開発環境で誤って http:// のままハードコードされていることが意外とあります。

# NG
content = [{
    "type": "image",
    "source": {"type": "url", "url": "http://example.com/photo.jpg"},
}]

修正は単純で、必ず https:// に揃えること。混在を避けるため、本番投入前に URL を正規化するヘルパーを噛ませておくと安全です。

from urllib.parse import urlparse, urlunparse
 
def to_https(url: str) -> str:
    p = urlparse(url)
    if p.scheme == "https":
        return url
    if p.scheme == "http":
        return urlunparse(p._replace(scheme="https"))
    raise ValueError(f"Unsupported scheme: {p.scheme}")

S3 / R2 の Presigned URL を使う場合は、必ず HTTPS で発行されるようにバケットポリシーを締めておきます。

原因②: MIME タイプがサポート外

Claude API がサポートする画像 MIME は image/jpeg、image/png、image/gif、image/webp の 4 種類です。HEIC、AVIF、BMP、TIFF、SVG は通りません。

落とし穴になりやすいのが「拡張子は .jpg だが、CDN の Content-Type が application/octet-stream で返ってきている」ケース。Claude API は URL のレスポンスヘッダーを尊重するので、拡張子ではなく MIME で弾かれます。

# 期待: Content-Type: image/jpeg
curl -sI "https://example.com/photo.jpg" | grep -i content-type

application/octet-stream や binary/octet-stream が返ってきていたら、配信側で MIME を正しく設定するか、対象オブジェクトに ContentType を明示して上書きします。Cloudflare R2 や AWS S3 ではアップロード時に --content-type image/jpeg を必ず添えるようにしています。

iPhone から直接アップロードされた素材を扱う場合、HEIC のまま CDN に乗ってしまうのが定番の罠です。heif-convert か Sharp で JPEG/PNG に再エンコードしてから配信パスに置く運用にしないと、年に何度か必ずインシデントになります。

原因③: ファイルサイズが 5 MB を超えている

URL ソースで参照できる画像の上限は 1 ファイルあたり 5 MB、解像度の上限は長辺 8000 px です。私の壁紙アプリは 4K 素材を扱うので、生のままだと頻繁に超過します。

事前チェックを HEAD リクエストで挟むのが一番手堅い対策です。

import httpx
 
async def is_within_limit(url: str, max_mb: int = 5) -> bool:
    async with httpx.AsyncClient(follow_redirects=True, timeout=10) as c:
        r = await c.head(url)
        size = int(r.headers.get("content-length", "0"))
        return 0 < size <= max_mb * 1024 * 1024

超過していたら、配信側で長辺 2000 px ・ JPEG quality 85 に縮める変換 URL を別途用意しておくと、API 側の品質を犠牲にせずに済みます。Cloudflare Images の ?width=2000&quality=85 のようなオンザフライ変換が便利です。

原因④: URL が認証必須・リダイレクトで落ちる

CDN 側でアクセス元 IP を絞っていたり、Referer/Origin ヘッダーをチェックしていたりすると、Anthropic 側のフェッチャーが 401/403 を踏みます。署名期限の切れた Presigned URL も同様です。

検証は、HTTP ヘッダーを「外から」何も渡さない素の curl で 200 が返るかどうかでだいたい判定できます。

curl -sI -L "https://example.com/photo.jpg" | head -5

HTTP/2 401、HTTP/2 403、HTTP/2 302 が連鎖して HTTP/2 200 に着地しない場合、URL ソースのまま渡しても通りません。次のいずれかで対処します。

  • 認証を外せる公開バケットに画像を一旦コピーする(ライフサイクルポリシーで 24 時間後に削除)
  • Presigned URL の有効期限を 5 分以上 残して発行する(フェッチが即時に走る保証はないため)
  • どうしても認証が外せないなら、URL ソースを諦めて自分で download → base64 → source.type: base64 の経路に変える

302 のリダイレクトは最大 5 ホップまでは追従されますが、Cloudflare の Access ログインリダイレクトのような HTML 経由のものは追えません。最終 URL を事前解決してから渡す方が安全です。

原因⑤: クエリパラメータでの動的変換が間に合っていない

意外と踏むのが、https://example.com/photo.jpg?width=2000&quality=85 のような URL を渡したとき、CDN 側の変換ワーカーが初回アクセスで 5〜10 秒かかり、Anthropic 側のフェッチャーがタイムアウト判定する、というケースです。

私のパイプラインでは、API に渡す前に自分で HEAD を打って 200 を確実に引き出すウォームアップ処理を挟むようにしています。

async def warmup_and_call(url: str):
    async with httpx.AsyncClient(timeout=15) as c:
        await c.get(url)  # キャッシュをウォームアップ
    # ↓ Claude API 呼び出し
    response = await client.messages.create(...)

「先に自分で取りにいって CDN に焼く」というだけの手当てですが、これだけで失敗率が体感で 2% から 0.1% を切る程度まで下がりました。

URL ソースが本当に使えないときの退避策

上記すべてを試しても通らないときは、おとなしく base64 形式に切り替えます。コードは増えますが、フェッチ失敗の不確実性をローカル側に持ってこられるので、デバッグが圧倒的に楽になります。

import base64
import httpx
import anthropic
 
async def image_block_from_url(url: str) -> dict:
    async with httpx.AsyncClient(follow_redirects=True, timeout=15) as c:
        r = await c.get(url)
        r.raise_for_status()
    return {
        "type": "image",
        "source": {
            "type": "base64",
            "media_type": r.headers["content-type"].split(";")[0].strip(),
            "data": base64.standard_b64encode(r.content).decode("ascii"),
        },
    }

base64 だとリクエストペイロードが 33% ほど膨らみますが、画像 1 枚ごとに失敗してもアプリ全体が止まらない構造になるのが大きな利点です。私は本番では「URL でまず試す → 失敗したら base64 でリトライ」の二段構えにしています。

振り返り

URL ソースの invalid_request_error は、Anthropic 側で何が起きているかの可視性が低い分、自分の側で curl -sIL の結果をスナップショットとして残しておくのが結局いちばん早い解決策になります。エラー文面を眺めて推測する時間が長くなったら、まず端末から HEAD を打つ習慣を持っておくと、原因が画像配信側にあるのか API 呼び出し側にあるのかが 5 分でわかります。

似たような切り分けが必要になる場面は他にもあります。同じ Claude API 周りで invalid_request 系の取り回しに困ったら、Claude API の invalid_request_error メッセージ別対処 と Claude API のプロンプトキャッシュがヒットしない時の診断手順 もあわせて参考にしてみてください。

実装の参考になれば幸いです。お読みいただきありがとうございました。

シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⬡ API & SDK2026-09-22
PDF の入力トークンは文字数では読めません — 送る前にページ数から上限で挟む
Claude API に PDF を送る前の入力トークンを、文字数ではなくページ数から上限で挟んで見積もる手順です。A4 9 ページの日本語文書で文字数見積もりの 6.6 倍になった実測と、解像度ティアとトークナイザの二重の増分、count_tokens が file_id を数えられない制約までをまとめます。
⬡ API & SDK2026-05-22
Claude の『このモデルは現在利用できません』が出る時に効くモデル選択フォールバックの設計
Claude API で『このモデルは現在利用できません(this model is currently unavailable)』と返ってきたとき、529 Overloaded やレート制限と同じ扱いにすると遠回りになります。エラーの種類を SDK の例外クラスで見分け、モデル ID をリクエスト単位で切り替えるフォールバックの実装と、思い込みで書きやすい落とし穴をまとめました。
⬡ API & SDK2026-04-26
Claude API のプロンプトキャッシュで月額コストを半分にした実装メモ
長文のシステムプロンプトを毎リクエスト送る本番ワークロードでは、プロンプトキャッシュの設計次第で月額コストが大きく変わります。ヒット率の計測から始める手順、TTLの選択、トークン下限と4ブロック上限、toolsを1つ足してヒット率が0になった失敗談に加え、ヒット率から入力コストを逆算する計算と損益分岐(5分TTLで約22%、1時間TTLで約53%)まで、半年の運用実例でまとめました。