Stream Speech With Timestamps
Synthesize speech and stream it back together with word-level speech marks, for text highlighting, captions and audio-text synchronization while the audio is still arriving.
The response is a Server-Sent Events stream. Each speech.chunk event
carries a Base64-encoded run of audio, the speech marks that became
final with it, or both - a chunk may carry only one of the two, and the
last chunk of a stream is often marks-only. A terminal speech.done
event ends the stream; there is no [DONE] sentinel. Ignore any event
type you do not recognize, so that new event types do not break your
integration.
Speech-mark times are absolute milliseconds from the start of the
synthesis, so concatenate the audio chunks into one stream and apply the
marks against that single timeline. Which chunk a mark arrives on is a
delivery detail and carries no meaning. Times stay correct for every
output_format: changing the codec or sample rate does not change the
duration.
Every model this route accepts produces speech marks here. The default
simba-3.0, simba-3.2 and simba-3.5 stream marks word by word
alongside the audio. The legacy simba-english and simba-multilingual models,
still selectable on a workspace pinned before API version 2026-09-21,
synthesize a sentence at a time, so their marks arrive a sentence at a
time with that sentence’s audio; from that version on they return 400
model_retired on every synthesis route, and from 2026-11-21 both ids
are served by our current models.
For Base64-encoded audio and speech marks in one non-streamed JSON
response, use POST /v1/audio/speech.
Authentication
Enter your API key with the Bearer prefix, e.g. ‘Bearer sk_…’.
Headers
Selects the audio container/codec carried inside the events when
output_format is not set in the request body. The selected media
type is echoed on the Speechify-Audio-Content-Type response
header, since the response’s own Content-Type is text/event-stream.
Request
Plain text or SSML to be synthesized to speech.
Up to 20,000 characters of spoken text, the characters the request is billed for: SSML tags do not count. The input as sent, markup included, may be up to 32,000 characters.
Refer to https://docs.speechify.ai/docs/api-limits for the input size limits.
SSML sets pauses (<break>), speaking rate, pitch and volume (<prosody>) and pronunciation (<sub alias>): see https://docs.speechify.ai/build/guides/text-to-speech/ssml for each tag. Emotion styles and emphasis are applied only by the Simba 1.6 models, until 2026-11-21; simba-3.0, simba-3.2 and simba-3.5 accept those tags without applying them.
Id of the voice to be used for synthesizing speech. Refer to /v1/voices endpoint for available voices
Language of the input. Follow the format of an ISO 639-1 language code and an ISO 3166-1 region code, separated by a hyphen, e.g. en-US. Please refer to the list of the supported languages and recommendations regarding this parameter: https://docs.speechify.ai/docs/language-support.
Model used for audio synthesis. Defaults to simba-3.0, which is streaming-native and multilingual: it officially supports English plus de-DE, es-ES, es-MX, fr-FR, it-IT and pt-BR, and routes each request to its English or its multilingual training based on language (falling back to the voice’s locale when language is omitted). simba-3.2 is the streaming-native model with the lowest TTFB and richest expressivity; it is English only, so a non-English voice returns 400. simba-3.5 is the recommended model: streaming-native, covering 48 languages under one id, English included, routed by language like simba-3.0; every voice works on it, cloned voices included.
The legacy Simba 1.6 models simba-english and simba-multilingual are retired from API version 2026-09-21: naming one returns 400 model_retired. Pinning your API version to a date before 2026-09-21 keeps them on their Simba 1.6 training until 2026-11-21; from then both ids are served by our current models on every API version that can still name them. Migrate to simba-3.2 (English), simba-3.0 or simba-3.5 before then; call GET /v1/audio/models to see the set your workspace can select today.
The output audio format as a codec_sampleRate_bitrate string. Takes precedence over the Accept header when set, so you can request formats the Accept enum does not cover (e.g. pcm_16000, ulaw_8000). wav_* formats are not supported on streaming - use POST /v1/audio/speech for wav.
Optional. How to pronounce words and phrases in input: each entry gives a text and either the phones to say it with (ipa) or plain text to say in its place (alias). Every entry applies to the whole request.
Supported only in the languages a model’s entry in GET /v1/audio/models lists in pronunciation_languages, which does not include English yet: any other model or language returns 400 pronunciations_unsupported. An empty array is accepted by every model in every language. At most 200 entries. A malformed entry, a phone outside the model’s phone set, an ipa entry whose text is more than one word, or two entries for the same text returns 400 validation_failed, with fields naming each entry (for example pronunciations[3].ipa), before anything is synthesized or billed. So does an input longer than 20,000 characters once every alias stands in for its text.
Entries match case-insensitively unless case_sensitive is true, as whole words, except in Chinese, Japanese, Korean, Thai, Lao, Khmer and Burmese, where they match anywhere in the text; the longest matching entry wins, and an entry takes precedence over the model’s own pronunciation. Pronunciations are not billed, and speech marks still index input exactly as sent. See https://docs.speechify.ai/build/guides/text-to-speech/pronunciation.
Optional. A stable, opaque identifier for the end user this request is made for: a hash of your own user id or an opaque id, never an email address or other personal data. It is recorded with the request even under zero data retention, and your workspace can be given per-end-user limits and a block list keyed on it. See https://docs.speechify.ai/build/guides/concepts/safety-identifiers.
Response headers
Unique identifier for this request, present on every response (2xx and
non-2xx alike). If the caller sends a Speechify-Request-Id request
header the server echoes it back (sanitized and length-capped) so one
logical request can be traced end-to-end; otherwise the server generates
a fresh value. Log it on every response and quote it in support requests
- it is the stable handle that ties your observation to Speechify’s
server-side logs, and it matches the
request_idfield in the error envelope.
The legacy alias X-Request-ID carries the same value and is still
accepted on requests, until 2027-07-24. Prefer the un-prefixed name
(RFC 6648).
Request-rate budget: the maximum number of requests in the current
window (the bucket capacity). The IETF-draft un-prefixed name; the
legacy alias X-RateLimit-Limit carries the same value. Rides every
response.
Request-rate budget: requests left in the current window. Legacy
alias: X-RateLimit-Remaining.
Request-rate budget: integer delta-seconds until the window fully
refills (same unit as Retry-After). Legacy alias:
X-RateLimit-Reset.
How much of the wait for this response was Speechify’s, as W3C Server
Timing metrics in milliseconds. ttfb runs from the request reaching
the API to the first response byte, which on a streaming response is the
first audio. model is the model’s own time to first audio (the whole
synthesis on a batch response) and is omitted when it was not measured.
Whatever your client measures beyond ttfb is network distance and your
own stack. Readable from browsers.
Media type of the audio carried inside the events (for example
audio/mpeg), which the response’s own text/event-stream
Content-Type cannot express.
Response
A Server-Sent Events stream of speech.chunk events followed by one
terminal speech.done event. A failure after the stream has started,
including a content_policy_violation refusal, is delivered as a
speech.error event carrying the standard error envelope, because
the status code is already committed.
The transport is text/event-stream: each event is an
event:/data: pair whose data is one JSON payload matching the
schema below. The payload’s type field mirrors the event: name,
so the stream is also parseable from data: lines alone. Ignore
event types you do not recognize.
A run of synthesized audio, the speech marks that became final with it, or both - a chunk may carry only one of the two, and the last chunk of a stream is often marks-only. Mark times are absolute milliseconds from the start of the synthesis: concatenate the audio chunks into one stream and apply the marks against that single timeline. Which chunk a mark arrives on is a delivery detail and carries no meaning.
Terminal event: the synthesis completed and no further events follow.
There is no [DONE] sentinel.
Terminal event carrying the standard error envelope, emitted when a
failure happens after the stream has started and the status code is
already committed: an upstream fault (upstream_failure) or a content
policy refusal (content_policy_violation).