Getting Started
Start synthesising speech with the SonexLabs API in under 5 minutes.
Go to sonexlabs.com and sign up. Once logged in, you will be placed in a workspace (tenant).
In the sidebar, go to Configurations → Developer. Under the API Keys tab, click Create key, give it a name, and copy the key immediately; it is shown only once.
vsk_ and are scoped to your workspace. Store them in environment variables, never in source code.Replace YOUR_API_KEY and REPLACE_WITH_VOICE_ID below. You can find voice IDs by calling GET /v1/voices or browsing the voices in your dashboard. Only text is required — voice_id, language, speed, output_format, and sample_rate are all optional.
curl --location 'https://api.sonexlabs.com/v1/speech' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"text": "Your premium of rupees twelve thousand five hundred is due on the fifteenth of September.",
"voice_id": "REPLACE_WITH_VOICE_ID",
"language": "auto",
"speed": 1.0,
"output_format": "wav",
"sample_rate": 24000
}'language defaults to "auto" (auto-detected from the text) — a value other than "auto"is accepted but has no effect on pronunciation. speed accepts 0.5–2.0 (default 1.0); values outside that range are clamped, not rejected. output_format accepts wav, mp3, ogg, or mulaw (default wav) — mulaw is G.711 mu-law in a WAV container, for telephony bridges. sample_rate accepts 8000, 16000, 22050, 24000, 44100, or 48000 Hz (default 24000); 8000/16000 are the common choices for telephony and ASR pipelines. text is limited to 5,000 characters (~4-5 minutes of audio) on this endpoint — use /v1/speech/stream below for longer input.
A successful response returns raw audio bytes, with Content-Type matching output_formatand the character count billed in the X-Chars-Billed header. If voice_id isn't recognized, the request still succeeds using the default platform voice — check the X-Voice-Fallback response header ("true"/"false") and X-Voice-Fallback-Reason to detect this instead of a silent mismatch.
Prefer a GUI? Run every endpoint from our Postman collection instead — it's pre-filled with the same requests shown on this page.
POST /v1/speech/stream takes the exact same request body as /v1/speech, but streams audio back as chunked HTTP sentence-by-sentence instead of waiting for the full file — use this for longer scripts to reduce perceived latency.
curl --location 'https://api.sonexlabs.com/v1/speech/stream' \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"text": "Your premium of rupees twelve thousand five hundred is due on the fifteenth of September.",
"voice_id": "REPLACE_WITH_VOICE_ID",
"language": "auto",
"speed": 1.0,
"sample_rate": 24000
}'output_format is not accepted here — streamed audio is always chunked WAV/PCM. sample_rate is honored (same allowed values as /v1/speech). text is limited to 20,000 characters (~15-20 minutes of audio) on this endpoint.
Billing: your full request is pre-authorized against your balance upfront (so you can't start a request you can't afford). Complete the stream and you're billed the full requested amount. Disconnect early and you're billed a bounded estimate of how much audio actually reached you before the drop — never the full request for a stream that only partially delivered. X-Chars-Billed shows the requested/authorized amount; the amount actually charged for an early-disconnected stream will be lower and shows up in your usage ledger.
Fetch the voices available to your workspace. The id field is what you pass as voice_id.
curl https://api.sonexlabs.com/v1/voices \ -H "Authorization: Bearer YOUR_API_KEY"
[
{
"id": "72ly9crx9v",
"name": "Alok",
"language": "Hindi",
"languages": ["Hindi"],
"gender": "male",
"provider": "panini",
"type": "platform",
"preview_url": "https://...",
"tags": ["Indian", "Natural, Professional"],
"created_at": null
}
]Each synthesis call deducts from your TTS service credits first, then from your wallet balance. Check your remaining balance at any time:
curl https://api.sonexlabs.com/v1/balance \ -H "Authorization: Bearer YOUR_API_KEY"
{
"wallet": { "balance": 5.00, "currency": "USD" },
"credits": [
{ "service": "tts", "available": 50000, "next_expiry": "2027-06-01T00:00:00Z" }
]
}POST /v1/voices/clone is a multipart/form-data request, not JSON. Upload the reference audio directly with the file field (recommended), or pass audio_url if it is already hosted somewhere; provide exactly one of the two. Minimum 10 seconds of clean audio, one speaker. There is no language field: Pāṇini auto-detects language at synthesis time, same as with platform voices.
curl -X POST https://api.sonexlabs.com/v1/voices/clone \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "name=My Custom Voice" \ -F "file=@reference.wav"
Runs synchronously; the response is the ready-to-use voice, no polling required.
{
"id": "v_xyz789",
"name": "My Custom Voice",
"language": null,
"languages": [],
"gender": null,
"provider": "custom",
"type": "custom",
"preview_url": "https://storage.sonexlabs.com/voice-profiles/v_xyz789/ref_audio.wav",
"tags": ["custom", "cloned"],
"created_at": "2026-08-05T18:19:22.468Z"
}type is "platform" for built-in SonexLabs voices and "custom" for voices your workspace cloned. Use it to tell the two apart when you list voices with GET /v1/voices.
To remove a cloned voice, call DELETE /v1/voices/{voice_id}:
curl --location --request DELETE 'https://api.sonexlabs.com/v1/voices/REPLACE_WITH_VOICE_ID' --header 'Authorization: Bearer YOUR_API_KEY'
Pāṇini TTS supports 250+ languages and always auto-detects the language from your input text. No configuration needed. The optional language field on POST /v1/speech and POST /v1/speech/stream defaults to "auto"; leave it as "auto" to auto-detect.
Full list of 250+ languages at sonexlabs.com/models/tts.
The API enforces two limits per tenant, shared across all API keys in your workspace: 5 requests/second (burst) and 120 requests/minute (sustained). When exceeded, the API returns 429 Too Many Requests. Retry after the number of seconds indicated in the Retry-After response header.
Two client-side changes remove latency that has nothing to do with synthesis itself:
A fresh connection pays DNS lookup + TCP connect + TLS handshake before your request is even sent — measured at ~150-300ms combined. Reuse one HTTP client/session across requests (e.g. requests.Session() in Python, http.Agent({ keepAlive: true }) in Node, or any pooled aiohttp.ClientSession) instead of opening a new connection per call. This cost is paid once per connection, not once per request.
output_format for slower linksoutput_format: "wav" is the default and is uncompressed — the response payload is larger and takes longer to download over higher-latency or bandwidth-constrained connections (e.g. mobile networks). If you don't need raw WAV, request "mp3" or "ogg"instead: same synthesis time on our side, smaller payload, faster download.