Handling Timestamps and Transcription Metadata

Ma Mahalakshmi V Updated 16 Sep 2026
7 min read ·Lesson 109 of 224

Why Plain Text Is Sometimes Not Enough

A plain transcript string answers the question "what was said," but many real applications need to answer a harder question: "what was said, and exactly when." Captioning a video requires knowing when each phrase should appear on screen. Searching a podcast archive for a specific quote requires jumping directly to the moment it was spoken. Auditing a call center recording requires correlating a flagged phrase with an exact timestamp for compliance review. None of these are possible with a bare string of text — they require structured metadata that ties words and phrases back to positions in the original audio.

The transcription endpoint can return this metadata, but only if you ask for it explicitly using the right response_format and timestamp_granularities settings. This lesson covers how that metadata is structured, how to request it, and how to work with it programmatically.

Requesting Timestamped Output

from openai import OpenAI

client = OpenAI()

with open("podcast_segment.mp3", "rb") as audio_file:
    transcript = client.audio.transcriptions.create(
        model="gpt-5.6-terra",
        file=audio_file,
        response_format="verbose_json",
        timestamp_granularities=["segment", "word"],
    )

print(transcript.text)
print(transcript.duration)

for segment in transcript.segments:
    print(f"[{segment.start:.2f}s - {segment.end:.2f}s] {segment.text}")

Two parameters make this work together. response_format="verbose_json" switches the API from returning a bare string to returning a structured object carrying the full transcript plus metadata fields such as duration, language, and (when requested) segments and words. timestamp_granularities is a list telling the API which levels of timing detail to include — "segment" for phrase-level or sentence-level chunks, "word" for individual word-level timing. Requesting both gives you the most complete metadata, at the cost of a larger response payload.

Each segment object exposes start and end fields representing seconds elapsed from the beginning of the audio, along with the text spoken during that window. This is exactly the structure needed to generate subtitle files, since subtitle formats fundamentally consist of "show this text from this time to that time" entries.

Note: The exact field names on segment and word objects (for example start, end, text, and any confidence-related fields), and whether timestamp_granularities requires verbose_json specifically, are details that can change between API versions. Verify the current schema against the official OpenAI API documentation before building a parser that depends on specific field names.

Word-Level Timestamps

Requesting "word" granularity gives you a finer-grained words list, where each entry represents a single recognized word and its precise timing:

for word_info in transcript.words:
    print(f"{word_info.word!r} spoken at {word_info.start:.2f}s")

Word-level timing is considerably more granular than segment-level timing and is useful for applications like karaoke-style caption highlighting (highlighting each word as it is spoken), precise audio editing (cutting a clip starting exactly at a specific word), or fine-grained search-and-jump features in a transcript viewer. It comes at a cost, though: word-level responses are larger, and — depending on the exact acoustic conditions of the source audio — word-level boundaries can occasionally be less reliable than segment-level boundaries, because pinpointing the exact millisecond a single short word starts is inherently harder than identifying the boundaries of a multi-second phrase.

Building a Subtitle File from Segment Data

A concrete, realistic use of timestamp metadata is generating a subtitle file in the SRT format, a widely supported plain-text subtitle standard.

def format_srt_timestamp(seconds: float) -> str:
    hours = int(seconds // 3600)
    minutes = int((seconds % 3600) // 60)
    secs = int(seconds % 60)
    millis = int(round((seconds - int(seconds)) * 1000))
    return f"{hours:02d}:{minutes:02d}:{secs:02d},{millis:03d}"


def build_srt(segments) -> str:
    lines = []
    for index, segment in enumerate(segments, start=1):
        start_ts = format_srt_timestamp(segment.start)
        end_ts = format_srt_timestamp(segment.end)
        lines.append(str(index))
        lines.append(f"{start_ts} --> {end_ts}")
        lines.append(segment.text.strip())
        lines.append("")  # blank line separates entries
    return "\n".join(lines)

format_srt_timestamp converts a floating-point seconds value into the HH:MM:SS,mmm format that the SRT standard requires, which is a plain but easy-to-get-wrong piece of arithmetic — note the use of % (modulo) to peel off hours, minutes, and seconds in sequence, and integer truncation via int(...) to avoid fractional hour or minute values leaking into the output. build_srt then loops over the transcript's segments, numbering each entry starting from 1 (as the SRT format requires), formatting its time range, and appending the spoken text. The blank line appended after each entry is not cosmetic — SRT parsers use blank lines as the delimiter between subtitle entries, so omitting it produces a file most players will fail to parse correctly.

This function takes segments as a plain parameter rather than a specific SDK type, which means it can be tested with lightweight fake objects instead of a real transcript response, and reused regardless of exactly how the segment data was obtained.

Filtering Low-Confidence Content

Some transcription responses include per-segment or per-word confidence-related signals (implementations vary in exactly how this is exposed — as an explicit probability field, a log-probability value, or a "no speech probability" indicator for segments that might be silence or noise misidentified as speech). When available, this information is valuable for automatically flagging transcript regions that likely need human review, rather than presenting the entire transcript with uniform, unearned confidence.

def filter_low_confidence_segments(segments, threshold: float = -1.0):
    """
    Returns (reliable_segments, flagged_segments) based on a log-probability
    style confidence field. Segments below the threshold are flagged for review.
    """
    reliable = []
    flagged = []
    for segment in segments:
        avg_logprob = getattr(segment, "avg_logprob", None)
        if avg_logprob is not None and avg_logprob < threshold:
            flagged.append(segment)
        else:
            reliable.append(segment)
    return reliable, flagged

This function uses getattr(segment, "avg_logprob", None) rather than direct attribute access (segment.avg_logprob) specifically because not every response format or API version is guaranteed to expose this field, and a direct attribute access would raise an AttributeError on a segment object that lacks it. Using getattr with a default makes the function resilient to that variation, treating a missing confidence field as "no information available" rather than crashing.

Note: Whether a confidence-style field is exposed at all, its exact name, and how to interpret its scale (log-probability, raw probability, or something else) are all details that vary by response format and can change over time. Confirm the current schema and semantics in official documentation before building automated review-flagging logic around a specific field.

Comparing Metadata Approaches

ApproachGranularityTypical use caseResponse size
Plain text (response_format="text")NoneSimple dictation, quick notesSmallest
verbose_json, segment-levelPhrase/sentenceSubtitles, chaptering, search-and-jumpMedium
verbose_json, word-levelIndividual wordKaraoke captions, precise clip editingLargest

Choosing the right granularity up front matters because requesting more metadata than you need adds response size and parsing complexity for no benefit, while requesting less than you need means a second, wasted API call later to get the missing detail. If you are building a captioning feature, segment-level timing is usually sufficient and considerably lighter-weight than word-level; reach for word-level only when the feature genuinely requires per-word precision.

Common Mistakes

Requesting response_format="text" and then trying to access .segments or .words, which fails because plain text responses simply do not carry that metadata — there is nothing to access. Always request verbose_json with the appropriate timestamp_granularities up front if you know you will need timing data.

Assuming timestamps are always perfectly accurate to the millisecond, which causes overly rigid downstream logic (for example, hard-cutting an audio clip exactly at a word boundary) to occasionally clip the very beginning or end of a word. Timestamp boundaries from any automatic speech recognition system carry some inherent margin of error; build a small buffer into time-sensitive audio editing operations rather than trusting boundaries as exact to the millisecond.

Hardcoding assumptions about which metadata fields exist, which causes AttributeError exceptions when a field is absent for a particular response format, language, or SDK version. Use defensive access patterns like getattr with a sensible default, as shown above, for any field that is not guaranteed to be present in every response.

Best Practices

Request only the timestamp granularity your feature actually needs. Segment-level timing is lighter and often sufficient; reserve word-level timing for features that specifically require per-word precision, such as word-highlighting caption players.

Separate metadata parsing from business logic, as shown with build_srt and filter_low_confidence_segments taking plain segment data rather than being tightly coupled to the exact SDK response object. This makes both functions independently testable and reusable if the underlying transcription source ever changes.

Treat confidence-related fields as advisory signals for human review, not as ground truth for automated decisions, especially in domains (legal, medical, financial) where a transcription error carries real consequences. Flag low-confidence segments for a human to check rather than silently trusting or silently discarding them.

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 Handling Timestamps and Transcription Metadata and get answers drawn from it.

Signed-in readers only.