The TTS API: Generating Speech
On this page
Update November 2, 2026 — Audio models. Several models in this course are deprecated:
tts-1,tts-1-hd, andgpt-4o-mini-ttswill shut down on January 6, 2027 (successor:gpt-realtime-2.1-mini, which the OpenAI TTS guide states is not supported by/v1/audio/speech: speech synthesis migration moves to the Realtime API);gpt-realtime,gpt-4o-realtime, and legacy audio models on January 20, 2027 (gpt-realtime-2.1,gpt-realtime-2.1-mini,gpt-audio-1.5);whisper-1,gpt-4o-transcribe, and/v1/audio/translationson February 26, 2027 (gpt-transcribe,gpt-live-transcribe,v1/realtime/translations). For full-duplex voice, GPT-Live 1 has been available in the API since September 10, 2026. Lesson corrected November 3, 2026:response.stream_to_file()on the result ofspeech.create()is deprecated by the SDK (“Due to a bug, this method doesn’t actually stream”) → usewith_streaming_response, as in the official guide; models and voices updated (gpt-4o-mini-ttsand its voice instructions, 13 voices includingmarinandcedar); new section on migrating togpt-realtime-2.1-minibefore January 6, 2027, based on the Realtime conversations guide. Sources: Text to speech · Create speech · Realtime conversations · Deprecations.
The TTS API: Generating Speech
OpenAI’s Text-to-Speech (TTS) API converts text into high-quality audio. Unlike the Realtime API, which handles complete voice conversations, the TTS API is a single-shot service: you send text, and you receive an audio file. It’s the ideal tool for generating announcements, voice notifications, audiobooks, or accessible content.
Your First TTS Call
Generating speech is remarkably simple:
from openai import OpenAI
from pathlib import Path
client = OpenAI()
speech_file = Path("welcome.mp3")
with client.audio.speech.with_streaming_response.create(
model="gpt-4o-mini-tts",
voice="coral",
input="Welcome to Corsen Academy. "
"Your training on voice agents starts now.",
instructions="Speak in a warm, measured tone.",
) as response:
response.stream_to_file(speech_file)
The API returns an audio stream that you can save directly or stream to a player. Use with_streaming_response, as shown here: calling stream_to_file() on the result of a plain speech.create() is deprecated by the SDK, which notes that it doesn’t actually stream the audio.
Available Models
The three Audio API models are deprecated and will shut down on January 6, 2027: this is the transition period during which you’ll still use them.
| Model | Specificity | Voices |
|---|---|---|
gpt-4o-mini-tts | Accepts instructions: accent, emotion, intonation, speed, tone, whispering | 13 voices, including marin and cedar, recommended for quality |
tts-1 | Lower latency, lower quality | 9 voices: alloy, ash, coral, echo, fable, onyx, nova, sage, shimmer |
tts-1-hd | Higher quality than tts-1, higher latency | The same 9 voices |
Instructions don’t work with tts-1 and tts-1-hd. The speed parameter, from 0.25 to 4.0, adjusts the rate with all models. According to the guide, voices are optimized for English: listen to them in French on OpenAI.fm before choosing. OpenAI’s usage policies also require you to clearly inform your users that the voice they hear is generated by AI.
Output Formats
The API produces six formats, selected with response_format:
| Format | Usage |
|---|---|
mp3 (default) | General use |
opus | Internet streaming and communication, low latency |
aac | Digital compression, preferred by YouTube, Android, and iOS |
flac | Lossless compression, for archiving |
wav | Uncompressed, to avoid decoding overhead |
pcm | Raw samples at 24 kHz, 16-bit signed, no header |
for fmt in ("mp3", "opus", "aac", "flac", "wav", "pcm"):
with client.audio.speech.with_streaming_response.create(
model="gpt-4o-mini-tts",
voice="marin",
input="Text to convert",
response_format=fmt,
) as response:
response.stream_to_file(f"example.{fmt}")
For a voice agent built on the Realtime API, the pcm format is most convenient: its 24 kHz, 16-bit samples match the Realtime API’s audio/pcm format at 24 kHz.
Generating Audio in Batches
For large volumes of text, process segments in parallel:
import asyncio
from openai import AsyncOpenAI
async_client = AsyncOpenAI()
async def generate_speech_batch(segments: list[dict]) -> list[bytes]:
"""Generate audio for multiple segments in parallel."""
async def generate_one(segment):
response = await async_client.audio.speech.create(
model="gpt-4o-mini-tts",
voice=segment.get("voice", "alloy"),
input=segment["text"],
response_format="mp3"
)
return await response.aread()
tasks = [generate_one(seg) for seg in segments]
return await asyncio.gather(*tasks)
# Usage
segments = [
{"text": "Chapter one. Introduction to voice agents.", "voice": "onyx"},
{"text": "Voice agents transform human-computer interaction.", "voice": "onyx"},
{"text": "In this chapter, we'll explore the fundamentals.", "voice": "onyx"},
]
audio_chunks = asyncio.run(generate_speech_batch(segments))
# Concatenate files
from pydub import AudioSegment
import io
combined = AudioSegment.empty()
for chunk in audio_chunks:
segment = AudioSegment.from_mp3(io.BytesIO(chunk))
combined += segment
combined.export("chapter_1.mp3", format="mp3")
Character Limit and Splitting
The TTS API has a limit of 4,096 characters per request. For longer texts, split intelligently:
def split_text_for_tts(text: str, max_chars: int = 4000) -> list[str]:
"""Split text into segments respecting TTS limits."""
sentences = text.replace(". ", ".\n").split("\n")
segments = []
current = ""
for sentence in sentences:
if len(current) + len(sentence) + 1 > max_chars:
if current:
segments.append(current.strip())
current = sentence
else:
current += " " + sentence if current else sentence
if current:
segments.append(current.strip())
return segments
After January 6, 2027: Making a Realtime Model Speak
OpenAI designates gpt-realtime-2.1-mini as the replacement for the three models, and warns: it’s not a model for the /v1/audio/speech endpoint; migration changes the workflow. You go through a Realtime session. The Realtime conversations guide shows how to get a response without context, which ignores the conversation and follows only its instructions: this is the way to make it say a given text.
import base64
import json
import os
import wave
import websocket # pip install websocket-client
TEXT = "Welcome to Corsen Academy. Your training on voice agents starts now."
ws = websocket.WebSocket()
ws.connect(
"wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1-mini",
header=[f"Authorization: Bearer {os.environ['OPENAI_API_KEY']}"],
)
# Audio output only, 16-bit PCM at 24 kHz, with chosen voice
ws.send(json.dumps({
"type": "session.update",
"session": {
"type": "realtime",
"output_modalities": ["audio"],
"audio": {"output": {"format": {"type": "audio/pcm", "rate": 24000}, "voice": "marin"}},
},
}))
# A context-free response (empty input), which must say the text as-is
ws.send(json.dumps({
"type": "response.create",
"response": {
"input": [],
"output_modalities": ["audio"],
"instructions": f"Say exactly the following text, without adding anything:\n{TEXT}",
},
}))
audio, transcription = bytearray(), []
while True:
event = json.loads(ws.recv())
if event["type"] == "response.output_audio.delta":
audio += base64.b64decode(event["delta"])
elif event["type"] == "response.output_audio_transcript.delta":
transcription.append(event["delta"])
elif event["type"] == "error":
raise RuntimeError(event["error"]["message"])
elif event["type"] == "response.done":
break
ws.close()
with wave.open("welcome.wav", "wb") as wav:
wav.setnchannels(1)
wav.setsampwidth(2) # 16 bits
wav.setframerate(24000)
wav.writeframes(bytes(audio))
print("Spoken text:", "".join(transcription))
Two differences with the Audio API matter in production. The result is no longer guaranteed word-for-word: a Realtime model follows an instruction; it doesn’t read a file. So compare the transcription of what it said, returned in response.output_audio_transcript.delta, to the expected text, and regenerate if there’s a mismatch. Billing changes too: it follows the Realtime model’s rates, in text and audio tokens, listed on its spec sheet. Finally, a rejected key doesn’t prevent connection: it arrives just after, in an error event.
Key Takeaways
gpt-4o-mini-tts,tts-1, andtts-1-hdshut down on January 6, 2027;gpt-4o-mini-ttsis the only one that accepts voice instructions (instructions)- Record audio with
with_streaming_response:stream_to_file()on a plainspeech.create()is deprecated - Six output formats: MP3, Opus, AAC, FLAC, WAV, and raw PCM
- PCM format is ideal for integration with the Realtime API
- 4,096-character limit per request: split long texts at sentence boundaries
- Batch processing with the async client speeds up generation of large content
- Migration goes through a Realtime session with
gpt-realtime-2.1-mini: context-free response, 24 kHz PCM audio, transcription to compare against text