Sonic 3.6 vs Eleven v4 Turbo: one script, one Python A/B run on Sume

Send the same text to sonic-3.6 and eleven-v4-turbo through the TTS Router with one Python script, poll both jobs, and compare the files by ear.

6 min readSume
All posts

To compare Cartesia Sonic 3.6 and Eleven v4 Turbo on your own script, send the same transcript to POST /v1/tts-router/generate twice, once with model set to sonic-3.6 and once with eleven-v4-turbo, then poll both jobs and listen to the two files. The only things that change between the calls are the model id and the voice, because a Sonic row takes a Sume voice and an Eleven row takes an ElevenLabs voice id.

The request fields come from the router description in Sume's API reference and the polling steps from Sume's Jobs and results page, both read 2026-10-11. ElevenLabs' models page, read the same day, describes v4 Turbo as a low-latency model with a median inference latency of about 100 ms, which matters for live agents; a finished-file job like this one does not depend on it.

What has to match, and what cannot?

For a fair listen, hold the text, the language and the output container constant. Two things cannot match: the voices, since each family has its own voice list, and the settings, since Sonic takes generation_config and Eleven takes voice_settings.

  • Same: transcript, language, and an MP3 output (the Eleven rows return MP3 only).
  • Different: Sonic's voice.id is a Sume voice id, Eleven's is a 20-character ElevenLabs id from GET /v1/tts-router/voices?family=eleven.
  • Different: leave generation_config off the Eleven call and voice_settings off the Sonic call, because each row refuses the other family's field.
  • Keep the script at or under 10,000 characters so both rows accept it in one request.

How do I run both jobs from Python?

Set SUME_API_KEY, SONIC_VOICE and ELEVEN_VOICE, then run the script. It stops if the key is missing, polls each job until terminal is true, and prints the result only when result_ready is true. A fixed Idempotency-Key per model and label means a second run of the same label returns the same jobs instead of billing again.

import os, time, requests

BASE = "https://api.sume.com"
KEY = os.environ.get("SUME_API_KEY", "")
if not KEY:
    raise SystemExit("set SUME_API_KEY")
AUTH = {"Authorization": f"Bearer {KEY}"}
TEXT = "Our spring sale starts Friday. Everything ships free."
VOICES = {"sonic-3.6": os.environ["SONIC_VOICE"],
          "eleven-v4-turbo": os.environ["ELEVEN_VOICE"]}

def run(model, label="ab-1"):
    body = {"model": model, "transcript": TEXT, "language": "en",
            "voice": {"id": VOICES[model]}}
    head = {**AUTH, "Idempotency-Key": f"{label}-{model}"}
    r = requests.post(f"{BASE}/v1/tts-router/generate", json=body,
                      headers=head, timeout=30)
    r.raise_for_status()
    jid = r.json()["job"]["id"]
    while True:
        s = requests.get(f"{BASE}/v1/jobs/{jid}/status", headers=AUTH,
                         timeout=30).json()
        if s.get("terminal"):
            break
        time.sleep(float(s.get("next_poll_after_seconds") or 3))
    if not s.get("result_ready"):
        raise RuntimeError(f"{model} finished without a result")
    return requests.get(f"{BASE}/v1/jobs/{jid}/result", headers=AUTH,
                        timeout=30).json()

for m in VOICES:
    print(m, run(m))

What should I compare when I listen?

Listen for the things that decide a voiceover: whether numbers and brand names are read correctly, whether the pacing fits the length of your video, and whether the delivery is steady across a second take. One short line is not enough, so use three or four lines from the real script, including one with a number and one with a product name, and have two people choose without knowing which file is which.

Then compare the price. At the catalog rates, the short test line above is a cent or less on either row, and a 10,000-character script is $0.48 on Sonic 3.6 and $0.50 on v4 Turbo, rounded up to the cent. The difference is small, so choose on sound and on the limits in the table below.

What differs between the two rows, from Sume's code read 2026-10-11
Itemsonic-3.6eleven-v4-turbo
Price per 1,000 characters$0.0475$0.05
Largest request20,000 characters10,000 characters
OutputMP3 or WAVMP3 only
Word timings and sentence slicesyesrefused
Voice selectorSume voice or avatarElevenLabs voice id

What do I do with the winner?

Pin the winning model id in your code rather than a moving alias. sonic-latest always resolves to the newest stable Sonic, which is useful until the day it changes a voice mid-campaign. Keep the A/B script, and re-run it when the catalog adds a row. See Text to speech API in Python for the single-model version.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume