Idempotency keys for batch TTS: derive them from what you render

A retry loop that mints random keys pays twice. Hash the sentence, voice, model and settings into the key, and re-running a script returns the first job.

5 min readSume
All posts

How do you avoid paying twice when a batch TTS script crashes halfway? Derive each Idempotency-Key from what the request renders, not from a counter or a random id. Re-running the script then sends the same keys, and the API returns the existing jobs.

Cheap generation makes big batches normal. Mistral's Voxtral TTS (read 2026-10-04) is priced at $0.016 per 1,000 characters, and a book-length script is hundreds of requests. A crash at request 212 should cost nothing on restart.

What the key must include

If two requests would produce different audio, their keys must differ. If they would produce the same audio, their keys should match.

  • The exact transcript text for the sentence.
  • The voice id or avatar handle, and the language.
  • The model id when you use the router.
  • Generation settings: speed, volume and emotion.
  • A version string you bump deliberately when you want a fresh take.

A key function

Hash a canonical form of those inputs. Sorted keys keep the hash stable across runs. Every Sume submit also reports idempotency_hit, so you can log which sentences were already rendered.

import hashlib, json

def tts_key(text, voice, model, config, version="v1"):
    blob = json.dumps({"t": text, "v": voice, "m": model,
                       "c": config, "ver": version},
                      sort_keys=True, ensure_ascii=False)
    return "tts-" + hashlib.sha256(blob.encode("utf-8")).hexdigest()[:40]

a = tts_key("Hello there.", "voi_demo", "sonic-3.6", {"speed": 1.0})
b = tts_key("Hello there.", "voi_demo", "sonic-3.6", {"speed": 1.0})
c = tts_key("Hello there.", "voi_demo", "sonic-3.6", {"speed": 1.1})
print(a == b, a == c)

Edge cases

Reusing a key with a different body is a conflict, not a new job, which is what you want: a changed sentence must get a changed key. Do not put timestamps or run numbers in the key, since those change on every restart and defeat the point.

A mode of sync or subscribe waits at most 30 seconds, so a slow sentence returns before it is done. Poll the status_url with the same job instead of resubmitting, and the key stays valid for the whole batch.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume