Vision and multimodal (OpenAI)
OpenAI’s multimodal surface spans vision input (image-in to GPT-5 / GPT-4o / o3 / o4-mini — text comes back), audio input/output (GPT-4o-audio + Realtime API for voice), image generation (DALL-E 3 + the newer gpt-image-1 with multi-input editing), speech recognition (Whisper + GPT-4o-transcribe), and text-to-speech (TTS-1 + TTS-1-HD + GPT-4o-TTS). Vision input is the most-used feature: pass an image via image_url (https URL or base64 data URI) with a detail: low/high/auto hint, and any modern chat model reads it natively. The detail parameter is the cost-control knob — low uses a flat 85 tokens per image regardless of size; high uses tiles (~170 tokens per 512×512 tile). Image generation and editing live on separate endpoints (/v1/images/generations, /v1/images/edits).
See also
- realtime-api-openai — bidirectional voice with GPT-4o-realtime
- openai-models-and-capabilities — model multimodality matrix
- openai-api-and-sdks —
client.images.*andclient.audio.*surfaces - vision-and-multimodal — sister doc for Claude
- vision-and-long-context-gemini — sister doc for Gemini
1. Image input — supported models
| Model | Image input | Notes |
|---|---|---|
| GPT-5 / GPT-5-mini | ✅ | Multimodal native |
| GPT-5-nano | Limited | Smaller capability |
| GPT-4o / GPT-4o-mini | ✅ | Strong baseline |
| GPT-4 Turbo Vision | ✅ | Legacy |
| o3 / o4-mini | ✅ | Reasoning + vision |
| o3-mini | Limited / partial | |
| o1 / o1-pro | ❌ | Text-only |
| GPT-3.5 | ❌ | Text-only |
2. Image input formats
Two paths:
HTTPS URL
client.chat.completions.create(
model="gpt-5",
messages=[
{"role": "user", "content": [
{"type": "text", "text": "What's in this image?"},
{"type": "image_url",
"image_url": {"url": "https://example.com/cat.jpg",
"detail": "high"}},
]},
],
)URL is fetched server-side by OpenAI. Must be publicly accessible — no auth headers passed.
Base64 data URI
import base64
b64 = base64.b64encode(open("photo.jpg", "rb").read()).decode()
messages=[
{"role": "user", "content": [
{"type": "text", "text": "What's in this image?"},
{"type": "image_url",
"image_url": {"url": f"data:image/jpeg;base64,{b64}",
"detail": "high"}},
]},
]Use for private files, generated images, or when you don’t have a stable URL.
Responses API equivalent
client.responses.create(
model="gpt-5",
input=[
{"role": "user", "content": [
{"type": "input_text", "text": "What's in this image?"},
{"type": "input_image", "image_url": "https://example.com/cat.jpg"},
# OR base64:
# {"type": "input_image", "image_url": f"data:image/jpeg;base64,{b64}"},
]},
],
)Note Responses uses input_text / input_image instead of text / image_url.
3. Supported formats
| Format | Supported |
|---|---|
| PNG | ✅ |
| JPEG | ✅ |
| WebP | ✅ |
| GIF (static) | ✅ |
| GIF (animated) | First frame only |
| BMP | ❌ |
| TIFF | ❌ |
| HEIC / HEIF | ❌ |
For unsupported formats, convert to PNG / JPEG first.
4. detail parameter — the cost knob
| Value | Behavior | Token cost |
|---|---|---|
"low" | Treats image as 512×512 thumbnail | 85 tokens flat regardless of input size |
"high" | Full-resolution tiles | 85 base + (170 × tile_count) |
"auto" (default) | Server picks based on image size | Varies |
High-detail tile math
Per platform.openai.com/docs/guides/vision:
- Image is scaled so the shortest side ≤ 768 px
- Then scaled so the longest side ≤ 2048 px
- Resulting image is split into 512×512 tiles
- Each tile = 170 tokens, plus 85 base tokens
Examples:
- 512×512 → 1 tile = 255 tokens
- 1024×1024 → 4 tiles = 765 tokens
- 1024×768 → 2 tiles = 425 tokens
- 2048×1024 → 6 tiles = 1,105 tokens
For classification / “what is this” → detail: "low" (85 tokens) is plenty.
For OCR / reading fine print → detail: "high".
5. Image limits per request
- Max images per message: 50 (Chat Completions) / typically 50+ (Responses)
- Max image size per file: 20 MB
- Max combined request size: 10-20 MB depending on encoding
- No explicit max images per request: a 128k-context call could fit many images depending on detail settings
For batch processing many images, the Batch API + Files API is more practical.
6. Image generation (DALL-E 3 + gpt-image-1)
DALL-E 3
img = client.images.generate(
model="dall-e-3",
prompt="A cat astronaut on the moon, vivid style, dramatic lighting.",
size="1024x1024", # or "1024x1792", "1792x1024"
quality="hd", # or "standard"
style="vivid", # or "natural"
n=1, # DALL-E 3 only supports n=1
response_format="url", # or "b64_json"
)
print(img.data[0].url)Pricing (approximate):
- Standard 1024×1024: $0.040
- HD 1024×1024: $0.080
- Standard 1024×1792 or 1792×1024: $0.080
- HD 1024×1792 or 1792×1024: $0.120
DALL-E 3 has built-in prompt rewriting (the model often revises your prompt for safety / fidelity). The revised prompt is in img.data[0].revised_prompt.
gpt-image-1 (newer, multi-input editing)
# Generation (similar to DALL-E)
img = client.images.generate(
model="gpt-image-1",
prompt="...",
size="1024x1024",
quality="high", # "low", "medium", "high", "auto"
n=1,
)
# Editing (multi-image input!)
edit = client.images.edit(
model="gpt-image-1",
image=[open("photo1.jpg", "rb"), open("photo2.jpg", "rb")], # multi-image
prompt="Combine these two scenes into one image.",
)gpt-image-1 supports:
- Multi-image input
- Mask-based editing (paint over region to regenerate)
- Reference images for style transfer
- More fine-grained control than DALL-E 3
Variations (DALL-E 2)
var = client.images.create_variation(
model="dall-e-2",
image=open("original.png", "rb"),
n=2,
size="1024x1024",
)DALL-E 3 doesn’t support variations; use DALL-E 2 or gpt-image-1.
7. Image edits with mask
edit = client.images.edit(
model="gpt-image-1", # or "dall-e-2"
image=open("original.png", "rb"),
mask=open("mask.png", "rb"), # PNG with transparent area = edit region
prompt="Replace this area with a starry sky.",
n=1,
size="1024x1024",
)The mask is a PNG where transparent pixels mark the region to regenerate.
8. Audio input
GPT-4o-audio (chat with audio)
import base64
audio_b64 = base64.b64encode(open("question.wav", "rb").read()).decode()
resp = client.chat.completions.create(
model="gpt-4o-audio-preview",
modalities=["text", "audio"],
audio={"voice": "alloy", "format": "wav"},
messages=[
{"role": "user", "content": [
{"type": "input_audio",
"input_audio": {"data": audio_b64, "format": "wav"}},
]},
],
)
# Text response
print(resp.choices[0].message.content)
# Audio response
audio_b64_out = resp.choices[0].message.audio.data
with open("answer.wav", "wb") as f:
f.write(base64.b64decode(audio_b64_out))Supported formats: WAV, MP3, OGG, FLAC, AAC, M4A, PCM, …
Pricing: audio tokens cost more than text tokens; see openai-models-and-capabilities.
Whisper transcription (most common ASR path)
transcript = client.audio.transcriptions.create(
file=open("meeting.mp3", "rb"),
model="whisper-1",
response_format="json", # "json", "verbose_json", "srt", "vtt", "text"
language="en", # ISO-639-1 hint (optional but improves accuracy)
prompt="Acme Corp meeting...", # context hint
temperature=0,
)
print(transcript.text)response_format="verbose_json" adds word-level timestamps and segment info:
transcript = client.audio.transcriptions.create(
file=open("audio.mp3", "rb"),
model="whisper-1",
response_format="verbose_json",
timestamp_granularities=["word", "segment"],
)
for word in transcript.words:
print(word.word, word.start, word.end)Whisper limits:
- Max file size: 25 MB
- For longer audio, chunk client-side (sox / ffmpeg) and stitch transcripts
GPT-4o-transcribe (alternative ASR)
transcript = client.audio.transcriptions.create(
file=open("audio.mp3", "rb"),
model="gpt-4o-transcribe", # or "gpt-4o-mini-transcribe"
response_format="json",
)Better accuracy on some languages and acoustic conditions; varies by use case. Test both.
Translation
translation = client.audio.translations.create(
file=open("japanese.mp3", "rb"),
model="whisper-1",
)
print(translation.text) # English textAlways outputs English; supports many input languages.
9. Audio output (TTS)
# Standard quality (faster, cheaper)
speech = client.audio.speech.create(
model="tts-1",
voice="nova", # alloy, echo, fable, onyx, nova, shimmer
input="Hello, this is a test.",
response_format="mp3", # mp3, opus, aac, flac, wav, pcm
speed=1.0, # 0.25 to 4.0
)
with open("out.mp3", "wb") as f:
f.write(speech.content) # or speech.read() in older SDK
# HD quality
speech = client.audio.speech.create(model="tts-1-hd", voice="nova", input="...")
# GPT-4o-TTS (newer, more expressive)
speech = client.audio.speech.create(model="gpt-4o-tts", voice="nova", input="...")Voices (vary by model):
tts-1/tts-1-hd:alloy,echo,fable,onyx,nova,shimmergpt-4o-tts/ Realtime: above +ash,coral,sage,verse, plus newer additions
Speed range: 0.25 (very slow) to 4.0 (very fast). Default 1.0.
10. Realtime API for bidirectional audio
For low-latency voice conversations (sub-second response), use the Realtime API instead of TTS + transcription. See realtime-api-openai.
11. Vision + tool use
Vision input combines with tool calling:
client.chat.completions.create(
model="gpt-5",
messages=[
{"role": "user", "content": [
{"type": "text", "text": "What's the weather where this photo was taken?"},
{"type": "image_url", "image_url": {"url": photo_url}},
]},
],
tools=[get_weather_tool],
)Model identifies location from image, calls get_weather. Powerful for grounded visual tasks.
12. Vision + structured output
class Receipt(BaseModel):
merchant: str
date: str
total: float
line_items: list[dict]
resp = client.chat.completions.parse(
model="gpt-5",
messages=[
{"role": "user", "content": [
{"type": "text", "text": "Extract receipt data."},
{"type": "image_url", "image_url": {"url": receipt_url}},
]},
],
response_format=Receipt,
)
receipt: Receipt = resp.choices[0].message.parsedStrict-mode structured output works fine with vision input. Standard pattern for OCR → structured.
13. Common pitfalls
detail: "high"on a 4K image — easy to hit 5,000+ tokens per image without realizing. Pre-resize to ~1024 longest side, or usedetail: "low".- URL must be publicly accessible — OpenAI fetches server-side. Auth headers, signed URLs without long enough validity, localhost URLs, all fail.
- HEIC iPhone photos — not supported. Convert to JPEG client-side.
- GIF first-frame-only — animated GIFs analyze only frame 0. Pre-extract a representative frame.
- Whisper 25 MB cap — chunking is on you. Use
pydub/ffmpeg. - Whisper hallucinations on silence — pre-trim silent leading/trailing audio; gives “Thanks for watching” or similar artifacts.
- TTS speed > 1.5 sounds robotic — past 1.5x, audio quality degrades. For really fast playback, post-process with audio library.
- DALL-E 3 prompt rewriting — your verbatim prompt may not be used. Check
revised_prompt. To disable:"I NEED to test how the tool works with extremely simple prompts. DO NOT add any detail, just use it AS-IS: <your prompt>"(community hack; not officially documented). - Image edit mask must be PNG with alpha channel — JPEG masks fail.
- Mixing audio chat with text-only models — GPT-5 doesn’t accept audio input directly (use Realtime or 4o-audio). Trying to send
input_audioto GPT-5 errors.
14. Comparison to other vendors
| Aspect | OpenAI | Claude | Gemini |
|---|---|---|---|
| Image input formats | PNG, JPEG, WebP, GIF | PNG, JPEG, GIF, WebP | PNG, JPEG, WebP, HEIC, HEIF |
| URL input | ✅ (publicly accessible) | ✅ | ❌ (must upload via Files API) |
| Detail / sizing param | detail: low/high/auto | Auto (anthropic-resize opt) | media_resolution: low/medium/high |
| Token cost (1024×1024) | 765 (high) / 85 (low) | ~1,500 - 2,000 | 1,032 (4 tiles) |
| Native PDF | ❌ (convert to image) | ✅ (document blocks) | ✅ (native) |
| Audio input | GPT-4o-audio + Realtime | ❌ | ✅ (native) |
| Audio output | TTS-1 / -HD / GPT-4o-TTS / Realtime | ❌ | TTS variants (Live API) |
| Image generation | DALL-E 3 / gpt-image-1 | ❌ | Imagen / Nano Banana |
| Native video | ❌ (frames only) | ❌ | ✅ (native, up to 1h) |
15. Further reading
- Vision guide — image input details
- Images guide — DALL-E + gpt-image-1
- Audio guide — Whisper + TTS
- GPT-4o-audio guide — chat with audio
- DALL-E 3 system card — safety + capability detail
- Whisper paper — ASR architecture