Sume TTS 400: send transcript or transcript_source, never both
A TTS request needs exactly one of transcript or transcript_source. Both, or neither, is an error. Live-commerce Formats need the source. A validator.

Sume TTS 1.0 takes exactly one text input: transcript, a literal string, or transcript_source, a reference to a script revision. Sending both, or sending neither, is rejected.
The two shapes
transcript: literal text, up to 20,000 characters. Fine for generic requests.transcript_source: an object withscript_revision_idandsentence_ids(up to 1,000). Sume resolves the text server side.- Live-commerce Formats require
transcript_source. A literal transcript there fails withtts_text_source_required.
Check before you send
This Python function runs offline and mirrors the rule, so a bad body fails in your tests instead of at the API.
def check_body(body):
has_text = "transcript" in body
has_source = "transcript_source" in body
if has_text == has_source:
raise ValueError("send exactly one of transcript or transcript_source")
if has_text and len(body["transcript"]) > 20000:
raise ValueError("transcript over 20,000 characters")
if has_source:
ids = body["transcript_source"].get("sentence_ids", [])
if not 1 <= len(ids) <= 1000:
raise ValueError("sentence_ids must hold 1 to 1000 ids")
return True
print(check_body({"transcript": "Hello", "language": "en"}))
try:
check_body({"transcript": "Hello", "transcript_source": {"sentence_ids": ["a"]}})
except ValueError as e:
print("rejected:", e)
Why it is strict
Source-bound requests are resolved before the credit reservation, so a stale revision or wrong workspace fails closed and charges nothing. Because the text is chosen by id, retries with the same idempotency key reuse the same job instead of drifting.
Related posts
More in Developers
- Sume TTS has no SSML field: speed, emotion, pronunciation dictionary
Moving an SSML voice script to Sume TTS? The request takes plain transcript text, so use speed 0.6 to 1.5, volume, emotion and a pronunciation dictionary id.
- Sume /v1/videos says cancelled, /v1/jobs says canceled: guard it
The /v1/videos poll uses pending, in_progress and cancelled; /v1/jobs uses queued, processing and canceled. A small normalizer keeps your poller from hanging.
- Sume webhook and status poll race: never move a job row backwards
A late poll can say processing after the webhook already said completed. A rank-guarded SQLite update keeps a Sume job row from going backwards.
- Sume webhook five-minute replay window: reject stale deliveries
Sume signs timestamp.raw_body and advises a five-minute replay tolerance. Reject stale timestamps, refuse an empty secret, and keep job_id as the dedupe key.
Written by Sume