Text to speech

POST /v1/tts. Send text, get audio back, streamed as it's generated.

http
POST https://api.iodine.dev/v1/tts
Authorization: Bearer io_live_...
Content-Type: application/json

Request body

FieldTypeDefault
textstringrequiredWhat to say. Up to 5,000 characters. On v2, may include emotion and delivery cues like {happy}. Line breaks, tabs and repeated spaces are tidied up for you.
voicestringrequiredA voice id from GET /v1/voices, or one of your cloned voices (vc_…).
model"v1" | "v2"from the voiceWhich model speaks. Left out, Iodine picks the model that has the voice, v1 first.
format"wav" | "pcm""wav"WAV with a header, or raw PCM samples.
streambooleantrueStream audio as it's generated, or send it all at once when it's done.
temperaturenumberthe model'sBetween 0 and 2. Lower is steadier, higher is more varied.
language"en" | "es" | "fr" | "de" | "ja" | "zh"from the textv2 only: the language you're speaking, for the most natural accent. Japanese and Chinese are recognised from the text.
json
{
  "text": "Your order has shipped and should arrive on Thursday.",
  "voice": "katie",
  "format": "wav",
  "stream": true
}

Response

On success the body is audio, with these headers:

HeaderValue
Content-Typeaudio/wav or audio/pcm
X-Request-IdThe request's id. It also appears in the console's request log, so quote it when you contact us.
X-Sample-Rate24000
X-Audio-Encodings16le (signed 16-bit, little-endian)
X-Audio-Channels1

On failure the body is JSON: {"error": {"code": "...", "message": "..."}}. See Errors and limits.

Streaming

By default ("stream": true) audio is sent as soon as each piece is generated, so you can start playing it before the whole sentence is done. With "format": "wav" the stream begins with a WAV header whose length fields are set to the maximum, as is usual for streamed WAV. Players and libraries handle this, but if you need exact lengths, either set "stream": false or ask for pcm and add your own header.

With "stream": false the response arrives in one piece once generation finishes, with a Content-Length and, for WAV, an exact header.

Formats

  • wav: plays anywhere. 24 kHz, mono, 16-bit PCM inside a WAV container.
  • pcm: raw samples, the same audio without a header. Best for piping into a player, a phone line or your own encoder.

Play raw PCM with ffplay:

bash
curl -s https://api.iodine.dev/v1/tts \
  -H "Authorization: Bearer $IODINE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Streaming straight to the speakers.", "voice": "katie", "format": "pcm"}' \
| ffplay -f s16le -ar 24000 -ch_layout mono -nodisp -autoexit -

Playing a stream in Python

python
import os
import requests
import sounddevice as sd  # pip install sounddevice

with requests.post(
    "https://api.iodine.dev/v1/tts",
    headers={"Authorization": f"Bearer {os.environ['IODINE_API_KEY']}"},
    json={"text": "Playback starts before generation ends.", "voice": "katie", "format": "pcm"},
    stream=True,
) as resp, sd.RawOutputStream(samplerate=24000, channels=1, dtype="int16") as out:
    resp.raise_for_status()
    leftover = b""
    for chunk in resp.iter_content(chunk_size=4096):
        data = leftover + chunk
        cut = len(data) - len(data) % 2  # whole 16-bit samples only
        out.write(data[:cut])
        leftover = data[cut:]

What you're charged

You pay for the audio you receive: its length in minutes times your rate. If you hang up partway through a stream, you pay only for what was sent. Requests that fail on our side cost nothing. See Billing and data.