Voice preview

Voice preview

Hear a voice before you ship it. Every preview endpoint returns a audio/wav body you can play directly — no polling, no job id.

There are two ways to preview, and they answer different questions:

EndpointQuestion it answers
POST /api/agents/{id}/voice-preview"What will this agent sound like on a call?"
POST /api/providers/tts/{provider}/preview<br/>POST /api/providers/s2s/{provider}/preview"What does this voice sound like?" — before anything is saved

All of them accept an API key (x-api-key) or a dashboard JWT.

Preview a saved agent

POST /api/agents/{id}/voice-preview

Loads the agent and auditions the configuration that is actually stored, using the same keys a live call would use. It also picks the engine the way a call does: if the agent's s2sConfig names a provider, you hear the realtime model — otherwise you hear its TTS voice. That matters, because a realtime agent never plays its ttsVoice, so previewing that field would play a voice the caller will never hear.

curl -X POST https://api.telenow.ai/api/agents/{id}/voice-preview \
  -H "x-api-key: vai_live_…" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hi, thanks for calling Acme. How can I help?"}' \
  --output preview.wav
FieldTypeNotes
textstringOptional. What the voice should say. Defaults to a short sample. Capped at 500 characters (200 on the realtime engine).

The response body is the audio. Which engine answered comes back in headers, so the body stays playable:

HeaderExampleMeaning
X-Voice-Engines2s / cascadeWhich stack produced this audio
X-Voice-Provideropenai / elevenlabsThe engine that spoke
X-Voice-Modelgpt-realtimeRealtime model (empty on the cascade)
X-Voice-IdmarinThe voice that spoke (a cloned voice is reported resolved)

These four are CORS-exposed, so browser clients can read them.

Check X-Voice-Engine when you expect a realtime agent: cascade means the agent will run the STT→LLM→TTS stack instead — usually because the realtime engine is not enabled on this deployment. This route deliberately mirrors what a call would do rather than refusing, so what you hear is always what the caller will hear.

If the agent's voice is a cloned voice, this route resolves it to the real provider voice — so the clone is what you hear, not a stand-in.

Preview a voice before saving

Use these while a user is still choosing — they take the settings in the request body and touch nothing stored.

Text-to-speech

POST /api/providers/tts/{provider}/preview
curl -X POST https://api.telenow.ai/api/providers/tts/elevenlabs/preview \
  -H "x-api-key: vai_live_…" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello from Acme.", "voice": "9BWtsMINqrJLrRacOk9x"}' \
  --output sample.wav
FieldTypeNotes
textstringRequired. Capped at 500 characters.
voicestringVoice id from GET /api/providers/tts/{provider}/voices. Omit for the provider's default.
configobjectOptional — the same shape as an agent's ttsConfig (model, language, tunable settings, and a BYOK apiKey). Pass it so the preview matches what a real call will sound like.

Realtime (speech-to-speech)

POST /api/providers/s2s/{provider}/preview

Opens one short real session with the engine and has the model speak your line, so what you hear is genuine realtime output rather than a TTS approximation.

curl -X POST https://api.telenow.ai/api/providers/s2s/openai/preview \
  -H "x-api-key: vai_live_…" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-realtime", "voice": "marin"}' \
  --output sample.wav
FieldTypeNotes
modelstringRequired. A model id from GET /api/catalog/s2s.
voicestringRequired. A voice that model offers.
textstringOptional line to speak. Capped at 200 characters.
configobjectOptional — the agent's s2sConfig bag, including a BYOK apiKey. On gemini the voice-direction keys ride here too (audioProfile, scene, directorNotes, accent, language), so the sample is spoken in the configured accent and language, not the model's default.

Returns 404 when the realtime engine is not enabled on the deployment — check enabled on GET /api/catalog/s2s first. (The by-agent route above does not: it falls through to the cascade, because that is what the agent's calls do.)

Rate limits and cost

Every preview opens a real provider session on a real key, so it costs money — your own when you pass a BYOK apiKey, the platform's otherwise. They are throttled per organization, independently for each kind:

KindMinimum gap between previews
Text-to-speech1.5 seconds
Realtime6 seconds

Exceeding it returns 429 carrying a Retry-After header and a retryAfter field (whole seconds, rounded up) in the body. The two kinds are throttled independently, so auditioning a TTS voice never locks out the realtime picker.

Previews are meant to be driven by a person auditioning voices — build a picker, not a batch job. A read-only API key (role member or viewer) cannot call them at all, because a preview spends against a real vendor key. POST /api/agents/{id}/voice-preview additionally refuses when the org is suspended or out of credit. Previews themselves are not metered or billed per call — the throttle is the only ceiling, which is why it is strict. If you need bulk synthesis, use the App AI gateway (ai:tts), which is metered instead.

Errors

StatusMeaning
400The request was rejected before or by the vendor — an unsupported provider, a failed init, or a TTS 4xx (usually a voice id that doesn't exist on the key in use, BYOK vs. platform). The message carries the provider's own detail.
401Missing or invalid credential.
403The API key's role is read-only (member/viewer), or the org is suspended / out of credit.
404Unknown agent — or an agent in another organization, which is deliberately indistinguishable from an unknown id. Also returned by /providers/s2s/{provider}/preview when the realtime engine is disabled here.
429Throttled — see the table above.
502The engine ran but failed or produced no audio (code: provider_error). Not worth retrying when the message names a voice or a key.