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

1. Image input — supported models

ModelImage inputNotes
GPT-5 / GPT-5-miniMultimodal native
GPT-5-nanoLimitedSmaller capability
GPT-4o / GPT-4o-miniStrong baseline
GPT-4 Turbo VisionLegacy
o3 / o4-miniReasoning + vision
o3-miniLimited / partial
o1 / o1-proText-only
GPT-3.5Text-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

FormatSupported
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

ValueBehaviorToken cost
"low"Treats image as 512×512 thumbnail85 tokens flat regardless of input size
"high"Full-resolution tiles85 base + (170 × tile_count)
"auto" (default)Server picks based on image sizeVaries

High-detail tile math

Per platform.openai.com/docs/guides/vision:

  1. Image is scaled so the shortest side ≤ 768 px
  2. Then scaled so the longest side ≤ 2048 px
  3. Resulting image is split into 512×512 tiles
  4. 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 text

Always 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, shimmer
  • gpt-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.parsed

Strict-mode structured output works fine with vision input. Standard pattern for OCR → structured.

13. Common pitfalls

  1. detail: "high" on a 4K image — easy to hit 5,000+ tokens per image without realizing. Pre-resize to ~1024 longest side, or use detail: "low".
  2. URL must be publicly accessible — OpenAI fetches server-side. Auth headers, signed URLs without long enough validity, localhost URLs, all fail.
  3. HEIC iPhone photos — not supported. Convert to JPEG client-side.
  4. GIF first-frame-only — animated GIFs analyze only frame 0. Pre-extract a representative frame.
  5. Whisper 25 MB cap — chunking is on you. Use pydub / ffmpeg.
  6. Whisper hallucinations on silence — pre-trim silent leading/trailing audio; gives “Thanks for watching” or similar artifacts.
  7. TTS speed > 1.5 sounds robotic — past 1.5x, audio quality degrades. For really fast playback, post-process with audio library.
  8. 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).
  9. Image edit mask must be PNG with alpha channel — JPEG masks fail.
  10. Mixing audio chat with text-only models — GPT-5 doesn’t accept audio input directly (use Realtime or 4o-audio). Trying to send input_audio to GPT-5 errors.

14. Comparison to other vendors

AspectOpenAIClaudeGemini
Image input formatsPNG, JPEG, WebP, GIFPNG, JPEG, GIF, WebPPNG, JPEG, WebP, HEIC, HEIF
URL input✅ (publicly accessible)❌ (must upload via Files API)
Detail / sizing paramdetail: low/high/autoAuto (anthropic-resize opt)media_resolution: low/medium/high
Token cost (1024×1024)765 (high) / 85 (low)~1,500 - 2,0001,032 (4 tiles)
Native PDF❌ (convert to image)✅ (document blocks)✅ (native)
Audio inputGPT-4o-audio + Realtime✅ (native)
Audio outputTTS-1 / -HD / GPT-4o-TTS / RealtimeTTS variants (Live API)
Image generationDALL-E 3 / gpt-image-1Imagen / Nano Banana
Native video❌ (frames only)✅ (native, up to 1h)

15. Further reading