client.audio.speech({ ...params }) -> Speechify.GetSpeechResponse
-
-
-
Synthesize speech audio from text or SSML. Returns the complete audio file plus billing and speech-mark metadata in a single JSON response. For low-latency playback or long-form text, use POST /v1/audio/stream. Set
output_formatfor explicit sample-rate/bitrate control (e.g.pcm_16000orulaw_8000for telephony).
-
-
-
await client.audio.speech({ audio_format: "mp3", input: "Hello! This is the Speechify text-to-speech API.", model: "simba-3.2", voice_id: "geffen_32" });
-
-
-
request:
Speechify.GetSpeechRequest
-
requestOptions:
AudioClient.RequestOptions
-
-
client.audio.stream({ ...params }) -> core.BinaryResponse
-
-
-
Synthesize speech and stream the audio back as it is generated, for low-latency playback. Set
output_formatin the body for explicit codec/sample-rate/bitrate control (e.g.pcm_16000orulaw_8000for telephony), or fall back to the Accept header for the container; the response is raw audio bytes (HTTP chunked). For Base64-encoded audio with speech-mark metadata in a single JSON response, use POST /v1/audio/speech.
-
-
-
await client.audio.stream({ body: { input: "input", voice_id: "voice_id" } });
-
-
-
request:
Speechify.StreamAudioRequest
-
requestOptions:
AudioClient.RequestOptions
-
-
client.audio.streamWithTimestamps({ ...params }) -> core.Stream<Speechify.SpeechStreamEvent>
-
-
-
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.chunkevent 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 terminalspeech.doneevent 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.Speech marks are produced by the streaming-native models. The default
simba-3.0andsimba-3.2both serve this route; the legacysimba-englishandsimba-multilingualmodels return 400speech_marks_unsupportedhere. For Base64-encoded audio and speech marks in one non-streamed JSON response, on any model, use POST /v1/audio/speech.
-
-
-
const response = await client.audio.streamWithTimestamps({ body: { input: "Streaming long-form audio with the Speechify API.", model: "simba-3.2", voice_id: "geffen_32" } }); const audioChunks: Buffer[] = []; for await (const item of response) { switch (item.type) { case "speech.chunk": // `audio` is Base64 and the SDK does not decode it for you. It is // absent on a marks-only chunk, which the last chunk often is. if (item.audio != null) { audioChunks.push(Buffer.from(item.audio, "base64")); } // Mark times are absolute ms from the start of the synthesis, so // they apply to the concatenated audio, not to this chunk. for (const mark of item.speech_marks ?? []) { console.log(mark.start_time, mark.value); } break; case "speech.error": // A failure after the stream has opened arrives as an event, NOT as // a thrown error: the 200 status is already committed. The SDK does // not raise it for you, so handle it explicitly — otherwise the loop // ends normally and you treat truncated audio as a success. throw new Error(`${item.error.code}: ${item.error.message}`); case "speech.done": // Terminal event. There is no `[DONE]` sentinel. console.log("billable characters:", item.billable_characters_count); console.log("audio duration (ms):", item.audio_duration_ms); break; default: // Ignore unrecognized event types so new ones cannot break you. break; } } const audio = Buffer.concat(audioChunks);
-
-
-
request:
Speechify.StreamWithTimestampsAudioRequest
-
requestOptions:
AudioClient.RequestOptions
-
-
client.models.list() -> Speechify.ModelsResponse
-
-
-
List the text-to-speech models available for synthesis. Drive a model picker from this response, then pass a model
idas themodelparameter to POST /v1/audio/speech or /v1/audio/stream. The response marks the default model (used when a request omitsmodel), the routes each model may be passed to, and which voices it accepts. Multi-speaker models arrive in a separatedialogue_modelsarray because they are valid only on POST /v1/audio/dialogue. Returns the full set in a single response: the model catalog is static platform reference data, so it is intentionally not paginated.
-
-
-
await client.models.list();
-
-
-
requestOptions:
ModelsClient.RequestOptions
-
-
client.voices.list({ ...params }) -> core.Page<Speechify.GetVoice, Speechify.ListVoicesResponse>
-
-
-
Lists the voices available to the caller - the shared voice catalog plus the workspace's cloned voices, whichever member or service-account key created them. By default the full catalogue is returned in one response. Pagination is opt-in: pass
limit(and thencursorfrom the previous response) to page through the list whilehas_moreis true. Max page size is 200. Narrow the list with thetypeandlocalefilters (applied before pagination, so pages stay full).
-
-
-
const pageableResponse = await client.voices.list({ locale: "en", model: "simba-3.2" }); for await (const item of pageableResponse) { console.log(item); } // Or you can manually iterate page-by-page let page = await client.voices.list({ locale: "en", model: "simba-3.2" }); while (page.hasNextPage()) { page = page.getNextPage(); } // You can also access the underlying response const response = page.response;
-
-
-
request:
Speechify.ListVoicesRequest
-
requestOptions:
VoicesClient.RequestOptions
-
-
client.voices.create({ ...params }) -> Speechify.GetVoice
-
-
-
Create a cloned voice for the workspace from a 10-30 second audio sample, with verified consent from the speaker.
Cloning requires proof that the speaker agreed to it. Create a consent challenge with
POST /v1/voices/consent-challenges, show the returnedphraseto the speaker, record them reading it aloud, and send that recording here asconsent_recordingtogether with the challenge'sconsent_challenge_id. Speechify transcribes the recording, checks it against the phrase it issued, checks that its speaker is the speaker in yoursample, and keeps it as the consent record for the voice. The person consenting therefore has to be the person being cloned. A challenge is single use and short-lived, so record and submit in one sitting.The clone belongs to the workspace rather than the member who created it, and access follows the caller's workspace role and API-key scopes exactly as for any other voice: voices scopes to list it, audio scopes to synthesize with it, and the content-management permission plus a write scope on the key to delete it. Cloned voices are usable self-serve on
simba-3.0,simba-englishandsimba-multilingual.simba-3.2also serves cloned voices, currently as a limited release enabled per workspace; contact Speechify to have it enabled for yours.Callers pinned before
Speechify-Version: 2026-09-13use the previous flow instead: no challenge, and aconsentform field carrying the speaker's name and email as a JSON string. That flow is deprecated and will be removed after a sunset window announced in the changelog.
-
-
-
await client.voices.create({ sample: fs.createReadStream("/path/to/your/file"), consent_recording: fs.createReadStream("/path/to/your/file"), "Idempotency-Key": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", name: "name", gender: "male", consent_challenge_id: "consent_challenge_id" });
-
-
-
request:
Speechify.CreateVoicesRequest
-
requestOptions:
VoicesClient.RequestOptions
-
-
client.voices.get({ ...params }) -> Speechify.GetVoice
-
-
-
Fetch a single voice by id - a shared catalogue voice or one of the workspace's cloned voices. A cloned voice that belongs to another workspace returns 404, identical to an unknown id, so voice inventory is never enumerable across tenants.
-
-
-
await client.voices.get({ voice_id: "voice_id" });
-
-
-
request:
Speechify.GetVoicesRequest
-
requestOptions:
VoicesClient.RequestOptions
-
-
client.voices.delete({ ...params }) -> void
-
-
-
Delete one of the workspace's cloned voices. Requires the
content.managepermission (owner, admin, or member); a service-account key is authorized by its scopes instead.
-
-
-
await client.voices.delete({ voice_id: "voice_id" });
-
-
-
request:
Speechify.DeleteVoicesRequest
-
requestOptions:
VoicesClient.RequestOptions
-
-
client.voices.downloadSample({ ...params }) -> core.BinaryResponse
-
-
-
Download a personal (cloned) voice sample
-
-
-
await client.voices.downloadSample({ voice_id: "voice_id" });
-
-
-
request:
Speechify.DownloadSampleVoicesRequest
-
requestOptions:
VoicesClient.RequestOptions
-
-
client.voices.consentChallenges.create({ ...params }) -> Speechify.ConsentChallenge
-
-
-
Start the consent check for a voice clone.
Returns a
phrasefor the speaker to read aloud and anidthat identifies this challenge. Show the phrase to the speaker exactly as returned, record them reading it, then send the recording and theidtoPOST /v1/voices, which verifies the recording against the phrase and against the voice sample being cloned, then keeps it as the consent record.A challenge is single use, is bound to the workspace that created it, and expires at
expires_at- it is proof that a speaker was in front of a microphone just now, so create it when you are ready to record, not at the start of your flow. If it expires, create another one and record again.Challenge creation is rate limited per workspace at a few dozen per hour, far more tightly than the rest of the voice surface, because each one precedes a person recording themselves - mint it when your speaker is ready, not speculatively. Read the live ceiling off
RateLimit-*rather than hard-coding it. On a429, always honourRetry-Afterrather than a fixed backoff of your own: the wait is measured in minutes and can run to most of an hour.RateLimit-*are omitted rather than reporting a bucket that is not the one refusing.
-
-
-
await client.voices.consentChallenges.create({ "Idempotency-Key": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d", full_name: "Jane Doe" });
-
-
-
request:
Speechify.voices.CreateConsentChallengeRequest
-
requestOptions:
ConsentChallengesClient.RequestOptions
-
-