Transcribing Audio with the OpenAI SDK

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

The Transcription Endpoint in Detail

The core call for converting audio into text is client.audio.transcriptions.create(). Lesson 1 introduced this call at a high level to establish the overall STT workflow shape. This lesson goes deeper into the parameters that control transcription behavior, the response formats available, and the practical details of getting reliable results from real-world audio rather than clean sample files.

At minimum, the call requires two things: a model identifier and a file — an open, binary-mode file handle (or an equivalent byte stream) containing the audio. Everything else is optional, but the optional parameters are what separate a toy example from a production-grade transcription feature.

from openai import OpenAI

client = OpenAI()

with open("interview.mp3", "rb") as audio_file:
    transcript = client.audio.transcriptions.create(
        model="gpt-5.6-terra",
        file=audio_file,
        language="en",
        prompt="This is a technical interview about backend engineering.",
        temperature=0.0,
    )

print(transcript.text)

Each optional parameter here does real work:

  • language tells the model which language to expect in the audio, using an ISO-639-1 code ("en" for English, "es" for Spanish, and so on). Providing this when you know the language in advance improves both accuracy and latency, because the model does not need to spend effort detecting the language from the audio itself.
  • prompt is a short piece of context text that biases the transcription toward expected vocabulary. It does not become part of the output; instead, it primes the model, which is especially useful for domain-specific jargon, acronyms, or proper nouns that the model would otherwise misrecognize or misspell. For example, providing a prompt that mentions "Kubernetes" and "PostgreSQL" makes the model more likely to spell those correctly if they appear in the audio.
  • temperature controls the randomness of the underlying decoding process. For transcription, lower values (including 0.0) generally produce more deterministic, consistent output for a given audio input, which matters when you need reproducible transcripts for testing or auditing.

Note: The exact set of supported languages, the effect strength of the prompt parameter, and default values for temperature can change between model versions. Confirm current behavior against the official OpenAI API documentation before relying on specific parameter defaults in production.

Why the Model Needs Context, Not Just Audio

A common misconception is that a transcription model works purely acoustically — that it hears sounds and maps them to phonemes and then to words, with no higher-level understanding involved. In practice, models like this incorporate language modeling: they use the statistical structure of language to disambiguate audio that is acoustically ambiguous. This is exactly why the prompt parameter is effective — it shifts the model's expectations about what kind of language is likely to appear, which changes how ambiguous audio segments get resolved into text.

This has a practical implication for how you should think about transcription quality: it is not purely a function of "how good is the microphone" or "how good is the model." It is also a function of how much relevant context you give the model about the content it is about to hear. Two identical audio files with different prompt values can produce measurably different transcription quality on domain-specific terms.

Response Formats

The transcription endpoint supports multiple response formats, controlled by the response_format parameter. This matters because different downstream uses need different levels of structure:

from openai import OpenAI

client = OpenAI()

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

plain = transcribe_with_format("voicemail.wav", response_format="text")
structured = transcribe_with_format("voicemail.wav", response_format="verbose_json")

print(plain)
print(structured.text)
print(structured.duration)

With response_format="text", you get back the plain transcribed string with essentially no extra metadata. With response_format="verbose_json", you get a structured object that includes the transcribed text plus metadata such as detected language and audio duration, and — as covered in depth in Lesson 4 — per-segment and per-word timing information. Choosing the right format up front avoids having to re-request the same audio just to get metadata you did not originally ask for, which matters because re-transcription costs both time and money.

Note: Available response_format values (for example text, json, verbose_json, and any subtitle-oriented formats) and exactly which fields each one populates are implementation details that can evolve. Check the current documentation for the full list and field names before building a parser against a specific format.

A Practical Transcription Function with Error Handling

Real applications need to handle failures gracefully rather than letting an unhandled exception crash a request. Here is a more complete function that wraps the API call with basic error handling and input validation:

from pathlib import Path
from openai import OpenAI, APIError

client = OpenAI()

SUPPORTED_EXTENSIONS = {".mp3", ".mp4", ".mpeg", ".mpga", ".m4a", ".wav", ".webm"}


class TranscriptionError(Exception):
    """Raised when a transcription request cannot be completed."""


def transcribe_file(file_path: str, language: str | None = None) -> str:
    path = Path(file_path)

    if not path.exists():
        raise TranscriptionError(f"Audio file not found: {file_path}")

    if path.suffix.lower() not in SUPPORTED_EXTENSIONS:
        raise TranscriptionError(
            f"Unsupported audio extension '{path.suffix}'. "
            f"Expected one of: {sorted(SUPPORTED_EXTENSIONS)}"
        )

    try:
        with path.open("rb") as audio_file:
            transcript = client.audio.transcriptions.create(
                model="gpt-5.6-terra",
                file=audio_file,
                language=language,
            )
    except APIError as exc:
        raise TranscriptionError(f"Transcription request failed: {exc}") from exc

    return transcript.text

This function does three things worth calling out explicitly. First, it validates that the file exists and has a plausible extension before opening a network connection — failing fast on obvious problems saves an unnecessary API call and gives the caller a clearer error message than a generic HTTP failure would. Second, it defines a custom TranscriptionError exception so that calling code can catch a single, well-defined error type rather than needing to know about the SDK's internal exception hierarchy. Third, it catches APIError (the SDK's base exception for API-level failures) and re-raises it as the custom error using raise ... from exc, which preserves the original traceback for debugging while presenting a clean, application-specific error type to the rest of the codebase.

The language: str | None = None type hint communicates that language detection is optional — when None is passed through to the API call, most SDK implementations either omit the parameter or let the API auto-detect the language, though you should confirm the SDK's exact handling of None values for optional parameters if you rely on this behavior.

Testing Transcription Logic Without Calling the API

You should not call a real API inside a unit test — it is slow, costs money, and makes tests non-deterministic (network issues, rate limits). Instead, use dependency injection: pass in a fake client that mimics the shape of the real one.

class FakeTranscript:
    def __init__(self, text: str):
        self.text = text


class FakeTranscriptions:
    def __init__(self, text: str):
        self._text = text

    def create(self, **kwargs):
        assert "file" in kwargs
        assert kwargs["model"] == "gpt-5.6-terra"
        return FakeTranscript(self._text)


class FakeAudio:
    def __init__(self, text: str):
        self.transcriptions = FakeTranscriptions(text)


class FakeClient:
    def __init__(self, text: str):
        self.audio = FakeAudio(text)


def transcribe_with_client(client, file_obj, language=None) -> str:
    transcript = client.audio.transcriptions.create(
        model="gpt-5.6-terra",
        file=file_obj,
        language=language,
    )
    return transcript.text


def test_transcribe_with_client_returns_text():
    fake_client = FakeClient(text="hello from the fake model")
    result = transcribe_with_client(fake_client, file_obj=object(), language="en")
    assert result == "hello from the fake model"
    print("PASS: transcribe_with_client_returns_text")


test_transcribe_with_client_returns_text()

This test rewrites transcribe_file as transcribe_with_client, which accepts the client as a parameter instead of constructing one internally — this is the essence of dependency injection, and it is what makes the function testable at all. FakeClient, FakeAudio, and FakeTranscriptions mirror the attribute structure of the real SDK objects (client.audio.transcriptions.create(...)) just closely enough to satisfy the code under test, without making any network call. The assert statements inside FakeTranscriptions.create double as lightweight verification that the calling code passed the parameters you expect, which catches regressions if someone later changes the call signature without updating the test.

Common Mistakes

Not specifying a language when it is known in advance, which causes unnecessary language-detection overhead and occasionally lower accuracy on short or ambiguous audio clips. If you already know your users will speak a specific language, pass it explicitly.

Ignoring the prompt parameter for domain-specific audio, which causes systematic misspellings of technical terms, product names, or acronyms. Since the model has no way to know your domain's vocabulary unless you tell it, a short priming prompt meaningfully improves output quality for jargon-heavy audio.

Calling the real API inside unit tests, which causes slow, flaky, and costly test suites. Use the fake-object dependency injection pattern shown above for any test of your surrounding logic, and reserve real API calls for a small number of manual or integration-tier checks.

Best Practices

Always validate file existence and extension before calling the API. This produces faster, clearer failures and avoids spending API quota on requests that were never going to succeed.

Choose response_format deliberately based on downstream needs, rather than defaulting to whatever the first example you copied used. If you need timestamps, request verbose_json from the start rather than re-processing the same audio later.

Wrap SDK exceptions in a domain-specific exception type, as shown with TranscriptionError. This decouples the rest of your application from the specific exception hierarchy of the SDK, which makes it easier to swap implementations or SDK versions later without a cascading rewrite of error-handling code.

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 Transcribing Audio with the OpenAI SDK and get answers drawn from it.

Signed-in readers only.