I lost half a day to a feature that sent photos taken in an iPhone app to the Gemini API for analysis. My local test images went through fine, but images coming from real devices kept getting rejected with 400 INVALID_ARGUMENT. Unsupported MIME type: image/jpg. The cause turned out to be almost embarrassingly simple: my upload code was attaching the wrong MIME type string.
As an indie developer who has run an app business since 2014, I have been handing images and audio to multimodal APIs far more often over the past year or two. Unsupported MIME type is one of those errors where the message looks like one thing but the root cause splits into three. Without knowing how to isolate them, it is easy to burn time. Let me work through them in order.
Start with the spelling — image/jpg does not exist
The most common cause is a misspelled MIME type. image/jpg is especially tempting because the file extension is .jpg, but it is not a real MIME type. The correct MIME type for JPEG is 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()
# ❌ Rejected: 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}},
"Describe what is in this image",
],
)Changing image/jpg to image/jpeg is enough to make it pass.
# ✅ Correct spelling
{"inline_data": {"mime_type": "image/jpeg", "data": image_bytes}}Gemini accepts five image MIME types: image/png, image/jpeg, image/webp, image/heic, and image/heif. HEIC (image/heic), the default format on the iPhone, can be passed as-is, so there is no need to convert it to JPEG first. This is a point I find is not widely known.
If you map extensions to MIME types yourself, let Python's standard library do it to avoid typos.
import mimetypes
mime_type, _ = mimetypes.guess_type("photo.jpg")
print(mime_type) # image/jpeg (the correct spelling)Check whether the File API stored it as octet-stream
The second trap is when a file uploaded through the File API ends up with a MIME type of application/octet-stream. This happens when you upload files larger than 20MB, or files you reuse across requests, to the File API.
client.files.upload() infers the MIME type from the extension and contents, but when inference fails it registers the file as application/octet-stream (generic binary). Passing that straight into generate_content gets rejected, because octet-stream is not a supported input type.
Print the MIME type right after uploading to see whether inference missed.
uploaded = client.files.upload(file="recording.m4a")
print(uploaded.mime_type) # application/octet-stream is a red flagThe fix is to specify the MIME type explicitly at upload time. The newer google-genai SDK accepts it via 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, "Transcribe this audio"],
)Stop relying on the extension and state what you are sending every time. That alone clears up nearly all octet-stream errors.
If it is still rejected, the format itself is unsupported
If the spelling is right and you specified the MIME type but still see Unsupported MIME type, there is a good chance Gemini genuinely does not support that format.
Audio and video in particular vary by model and endpoint. A frequent offender is the .m4a that the iPhone Voice Memos app produces. As a container it is accepted as audio/mp4, but sending it with the non-standard audio/m4a or audio/x-m4a gets it rejected. When you just need it to work, the fastest path is to convert it to a format with stable support.
# Convert m4a to mp3 with ffmpeg before passing it in
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"),
)As of now, the main MIME types Gemini accepts are images (image/png, image/jpeg, image/webp, image/heic, image/heif), audio (audio/wav, audio/mp3, audio/aiff, audio/aac, audio/ogg, audio/flac), and documents (application/pdf and plain-text formats). Checking once, before you build, whether the file types you handle are on this list saves a scramble later. Because the spec changes from time to time, it is worth getting into the habit of confirming the current table in the official file prompting documentation.
A small habit to keep it from recurring
This error shows up precisely in production, where you cannot predict what files users will send. In my case, once I started rejecting disallowed MIME types with a whitelist at the point the app accepts an upload, I could stop problematic files before they ever reached the API.
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"Unsupported image format: {mime_type}. "
f"Supported: {', '.join(sorted(SUPPORTED_IMAGE))}"
)Translating the error from "the Gemini API refused this" into "this format is not supported" in the user's own words also makes support conversations much easier.
If you want a first step, print the MIME type string in the code that is currently throwing Unsupported MIME type. If you see image/jpg or application/octet-stream, your cause is one of the ones above.