API reference
Sautna API
One endpoint. Send text with your API key, receive a WAV file (24 kHz, mono, 16-bit). Create keys in Studio → API keys.
Synthesize speech
POST /api/v1/tts
| Field | Type | Description |
|---|---|---|
| text | string | The Arabic text to speak. Per-request length depends on your plan. |
| voice | string | Voice id. zol (male, default) or zola (female). |
| format | string | Output encoding: wav (default), mp3, opus, ulaw_8000 (8 kHz μ-law for telephony) or pcm (headerless 24 kHz s16le). Ignored when stream is true. |
| stream | boolean | true streams raw 24 kHz 16-bit mono PCM as sentences render — playback can start on the first sentence. Default returns a complete WAV. |
Success responses include X-Characters-Billed, X-Quota-Remaining, X-RateLimit-Limit and X-RateLimit-Remaining headers; a 429 carries Retry-After (seconds). Errors are JSON: {"error": {"code", "message"}}. There's also an OpenAPI spec for Postman or client codegen.
curl
curl -X POST https://sautna.com/api/v1/tts \
-H "Authorization: Bearer sautna_sk_..." \
-H "Content-Type: application/json" \
-d '{"voice": "zol", "text": "أهلين! كيف اخبارك؟"}' \
--output speech.wavPython
import requests
resp = requests.post(
"https://sautna.com/api/v1/tts",
headers={"Authorization": "Bearer sautna_sk_..."},
json={"voice": "zol", "text": "أهلين! كيف اخبارك؟"},
)
resp.raise_for_status()
open("speech.wav", "wb").write(resp.content)JavaScript
const res = await fetch("https://sautna.com/api/v1/tts", {
method: "POST",
headers: {
Authorization: "Bearer sautna_sk_...",
"Content-Type": "application/json",
},
body: JSON.stringify({ voice: "zol", text: "أهلين! كيف اخبارك؟" }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
await Bun.write("speech.wav", await res.arrayBuffer()); // or fs.writeFileStreaming · recommended for anything interactive
With "stream": true the response is raw 24 kHz s16le mono PCM, one chunk per rendered sentence — playback can start as soon as the first sentence lands instead of waiting for the whole text. This is how our own phone lines run. For IVR, resample to 8 kHz or request format=ulaw_8000 on a non-streamed call.
curl — streaming
# Streaming: raw 24 kHz s16le PCM; audio is sent as the model generates it
curl -N -X POST https://sautna.com/api/v1/tts \
-H "Authorization: Bearer sautna_sk_..." \
-H "Content-Type: application/json" \
-d '{"voice": "zol", "text": "أهلين! كيف اخبارك؟", "stream": true}' \
--output speech.pcm
# play it: ffplay -f s16le -ar 24000 -ch_layout mono speech.pcmsafe retries
# Reuse the SAME key only when retrying this exact request Idempotency-Key: assistant-turn-0184 # Retry 429 after Retry-After; retry 502/504 with exponential backoff. # Never reuse one key for different text, voice, format, or stream settings.
Pipecat integrations can wrap each received chunk in a TTSAudioRawFrameat 24 kHz, one channel. A ready-to-adapt service example is available at examples/pipecat_sautna_tts.py.
API limits
Community accounts do not include API access. API access is invite-only and can be granted by Sautna to selected accounts.
Monthly, per-request and rate limits are account-specific. Approved users can see their exact entitlements inside Studio → Plan and current rate-limit values in API response headers. To discuss approved API access, email hello@sautna.com.
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_api_key / invalid_api_key | No key sent, or the key was revoked. |
| 402 | quota_exceeded | Monthly character quota used up — wait for reset. |
| 403 | api_not_included | API access is invite-only. |
| 404 | unknown_voice | The voice id doesn't exist. |
| 413 | text_too_long | Text exceeds your plan's per-request limit. |
| 429 | rate_limited | Too many requests per minute — back off and retry. |
| 502/504 | synthesis_failed | Upstream synthesis error or timeout — safe to retry. |
Latency
Synthesis runs on an always-on GPU — there are no cold starts. Repeated sentences are served from cache (see the X-Cache header) and return instantly. Non-streamed requests render and quality-check the full audio before responding, so long texts take longer end-to-end — prefer stream: true when a listener is waiting. Set client timeouts around 60 seconds for long non-streamed texts.