Understanding Speech-to-Text and Text-to-Speech Workflows

Ma Mahalakshmi V Updated 16 Sep 2026
8 min read ·Lesson 106 of 224

The Two Directions of Audio Processing

Audio applications built on the OpenAI SDK move information in one of two directions. Either you are converting sound into text (speech-to-text, often abbreviated STT), or you are converting text into sound (text-to-speech, abbreviated TTS). Everything else in this unit — timestamps, meeting transcription pipelines, voice assistants, long-audio handling — is built on top of these two primitives, so it is worth understanding precisely what each one does, what it does not do, and how the two combine into full applications.

Unit 7, Lesson 4 introduced these two capabilities at a surface level: how to send an audio file to a transcription endpoint and how to request synthesized speech from text. This lesson does not repeat that material. Instead, it treats STT and TTS as architectural building blocks and explains the workflow shape that every audio application in this unit will reuse: input capture, preprocessing, model call, post-processing, and output delivery.

A speech-to-text workflow is not just "send audio, get text back." In a real application, audio arrives in inconsistent formats, at inconsistent lengths, and sometimes with background noise or multiple speakers. The workflow around the API call — validating the file, choosing the right response format, deciding what to do with low-confidence segments — is where most of the engineering effort actually goes. The API call itself is often a single line of code; the surrounding pipeline is what makes it production-ready.

Text-to-speech has the mirror problem. Generating audio from a short string is trivial. Generating audio for a long article, choosing a voice appropriate for the content, streaming it to a client without making the user wait for the entire file, and handling playback failures — that is where the real design work lives.

Why These Are Modeled as Separate Endpoints

It is tempting to assume speech-to-text and text-to-speech are two settings on the same model, but they are implemented as distinct API operations because they solve fundamentally different problems with different input/output shapes:

  • STT takes binary audio data (a file) and produces structured text (a string, or a JSON object with metadata).
  • TTS takes text (a string) and produces binary audio data (a file, typically streamed).

Because the input and output types are inverted, the two operations use different endpoints, different parameters, and different response-handling code. Understanding this separation matters early, because a common beginner mistake is trying to reuse the same client call pattern for both, which leads to confusing errors about missing or invalid parameters.

The Speech-to-Text Workflow Shape

At a conceptual level, an STT pipeline looks like this:

  1. Acquire audio. This might be a file already on disk, a file uploaded by a user through a web form, or a recording captured live from a microphone.
  2. Validate and normalize. Check the file format, duration, and size against what the API accepts. Reject or convert files that do not meet requirements before making a network call.
  3. Submit for transcription. Call the transcription endpoint with the audio and any parameters that affect output shape, such as language hints or response format.
  4. Post-process the result. Depending on the response format, you might need to extract plain text, parse timestamps, or filter out low-confidence words.
  5. Deliver or store the result. Save the transcript, display it to a user, or pass it into a downstream process such as a summarization step.

Here is a minimal implementation of steps 3 and 4, using the OpenAI Python SDK:

from openai import OpenAI

client = OpenAI()

def transcribe_audio(file_path: str) -> str:
    with open(file_path, "rb") as audio_file:
        transcript = client.audio.transcriptions.create(
            model="gpt-5.6-terra",
            file=audio_file,
        )
    return transcript.text

result = transcribe_audio("meeting_snippet.wav")
print(result)

This function opens the audio file in binary mode ("rb"), because audio is binary data, not text — attempting to open it in text mode would corrupt it or raise a decoding error. The file object is passed directly to client.audio.transcriptions.create, and the SDK handles the multipart upload internally. The model parameter tells the API which transcription model to use; this course uses the placeholder "gpt-5.6-terra" consistently, but in your own code you would use whichever transcription-capable model your account has access to.

Note: Supported audio formats and maximum file sizes for the transcription endpoint are subject to change. Confirm the current list of accepted formats (commonly WAV, MP3, M4A, and others) and the file size ceiling against the official OpenAI API documentation before deploying to production.

The return value, transcript.text, is the simplest possible response shape: a plain string containing the recognized speech. Later lessons in this unit (particularly Lesson 4) cover richer response formats that include timestamps and per-segment metadata, but for a large number of use cases — voicemail transcription, dictation, quick notes — plain text is all you need.

The Text-to-Speech Workflow Shape

The TTS workflow is structurally the inverse:

  1. Prepare the text. Clean up formatting, split overly long text into chunks if needed, and decide which voice and audio format to use.
  2. Submit for synthesis. Call the speech endpoint with the text and voice parameters.
  3. Receive audio data. The response is binary audio content, not a JSON object with a .text field.
  4. Deliver the audio. Save it to a file, stream it to a client, or pass it directly to a playback device.
from pathlib import Path
from openai import OpenAI

client = OpenAI()

def synthesize_speech(text: str, output_path: str, voice: str = "alloy") -> None:
    response = client.audio.speech.create(
        model="gpt-5.6-terra",
        voice=voice,
        input=text,
    )
    Path(output_path).write_bytes(response.read())

synthesize_speech(
    text="Your appointment is confirmed for three o'clock on Thursday.",
    output_path="confirmation.mp3",
)

Notice the shape reversal compared to the transcription example: here, input is a text string rather than a file, and the thing you write to disk is the API response rather than something you read from disk. The voice parameter selects a pre-defined synthetic voice; different voices are tuned for different tonal qualities (warmer, more neutral, more energetic), and picking one appropriate to your application's context — a customer support bot versus a children's story narrator, for instance — is a real design decision, not a cosmetic one.

response.read() pulls the raw audio bytes out of the response object, and Path(output_path).write_bytes(...) writes them to disk as a binary file. If you instead tried write_text(...), you would corrupt the audio, because MP3 data is not valid text and cannot be encoded as a string.

Note: The exact response object interface (.read(), streaming helpers, or direct byte access) can differ between SDK versions. Verify the current recommended pattern for retrieving audio bytes from a speech response in the official SDK reference before shipping this code.

Comparing the Two Workflows

AspectSpeech-to-TextText-to-Speech
Input typeBinary audio fileText string
Output typeText (plain or structured JSON)Binary audio data
Typical triggerUser uploads or records audioApplication needs to "speak" a response
Common post-processingParsing timestamps, filtering low-confidence wordsChunking long text, selecting voice/format
Failure modesCorrupted/unsupported file, unclear audio, long duration limitsText too long, unsupported characters, streaming interruptions

This table is not meant as a memorization aid; it is meant to make explicit that these are not two flavors of the same call. They fail differently, they are validated differently, and they belong in different parts of an application's architecture — STT typically sits at an intake boundary (accepting user input), while TTS typically sits at an output boundary (producing a response).

When to Use Each, and When Not To

Speech-to-text is appropriate whenever your application needs to accept spoken input as a first-class input type: voicemail systems, meeting note-takers, accessibility features for users who prefer speaking to typing, and voice-driven command interfaces. It is not appropriate as a substitute for real-time voice interaction — if you need continuous, low-latency, two-way audio conversation, the batch transcription endpoint shown above is the wrong tool, because it is designed for complete audio files rather than a live stream. Lesson 7 in this unit discusses the Realtime API, which is built specifically for that use case, and Unit 14, Lesson 5 gave you a first, brief look at it.

Text-to-speech is appropriate whenever a text response needs to become audio: voice assistants, accessibility read-aloud features, IVR (interactive voice response) phone systems, or generating narration for video content. It is not a good fit for scenarios requiring extremely precise pronunciation control (medical terminology, proper nouns in unfamiliar languages) without additional handling, since synthetic voices, however good, can mispronounce unusual words. In such cases, some applications insert phonetic hints or a pronunciation dictionary before synthesis, or accept manual review of generated audio before it reaches end users.

Common Mistakes

Opening audio files in text mode, which causes garbled data or a UnicodeDecodeError before the request is even sent. Audio must always be opened with "rb" (read-binary), never "r". This happens because developers coming from text-processing backgrounds default to text-mode file handling out of habit.

Treating the TTS response as a string, which causes corrupted output files or attribute errors when code accidentally calls .text on a speech response object. TTS responses carry binary audio, not text, so always retrieve bytes explicitly (as shown with .read()) and write them with a binary-safe method.

Assuming one universal audio format works everywhere, which causes upload rejections or playback failures. Different endpoints and different client-side players support different format sets; always check both what the transcription API accepts as input and what your playback environment can decode.

Best Practices

Validate audio before making a network call. Check file extension, approximate size, and (when feasible) duration locally, so you fail fast with a clear error message rather than waiting on a network round trip only to get rejected.

Keep STT and TTS logic in separate, single-purpose functions, as shown in the two examples above. This keeps error handling specific to each direction and makes it straightforward to swap models or providers later without touching unrelated code.

Log the parameters used for each synthesis or transcription call (model, voice, language hint) alongside the result. When audio quality issues are reported later, this makes it possible to reproduce and debug the exact configuration that produced a given output.

0 Comments

Reviewed before they appear

No comments yet.

OpenAI SDK
Introduction to the OpenAI SDK Setting Up Python Creating an API Key Your First Call — client.responses.create() and response.output_text Understanding Billing, Credits, and What a Request Costs Why Responses Replaced Chat Completions Anatomy of a Request: model, input, and instructions Anatomy of a Response: The Typed output Array, Not Just Text Roles: User, Assistant, and Developer/System Choosing a Model, and Reading the Models Page Instead of Memorizing Names Instructions vs. Input Writing Prompts That Get Consistent Results Few-Shot Examples Reasoning Models and the reasoning Parameter Debugging a Prompt That Misbehaves Why Streaming Matters for User Experience stream=True and Iterating Over Events Handling the Event Types You Actually Care About Background Mode for Long-Running Jobs Project — Add Live Streaming to Your Chatbot The Problem With Parsing Free Text JSON Schema and Strict Mode Pydantic Models With the SDK's Parse Helpers Handling Refusals and Validation Failures Project — A Resume-to-JSON Extractor Working With input_image input_file, PDFs, and the Files API Image Generation Speech-to-Text and Text-to-Speech Project: A PDF Question-Answering Script What Function Calling Is Defining a Tool Schema The Full Loop Multiple Tools Errors, Timeouts, and Untrusted Arguments Project: A Weather Assistant Web Search File Search and Vector Stores Code Interpreter Remote MCP Servers and Connectors Project: A Research Assistant What an Embedding Is, Without the Maths Generating and Storing Embeddings Similarity Search From Scratch Hosted Vector Stores vs. Rolling Your Own A Small RAG App Over a Folder of Notes Agents vs. a Single API Call — When You Need One pip install openai Giving Agents Tools Handoffs and Multi-Agent Triage Guardrails and Approvals Tracing and Observing What Your Agent Did A Multi-Agent Support Desk Error Codes and What Each One Means Retries, Timeouts, and Backoff Rate Limits and Spend Limits Prompt Caching and Cost Optimisation The Batch API for Bulk Work Async Clients and Concurrency Moderation and Safety Best Practices Designing the App Backend With FastAPI Streaming to a Simple Frontend Deploying and a Cost/Safety Checklist Why Web Search Is Useful for Current Information Using the Web Search Tool with the Responses API Configuring Search Behavior for Application Use Cases Understanding Citations and Source Attribution Where to Go Next Building a Research Assistant with Web Search Combining Web Search with Structured Outputs Handling Conflicting or Low-Quality Web Sources Reducing Unsupported Claims with Grounded Generation Testing Freshness-Sensitive AI Answers Production Considerations for Web-Grounded Applications Understanding File Search and Retrieval-Augmented Generation Creating and Organizing Vector Stores Uploading Documents for Retrieval Connecting Vector Stores to Responses API Requests Designing Document Metadata and Filtering Strategies Building a PDF Question-Answering Application Improving Retrieval Quality With Better Document Preparation Handling Missing Evidence and Retrieval Failures Combining File Search With Web Search Building a Production Knowledge-Base Assistant What the Code Interpreter Tool Is Designed For Running Python-Based Analysis Through the OpenAI SDK Uploading Datasets for Analysis Analyzing CSV and Spreadsheet Data Generating Charts and Data Summaries Handling Generated Files and Downloadable Artifacts Building a Data-Analysis Assistant Combining Code Execution with Structured Outputs Validating Generated Calculations and Results Security and Sandbox Considerations for Code Execution Understanding Multimodal Input with the OpenAI SDK Sending Images to a Model Image Analysis from URLs and Uploaded Files Extracting Text and Information from Screenshots Building an Image-Question-Answering Application Combining Image Input with Structured Output Analyzing Multiple Images in One Request Handling Image Quality and Input Limitations Designing Multimodal Prompts for Reliable Results Building a Practical Vision-Powered Python Application Understanding Speech-to-Text and Text-to-Speech Workflows Transcribing Audio with the OpenAI SDK Working with Uploaded Audio Files Handling Timestamps and Transcription Metadata Building a Meeting Transcription Workflow Generating Spoken Responses from Text Handling Long Audio and Processing Failures Combining Audio with Text and Tool Calling Building an End-to-End Python Voice Application What Embeddings Are and When to Use Them Generating Embeddings With the OpenAI API Preparing Text for Embedding Comparing Vectors With Cosine Similarity Building a Simple Semantic Search Engine in Python Storing Embeddings in a Database Metadata Filtering for Semantic Search Chunking Strategies for Better Retrieval Evaluating Semantic Search Quality Building a Document Similarity Application When Batch Processing Makes Sense Designing Large-Volume AI Processing Pipelines Using Asynchronous Python with the OpenAI SDK Running Concurrent Requests Safely Controlling Concurrency and Avoiding Rate Limits Tracking Batch Job Progress Handling Partial Failures in Bulk Workloads Retrying Failed Items Without Duplicating Successful Work Designing Resumable AI Processing Jobs Building a Production Batch-Processing Pipeline Batch Processing Makes Sense Large-Scale AI Processing Pipelines Async Python with OpenAI SDK Safe Concurrent Requests Concurrency & Rate Limits Batch Progress Tracking Partial Failure Handling Safe Retry Handling Resumable AI Jobs Production Batch Pipeline System–User Data Separation Reusable App Instructions Prompt Templates & Variables Extraction & Classification Prompts Summarization & Transformation Prompts Explicit Output Requirements Prompt Version Management Prompt Testing & Evaluation Reusable Python Prompt Library API Key Security Secure API Key Storage Secure Secret Management Prompt Injection Prevention Trusted vs. Untrusted Content Tool Argument Validation Sensitive Data Handling Secure Logging AI Action Authorization Production AI Security Checklist Why AI Applications Need Evaluation Beyond Unit Tests Unit Testing OpenAI SDK Integration Code Mocking API Responses in Python Tests Testing Structured Outputs Against Schemas Testing Tool-Calling Workflows Building a Small Evaluation Dataset Measuring Accuracy, Consistency, and Failure Rates Regression Testing Prompts and Model Changes Human Evaluation Versus Automated Evaluation Creating a Repeatable Evaluation Pipeline AI Request Monitoring Token Cost Management Usage Metrics Design Reducing Model Calls Prompt & Context Optimization Model Selection & Optimization AI Caching Strategies Interactive Latency Optimization Usage Dashboards & Budget Alerts Performance & Cost Checklist Every API Call Starts Fresh Fixing API Statelessness Server-Side Conversation Memory Limits of Response Chaining What We're Building Conversation Memory Challenges Preparing an OpenAI SDK Application for Deployment Environment-Specific Configuration for Development and Production Deploying a Python AI Service with Docker Container Health Checks and Startup Configuration Managing Secrets in Cloud Deployments Background Workers for Long-Running AI Tasks Queues and Asynchronous Job Architectures Scaling AI Workloads Horizontally Monitoring Production Incidents and Failures Production Deployment Checklist for OpenAI SDK Applications Reusable OpenAI Service Classes AI Client Dependency Injection Typed AI Responses Python Configuration Management AI Request Decorators Centralized AI Error Handling Clean SDK Abstractions Reusable OpenAI Utilities Internal AI Python Libraries SDK Integration Maintenance Production AI Chatbot Document Q&A System Web Research Assistant Customer Support Agent AI Data Analysis Assistant Image Analysis App Meeting Transcription & Summary Semantic Document Search Multi-Tool AI Agent Production OpenAI SDK App Why "It Looked Fine When I Tested It" Isn't Enough Timing Note Status Note Pre-Decision Status Note Current Availability Note
Ask about this post
AI Ask about this post

Ask questions about Understanding Speech-to-Text and Text-to-Speech Workflows and get answers drawn from it.

Signed-in readers only.