Speko Docs
TTS

Batch

Send a line of text, get one complete audio body back. The OpenAI SDK works unchanged.

Use batch when the whole line is already written and a moment of silence before playback is fine — a confirmation, a voicemail, an audio file you are going to store. If a listener is waiting on the first word, you want streaming instead.

Call it

curl -X POST https://api.speko.ai/v1/audio/speech \
  -H "Authorization: Bearer $SPEKO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "model": "auto", "input": "Your appointment is confirmed for Friday.", "response_format": "wav" }' \
  --output confirmation.wav
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["SPEKO_API_KEY"],
    base_url="https://api.speko.ai/v1",
)

speech = client.audio.speech.create(
    model="auto",
    input="Your appointment is confirmed for Friday.",
    response_format="wav",
)

with open("confirmation.wav", "wb") as audio:
    audio.write(speech.read())
import fs from 'node:fs/promises';
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.SPEKO_API_KEY,
  baseURL: 'https://api.speko.ai/v1',
});

const speech = await client.audio.speech.create({
  model: 'auto',
  input: 'Your appointment is confirmed for Friday.',
  response_format: 'wav',
});

await fs.writeFile('confirmation.wav', Buffer.from(await speech.arrayBuffer()));

model: "auto" hands the choice to the router: it ranks the live board for your key's language and objective, then calls the winner. Pin a model instead by passing its id from GET /v1/models.

Two endpoints, one behaviour

EndpointBodyUse when
/v1/audio/speechJSON with input and modelYou are using an OpenAI SDK.
/v1/synthesizeJSON with textYou prefer the native name, or you are not going through an SDK.

Both route identically. Every other field is shared:

FieldMeaning
voiceProvider voice id. Omit it and the router uses the chosen provider's validated default.
languageBCP 47. An accent tag such as es-PR narrows to the voices that actually produce it — see languages and accents.
speedHonored on the providers that accept it.
instructionsDelivery direction — style or accent, without changing the transcript. Gemini TTS applies it; the others ignore it.
response_formatpcm, pcm16, or wav. Defaults to pcm.

Audio format

ValueYou get
pcm, pcm16Raw 24 kHz mono 16-bit signed little-endian PCM. No container, no header — what a telephony or WebRTC pipeline wants.
wavThe same PCM behind a 44-byte RIFF header, so a player opens it directly.

Every provider is normalized to that PCM at the edge. That is what makes failover safe: a chain that moves from one vendor to another mid-request cannot hand you a different format than the one it started producing.

mp3 and opus are not supported and return 400 naming what is. Encoding them would put a codec in the audio path and make the format depend on who answered. An earlier build accepted the value and returned PCM bytes under an mp3 label, which a caller can neither decode nor explain — so they are refused by name instead.

Choosing the voice

Routing headers work here exactly as they do everywhere else — see routing. The two that matter most for speech:

X-Speko-Language: es-PR
X-Speko-Objective: quality

Unlike transcription, the full tag counts. es-PR and es do not rank the same: an accent tag narrows the candidate set to the vendors and voices that produce that accent, because accent narrowing applies to TTS only.

Read x-route on the response to see which provider actually served it.

Limits

  • Failover is free here. Nothing has reached you yet, so a failed candidate is retried against the next one silently. x-speko-failover-count tells you how many were tried. Streaming gives that up after its first byte.
  • x-speko-first-byte-ms is meaningful here and absent on a streaming response, where the headers are written before the audio exists.
  • Latency is the whole utterance. The router holds every chunk until the vendor says it is done, including for the four vendors it reaches over a WebSocket. That wait is the reason streaming exists.

On this page