Skip to main content
Set 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.
timestamps: true changes the response. Without stream, it is JSON with the audio base64-encoded inside it; with stream: true, it is Server-Sent Events. Writing the response body straight to a file produces an unplayable file. Decode the audio first.

Get timestamps

Without stream, the response is JSON. response_format still selects the audio format, and the encoded audio is returned in that format in the audio field:
The times are illustrative. 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 H and onestly. To work with words, group entries by their input positions, for example joining entries with no space between one’s end_idx and the next one’s start_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 of input.

Stream timestamps

With stream: 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.data is 16-bit signed little-endian mono PCM at audio.sample_rate. Join the chunks in order to get the full audio.
  • usage holds running totals for the request so far: the characters of input, not counting control tags, and the audio sent.
  • timestamps is present only on events that carry entries.
Entries arrive as the audio is generated. Each token is usually sent twice: first when it starts, with "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 text is the span of your input, and it is timed from its first spoken token to its last. normalized lists the spoken tokens, each with index (counting from 0 within the span), text, start_ms, and end_ms.
For the input The total is $42.50., the entry for $42.50 is:
The times are illustrative. When streamed, a rewritten entry is sent again as more of its tokens are spoken: merge by 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, set timestamps: true in start.request. The server then sends a timestamps event after the audio event its entries came with:
The entries are the same as on a streamed POST: merge them by 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:
  • index counts from 0.
  • start_ms and end_ms count from the start of the turn’s audio.
  • start_idx and end_idx are positions in the turn’s text: its text messages joined, as sent.

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.