Voice AI

Try Every Telnyx Ultra Voice in One Scene

Telnyx ships over 700 Ultra voices across 36 languages with sub-100ms time-to-first-byte. The problem is not the voices — it is hearing them. The docs list three. The Voices API returns 4,000+ across every provider. There is no easy way to hear a handful of Ultra voices side by side, in context, speaking the same kind of content you would actually ship.

So I built a small app that lets you do exactly that. Paste a dialogue script, assign each character a different Telnyx Ultra voice and an SSML emotion, and render the whole scene into one MP3. Hear every voice in one sitting, in a real scene, instead of clicking through a voice picker one sample at a time.

The canonical code example is in the Telnyx code examples repo:

https://github.com/team-telnyx/telnyx-code-examples/tree/main/multi-character-narrator-python

The Use Case: Hear Telnyx Voices In Context

Voice pickers exist. They play a fixed sample sentence per voice. What they do not do is let you hear a voice inside a real scene — a tense argument, a calm narrator, a panicked character, a reassuring guide — because a single sample sentence does not tell you how a voice handles emotion, pacing, or character.

This example solves that. You write a short scene with a few characters. Each character gets a different Telnyx Ultra voice. Each character gets an SSML emotion. You hit render. The app fans out parallel TTS calls, stitches the per-line audio in script order, and plays you one continuous MP3 with every voice speaking in character.

The default scene is the Ides of March from Julius Caesar. Five characters, ten lines, five distinct voices, five different emotions:

  • Cassius — determined, plotting the assassination
  • Caesar — surprised, realizing the betrayal
  • Brutus — apologetic, justifying the act
  • Mark Antony — angry, mourning the fallen leader
  • Narrator — calm, setting the scene

One render, one MP3, every voice in context. That is the demo.

The Eight Curated Ultra Voices

The app ships with eight pre-built Telnyx Ultra voices curated for the most common use cases. Each one is a real Telnyx voice, enumerated from the Voices API, with a UUID voice ID that works on the REST endpoint.

VoiceGenderLanguageBest Use CaseSound Profile
AsherMaleenVoice Assistants & MediaSmooth, dynamic, podcaster-style tone
CallieFemaleenCoaching & OnboardingHigh energy, encouraging, friendly tone
ClaraFemaleen-USGeneral Purpose IVR/AIClear, standard US accent, versatile pacing
HowardMaleen-USConversational AgentsDeep, reassuring, highly trustworthy
AllieFemaleen-USCasual & Interactive AIConversational flow, natural pauses
JasperMaleen-GBFinance & HealthcareCalm, authoritative, precise delivery
SkylerNeutralenModern Brand VoiceCasual, tech-forward, friendly vibe
ArvinMaleenNavigation & DirectivesSteady, clear cadence for detailed guidance

Pick any of the eight for any character. Click Preview to hear the voice with the selected emotion before rendering the whole scene.

Twenty Ultra SSML Emotions

Ultra supports inline SSML emotion tags placed before the text:

<emotion value="excited" />Great news — your order shipped early!

The app exposes all twenty Ultra SSML emotions as a per-character dropdown:

Primary: angry, excited, content, sad, scared

Additional: happy, enthusiastic, curious, calm, grateful, affectionate, sarcastic, surprised, confident, hesitant, apologetic, determined, frustrated, disappointed

Each character in the default Julius Caesar scene is auto-assigned an emotion that fits the role. Cassius is determined. Caesar is surprised. Brutus is apologetic. Mark Antony is angry. The Narrator is calm. Same voice, different emotion, different delivery — all from one inline SSML tag per line.

How It Was Made

The app is a single Flask file with an inline browser UI. No phone number, no webhook, no Cloud Storage, no database. One env var: TELNYX_API_KEY.

The pipeline

POST /narrate  (script with speaker labels)
  -> parse script into ordered lines
  -> map speaker -> voice (8 curated Ultra voices, overridable)
  -> map speaker -> emotion (20 Ultra SSML emotions, overridable)
  -> parallel fan-out: one REST TTS call per line
     POST /v2/text-to-speech/speech
       text_type=ssml, output_type=binary_output
       <emotion value="..." /> wrapping when emotion set
  -> stitch per-line MP3 bytes in script order
  -> store in memory (1h TTL)
  -> return project_id + per_line_ttfb_ms + audio_url
  -> GET /audio/<project_id>.mp3 streams the stitched MP3

Why REST, not WebSocket

Ultra is REST-only on the public WebSocket. A 403 on wss://api.telnyx.com/v2/text-to-speech/speech is intentional. The app uses POST /v2/text-to-speech/speech with output_type: binary_output so it can measure true time-to-first-byte per line. Base64 mode would hide the real latency.

Why UUIDs, not display names

Ultra voice IDs are UUIDs in the Telnyx.Ultra.<uuid> format, not short display names like Telnyx.Ultra.Clara. Short names return 400 on the REST endpoint. The Voices API at GET /v2/text-to-speech/voices returns all available voices with their UUIDs — over 700 Ultra voices alone, filterable by provider == "telnyx" and id | startswith("Telnyx.Ultra.").

The app ships with eight curated UUIDs so the demo works out of the box, but the dropdown is easy to extend with any voice from the Voices API.

Parallel fan-out with per-line error isolation

The app uses ThreadPoolExecutor to render every line in parallel. If one line fails (e.g. an invalid voice override), the response includes an errors array and the stitched audio contains only the successful lines in script order. A failed line does not lose the whole render.

The browser UI

The UI auto-detects speakers as you type. Each speaker gets:

  • A voice dropdown with all eight curated Ultra voices and their use-case descriptions
  • An emotion dropdown with all twenty Ultra SSML emotions
  • A Preview button that renders a short sample line in the selected voice with the selected emotion

The render button fans out the parallel TTS calls, stitches the result, and autoplays the MP3. The Cast section shows which voice played which character, with an emotion badge next to each.

Run It

git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/multi-character-narrator-python
cp .env.example .env    # fill in TELNYX_API_KEY
pip install -r requirements.txt
python app.py           # starts on http://127.0.0.1:5050

Open the browser UI. The default Julius Caesar script is pre-loaded. Pick voices, pick emotions, preview, render, play.

Or POST your own script:

curl -X POST http://localhost:5050/narrate \
  -H "Content-Type: application/json" \
  -d '{
    "title": "My scene",
    "script": "Narrator: The scene opens.\nKing: Bring the envoy.\nEnvoy: I am here.",
    "emotions": {"King": "confident", "Envoy": "hesitant"}
  }'

Where This Goes Next

The app is a starting point, not a finished product. A few directions:

  • Add more voices. The Voices API returns 700+ Ultra voices. Swap any of the eight curated UUIDs for a different one and the dropdown updates automatically.
  • Add more languages. Ultra covers 36 languages. The same script-render-stitch pipeline works for any of them via language_boost.
  • Add Cloud Storage. Replace the in-memory store with Telnyx Cloud Storage for persistent, shareable audio URLs.
  • Add more sample scripts. The default is Julius Caesar. Add audiobook chapters, podcast intros, e-learning role-plays, game cinematics — any multi-speaker content where you want to hear voices in context.

The point is the same: hear Telnyx voices in a real scene, not a sample sentence. Everything else follows from that.

Related Examples

Ready to build with low-latency voice AI?

Join developers building the future of real-time conversations