API: stream speech marks with POST /v1/audio/stream/with-timestamps

A new endpoint streams word-level speech marks alongside audio, so text highlighting, captions, and audio-text sync no longer need the batch POST /v1/audio/speech round trip. POST /v1/audio/stream is unchanged - same request body, still plain audio.

$curl -N -X POST https://api.speechify.ai/v1/audio/stream/with-timestamps \
> -H "Authorization: Bearer $SPEECHIFY_API_KEY" \
> -H "Content-Type: application/json" \
> -d '{
> "input": "Hello world.",
> "voice_id": "oliver",
> "model": "simba-3.2"
> }'

The response is a Server-Sent Events stream:

event: speech.chunk
data: {"audio":"<base64>","speech_marks":[{"type":"word","start":0,"end":5,"start_time":0,"end_time":512,"value":"Hello"}]}
event: speech.done
data: {"billable_characters_count":60,"audio_duration_ms":5350}
  • speech.chunk carries a Base64-encoded run of audio, the speech marks that became final with it, or both - either field may be absent, and the last chunk of a stream is often marks-only.
  • speech.done is terminal; there is no [DONE] sentinel.
  • speech.error carries the standard error envelope when a failure happens after the stream has started (the status code is already committed by then).
  • Ignore any event type you do not recognize, so new event types never break your integration.

Speech-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 with no meaning of its own, and times stay correct across every output_format.

Marks are produced by the streaming-native models: use simba-3.0 or simba-3.2. simba-english and simba-multilingual return 400 speech_marks_unsupported on this endpoint - use POST /v1/audio/speech for a non-streamed response with marks on any model.