Skip to content

POST /v1/tts POST

Synthesise text with voice and stream the audio back in format. The response starts after the first 80 ms frame is decoded.

POST /v1/tts HTTP/1.1
Host: api.vartalaap.si
x-api-key: vt_live_…
content-type: application/json

{"text": "आपकी EMI ₹12,450 है, जो 5 नवंबर को due है.", "voice": "anjura", "format": "wav"}

Request body

Field Type Default Notes
text string required ≤ 4 000 characters after trimming. Run through the Hinglish normaliser unless raw is true. Must leave something pronounceable (else 400 nothing to say after normalisation). Splits into ≤ 64 sentence chunks of ≤ 50 tokens.
voice string anjura anjura or raj; GET /v1/voices lists what your key may use. Unknown → 400; not allowed for the key → 403.
format string wav wav · pcm16 (s16le mono 24 kHz) · opus (Ogg/Opus 48 kbit/s). Anything else → 400.
stream bool true true: chunked transfer, first bytes after the first frame; WAV header carries a placeholder length. false: buffered; WAV gets a correct length and Content-Length; Opus is a complete file.
raw bool false Skip the normaliser; the text must already be normalised.
en_digits bool false Read codes/OTPs/masked numbers with English digit names (four eight two nine) instead of Hindi.
temperature number 0.3 0–2. Sampling noise; lower is more stable, higher more varied. Out of range → 400.
seed integer random Fixes the noise for reproducible output.
request_id string generated Echoed as x-request-id, stored in your usage log.
sample_rate integer 24000 Only 24000 is accepted (400 otherwise).
speed number 1.0 Only 1.0 is accepted: the model has no rate control (400 otherwise).

Response

200 OK, Content-Type audio/wav, audio/L16-style raw PCM (pcm16) or audio/ogg, x-request-id: <id>, Cache-Control: no-store. With stream: true the body is chunked (Transfer-Encoding: chunked); with stream: false it has Content-Length.

Audio: 24 000 Hz, mono, 16-bit. Frames are 80 ms (1 920 samples); after the first frame they are decoded four at a time, so bytes arrive in ~320 ms steps ahead of real time.

Errors

Status Body When
400 {"error":"text is empty"} · unknown voice …; see /v1/voices · format must be pcm16, wav or opus · sample_rate must be 24000 · speed other than 1.0 is not supported by this model · temperature must be in [0, 2] · nothing to say after normalisation · text: … request validation
401 missing x-api-key · invalid api key auth
403 voice raj not allowed for this key key's voice allow-list
413 text longer than 4000 characters · text splits into N chunks; max 64 size
503 + Retry-After: 1 all workers busy and the queue is full; retry capacity (the contract)
500 {"error":"<engine error>"} · worker gone engine failure; retry once

Errors are decided before any audio is sent: a 200 that has started streaming will not turn into an error status. If the engine fails mid-stream (rare), the body simply ends early; the WebSocket reports the same condition as an error event.

Examples

# streamed WAV straight to a player
curl -sN -X POST https://api.vartalaap.si/v1/tts -H "x-api-key: $VARTALAAP_API_KEY" -H 'content-type: application/json' \
  -d '{"text":"नमस्ते, मैं सरल finserv से प्रिया बोल रही हूँ.","voice":"anjura","format":"wav"}' | ffplay -nodisp -autoexit -i -

# complete Ogg/Opus file with English digits for the code
curl -s -X POST https://api.vartalaap.si/v1/tts -H "x-api-key: $VARTALAAP_API_KEY" -H 'content-type: application/json' \
  -d '{"text":"आपका OTP 482913 है.","voice":"raj","format":"opus","stream":false,"en_digits":true}' -o otp.ogg

# raw PCM into ffmpeg → m4a, reproducible with a seed
curl -sN -X POST https://api.vartalaap.si/v1/tts -H "x-api-key: $VARTALAAP_API_KEY" -H 'content-type: application/json' \
  -d '{"text":"ठीक है, धन्यवाद.","format":"pcm16","seed":7}' | ffmpeg -f s16le -ar 24000 -ac 1 -i - -c:a aac -b:a 96k out.m4a
import os, requests
r = requests.post("https://api.vartalaap.si/v1/tts", stream=True,
                  headers={"x-api-key": os.environ["VARTALAAP_API_KEY"]},
                  json={"text": "आपकी EMI ₹12,450 है.", "voice": "anjura", "format": "pcm16"})
if r.status_code == 503:
    wait = float(r.headers.get("retry-after", "1"))        # nothing was billed; retry after `wait`
r.raise_for_status()
for pcm in r.iter_content(chunk_size=None):
    sink.write(pcm)                                        # s16le, 24 kHz, mono
const res = await fetch("https://api.vartalaap.si/v1/tts", { method: "POST",
  headers: { "x-api-key": process.env.VARTALAAP_API_KEY, "content-type": "application/json" },
  body: JSON.stringify({ text: "आपकी EMI ₹12,450 है.", voice: "anjura", format: "pcm16" }) });
if (res.status === 503) retryAfter(Number(res.headers.get("retry-after") || 1));
for await (const pcm of res.body) sink.write(pcm);           // Buffer chunks, s16le 24 kHz mono