Skip to main content
Lesson 10 of 18 · Realtime API and Voice Agents

The TTS API: Generating Speech

Lesson 10 of 18 Updated November 3, 2026 Developer
On this page
  1. The TTS API: Generating Speech
  2. Your First TTS Call
  3. Available Models
  4. Output Formats
  5. Generating Audio in Batches
  6. Character Limit and Splitting
  7. After January 6, 2027: Making a Realtime Model Speak
  8. Key Takeaways

Update November 2, 2026 — Audio models. Several models in this course are deprecated: tts-1, tts-1-hd, and gpt-4o-mini-tts will 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/translations on 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 of speech.create() is deprecated by the SDK (“Due to a bug, this method doesn’t actually stream”) → use with_streaming_response, as in the official guide; models and voices updated (gpt-4o-mini-tts and its voice instructions, 13 voices including marin and cedar); new section on migrating to gpt-realtime-2.1-mini before 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.

ModelSpecificityVoices
gpt-4o-mini-ttsAccepts instructions: accent, emotion, intonation, speed, tone, whispering13 voices, including marin and cedar, recommended for quality
tts-1Lower latency, lower quality9 voices: alloy, ash, coral, echo, fable, onyx, nova, sage, shimmer
tts-1-hdHigher quality than tts-1, higher latencyThe 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:

FormatUsage
mp3 (default)General use
opusInternet streaming and communication, low latency
aacDigital compression, preferred by YouTube, Android, and iOS
flacLossless compression, for archiving
wavUncompressed, to avoid decoding overhead
pcmRaw 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, and tts-1-hd shut down on January 6, 2027; gpt-4o-mini-tts is the only one that accepts voice instructions (instructions)
  • Record audio with with_streaming_response: stream_to_file() on a plain speech.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