iPhoneアプリから撮影した写真をGemini APIに送って解析させる機能を作っていたとき、ローカルのテスト画像では通るのに、実機から送られてきた画像だけが 400 INVALID_ARGUMENT. Unsupported MIME type: image/jpg で弾かれる、という現象に半日ほど悩まされました。原因は拍子抜けするほど単純で、アップロード処理が付けていたMIMEタイプの文字列が間違っていただけでした。
2014年からアプリ事業を続けている個人開発者として、マルチモーダルなAPIに画像や音声を渡す場面はここ1〜2年で一気に増えました。その中で Unsupported MIME type は、エラーメッセージのわりに原因が3つに分かれていて、切り分けを知らないと無駄に時間を溶かしやすいエラーです。順番に潰していきます。
まず確認すべきは「綴り」です — image/jpg は存在しません
最も多いのが、MIMEタイプの綴り間違いです。特に image/jpg は、拡張子の .jpg に引きずられて書いてしまいがちですが、正式なMIMEタイプとしては存在しません。JPEGの正しいMIMEタイプは image/jpeg です。
from google import genai
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
with open("photo.jpg", "rb") as f:
image_bytes = f.read()
# ❌ これは弾かれる: Unsupported MIME type: image/jpg
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=[
{"inline_data": {"mime_type": "image/jpg", "data": image_bytes}},
"この画像に写っているものを説明してください",
],
)image/jpg を image/jpeg に直すだけで通ります。
# ✅ 正しい綴り
{"inline_data": {"mime_type": "image/jpeg", "data": image_bytes}}同様に間違えやすいのが image/jpeg 以外の表記揺れです。Geminiが画像で受け付けるMIMEタイプは、image/png・image/jpeg・image/webp・image/heic・image/heif の5つです。iPhoneの標準フォーマットであるHEIC(image/heic)はそのまま渡せるので、わざわざJPEGに変換する必要はありません。ここは意外と知られていない点だと感じています。
拡張子からMIMEタイプを自前でマッピングしている場合は、Pythonの標準ライブラリに任せたほうが綴りミスを防げます。
import mimetypes
mime_type, _ = mimetypes.guess_type("photo.jpg")
print(mime_type) # image/jpeg (正しい綴りが返る)File APIで octet-stream になっていないか
2つめの落とし穴は、File API経由でアップロードしたファイルのMIMEタイプが application/octet-stream になっているケースです。これは20MBを超えるファイルや、繰り返し使うファイルをFile APIにアップロードするときに起きます。
client.files.upload() は、ファイルの拡張子や中身からMIMEタイプを推測しますが、推測に失敗すると application/octet-stream(汎用バイナリ)として登録されます。そのまま generate_content に渡すと、octet-streamは解析対象として非対応のため弾かれます。
アップロード直後にMIMEタイプを確認すると、推測が外れているかどうかがすぐ分かります。
uploaded = client.files.upload(file="recording.m4a")
print(uploaded.mime_type) # application/octet-stream だと危険信号対策は、アップロード時にMIMEタイプを明示的に指定することです。新しい google-genai SDKでは config で渡せます。
from google.genai import types
uploaded = client.files.upload(
file="recording.m4a",
config=types.UploadFileConfig(mime_type="audio/mp4"),
)
print(uploaded.mime_type) # audio/mp4
response = client.models.generate_content(
model="gemini-2.5-flash",
contents=[uploaded, "この音声を文字起こししてください"],
)拡張子に頼らず、自分が何を送っているかを毎回明示する。これだけでoctet-stream由来のエラーはほぼ消えます。
それでも弾かれるなら、フォーマット自体が非対応です
綴りも合っている、MIMEタイプも明示している、それでも Unsupported MIME type が出る場合は、そのフォーマットをGeminiが本当にサポートしていない可能性が高いです。
特に音声と動画はモデルやエンドポイントによって対応範囲が変わります。よく問題になるのが、iPhoneのボイスメモが出力する .m4a です。これはコンテナとしては audio/mp4 で受け付けられますが、audio/m4a や audio/x-m4a という非標準の表記で送ると弾かれます。確実に通したいときは、対応が安定している形式に変換してしまうのが早いです。
# ffmpegでm4aをmp3に変換してから渡す
import subprocess
subprocess.run([
"ffmpeg", "-i", "voice.m4a", "-acodec", "libmp3lame", "voice.mp3"
], check=True)
uploaded = client.files.upload(
file="voice.mp3",
config=types.UploadFileConfig(mime_type="audio/mp3"),
)現時点でGeminiが受け付ける主なMIMEタイプは、画像が image/png・image/jpeg・image/webp・image/heic・image/heif、音声が audio/wav・audio/mp3・audio/aiff・audio/aac・audio/ogg・audio/flac、ドキュメントが application/pdf とプレーンテキスト系です。実装前に、自分が扱うファイル形式がこのリストに入っているかを一度確認しておくと、後から慌てずに済みます。仕様は更新されることがあるので、公式のファイルプロンプトのドキュメントで最新の対応表を確認する習慣をつけておくと安心です。
再発させないための一工夫
このエラーは、ユーザーがどんなファイルを送ってくるか分からない本番環境でこそ起きます。累計5,000万ダウンロードを超えるアプリ事業を続けてきて痛感したのは、ローカルで想定したフォーマットと、世界中の実機から実際に飛んでくるフォーマットは別物だということです。私の場合、アプリ側でアップロードを受け付ける時点で、許可するMIMEタイプをホワイトリストで弾くようにしてからは、APIまで到達する前に問題のあるファイルを止められるようになりました。サーバー側のログにもMIMEタイプを残しておくと、後から「どの形式で詰まったか」を集計でき、対応形式を増やす判断の材料になります。
SUPPORTED_IMAGE = {"image/png", "image/jpeg", "image/webp", "image/heic", "image/heif"}
def validate_image(mime_type: str) -> None:
if mime_type not in SUPPORTED_IMAGE:
raise ValueError(
f"未対応の画像形式です: {mime_type}. "
f"対応形式: {', '.join(sorted(SUPPORTED_IMAGE))}"
)エラーメッセージを「Gemini APIが拒否した」ではなく「この形式は未対応です」とユーザーの言葉に翻訳して返せると、サポート対応もぐっと楽になります。
まず試すなら、いま手元で Unsupported MIME type を出しているコードのMIMEタイプ文字列を print してみてください。image/jpg や application/octet-stream が出ていれば、原因はこの記事のどれかに当てはまるはずです。同じところで詰まっている方の手がかりになれば嬉しいです。