Verify a Sume TTS transcript_receipt SHA-256 yourself in Python
Recompute submitted_transcript_sha256 from your script with NFC and LF canonicalization and compare it to the transcript_receipt on a finished Sume TTS job.

A Sume TTS job generated from an accepted script carries a server-owned transcript_receipt in its result. One field, submitted_transcript_sha256, is the SHA-256 of the exact transcript the job read. You can recompute it from your own copy of the script and confirm the audio was made from the words you approved.
The receipt has six fields: job_id, script_revision_id, sentence_ids, canonicalization_version (always nfc-lf-sentence-v1), submitted_transcript_sha256 and input_integrity (always source_bound). A receipt you supply yourself is not accepted as proof; Sume's own verify-spine check is the authoritative one.
The recipe
Canonicalize each block to NFC with LF line endings, cut it at ., !, ?, take the sentences named in sentence_ids, join them, add a newline wherever the selection crosses into another block, strip surrounding whitespace, and hash the UTF-8 bytes. For a selection inside one block that is simply the joined sentence text, trimmed.
import hashlib, unicodedata
def canon(t):
t = unicodedata.normalize("NFC", t)
return t.replace("\r\n", "\n").replace("\r", "\n")
block = canon("Hello there. This is the approved line. Extra.")
chosen = ["Hello there.", " This is the approved line."]
assert "".join(chosen) in block
transcript = "".join(chosen).strip()
print(hashlib.sha256(transcript.encode("utf-8")).hexdigest())Reading the result
Compare the printed digest with result.transcript_receipt.submitted_transcript_sha256 from GET on the job. Equal digests mean the job read exactly those characters. A difference means a sentence was edited, the selection was different, or the line endings or Unicode form you hashed were not canonical. Fetch the job as described in Jobs and results.
For a whole-script check across many jobs, call POST /v1/tts-1.0/source/verify-spine; it also proves coverage, with no gap or overlap, which a single hash cannot.
Cost of a check
Hashing is local and free, and verify-spine is free and read-only. Only generating audio is billed, at $0.0475 per 1,000 characters.
Sources
Related posts
More in Developers
- 4K vertical Short in Timeline: the 2160 cap and 1214x2160
Timeline output width and height top out at 2160 and must be even, so 2160x3840 is refused. What the largest 9:16 frame is and whether a Short needs it.
- Voice one script in six languages: Sume TTS loop and 409 guard
MAI-Voice-2.1 sells one voice across 23 languages. On Sume, set language per line, handle the 409 voice-language guard and price a six-language batch.
- waitForJob on a failed Sume image job: read the record, skip /result
waitForJob resolves for failed and canceled jobs instead of throwing, and /result returns 409 job_not_completed for them. How to branch on job.status.
- Webhook for an unknown job_id: park it, then reconcile on insert
A Sume job.completed webhook can name a job_id your database has not stored yet. Return 2xx, park the event in SQLite, and apply it when the insert lands.
Written by Sume