Transcribing Audio with the OpenAI SDK
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:
languagetells 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.promptis 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.temperaturecontrols the randomness of the underlying decoding process. For transcription, lower values (including0.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
promptparameter, and default values fortemperaturecan 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_formatvalues (for exampletext,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.