timestamps: true on a POST /v1/audio/speech request to get, along with the audio, the time each token of your input is spoken and where that token sits in your text. Use it to highlight text as it is read, show captions in sync, or drive an avatar’s mouth. Timestamps work on long input, up to the usual limits of 5,000 characters or 50,000 when streamed. They also work on a streamed response and over the speech WebSocket, and they don’t change the price.
Get timestamps
Withoutstream, the response is JSON. response_format still selects the audio format, and the encoded audio is returned in that format in the audio field:
input is your text as you sent it, and timestamps lists one entry per token, in order.
Entries
Each entry is one token of your input:- Tokens, not words. Entries follow the model’s tokenizer, so a token can be part of a word: “Honestly” may come back as
Handonestly. To work with words, group entries by their input positions, for example joining entries with no space between one’send_idxand the next one’sstart_idx. - Only tokens with a letter or number. Punctuation, symbols, and control tags get no entry, so there can be gaps between entries, both in the text and in time.
- Positions count Unicode code points. Python string indexing matches them. JavaScript strings index UTF-16 code units, which differ for characters such as emoji, so slice
Array.from(input)instead ofinput.
Stream timestamps
Withstream: true (and response_format: "pcm", as for any streamed request), the response is Server-Sent Events (text/event-stream) instead of raw PCM. Each event is a data: line holding a JSON object, and the stream ends with data: [DONE]. An event with audio looks like this:
audio.datais 16-bit signed little-endian mono PCM ataudio.sample_rate. Join the chunks in order to get the full audio.usageholds running totals for the request so far: the characters of input, not counting control tags, and the audio sent.timestampsis present only on events that carry entries.
"end_ms": null, then again with its end. If both are known at once, it is sent once. Entries that arrive after the last audio come on a final event with usage and timestamps but no audio, just before [DONE].
Merge entries by index: a later copy of an entry replaces the earlier one.
The samples below save the audio to out.pcm as it arrives, keep the latest copy of each entry, and print the timestamps once the stream ends.
With text normalization
Text normalization is on by default, so the model speaks the normalized text:$42.50 is read as “forty-two dollars and fifty cents”. The entries are still on your input, and each one gains a normalized list:
- A token normalization left alone gets its entry as usual, with
"normalized": []. - A span normalization rewrote becomes one entry. Its
textis the span of your input, and it is timed from its first spoken token to its last.normalizedlists the spoken tokens, each withindex(counting from 0 within the span),text,start_ms, andend_ms.
The total is $42.50., the entry for $42.50 is:
index as usual.
To time the text exactly as written, set normalize: false. Entries then have no normalized field.
Over the WebSocket
On the speech WebSocket, settimestamps: true in start.request. The server then sends a timestamps event after the audio event its entries came with:
index.
Each turn is timed as if it were its own POST. A turn is the text you send before a flush, and the next turn starts after each flush or cancel:
indexcounts from 0.start_msandend_mscount from the start of the turn’s audio.start_idxandend_idxare positions in the turn’s text: itstextmessages joined, as sent.
Related
Create a speech
The POST endpoint, with every request field and the timestamps response schema.
Streaming TTS
Stream audio from a request, or stream text in over a WebSocket.
Text normalization
How the input is rewritten into spoken form before synthesis.
Speech WebSocket reference
Every client and server event, including
timestamps.