Verify a Sume video download against checksum_sha256 in Python
Stream a finished Sume artifact to disk, hash it with hashlib, and compare to checksum_sha256 from the job result. Skips cleanly when the field is null.

Fetch GET /v1/jobs/{id}/result, read data.result.artifacts[], stream each url to disk while feeding the bytes to hashlib.sha256, then compare the hex digest with the artifact's checksum_sha256. The field is nullable, so a null value means there is nothing to compare and you should log that instead of failing.
What the result gives you
The OpenAPI schema describes checksum_sha256 as a nullable string on each public artifact. It does not spell out the encoding, so the script below compares lowercase hex and prints both values on a mismatch. If your own jobs return another encoding, that mismatch output will show it immediately.
| Field | Type in the schema | Use |
|---|---|---|
url | string, Sume CDN URL | Download source. The path is opaque; do not parse it. |
size_bytes | integer or null | Cheap length check before hashing. |
checksum_sha256 | string or null | Integrity check after download. |
content_type | string or null | Pick a file extension. |
The script
The script uses only the standard library. The blocking calls run in a worker thread through asyncio.to_thread, and asyncio.run is the entry point.
import asyncio, hashlib, json, os, sys, urllib.request
BASE = "https://api.sume.com/v1"
def api_get(path):
req = urllib.request.Request(BASE + path, headers={"x-api-key": os.environ["SUME_API_KEY"]})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
def download(url, dest):
h, n = hashlib.sha256(), 0
with urllib.request.urlopen(url, timeout=120) as r, open(dest, "wb") as f:
for chunk in iter(lambda: r.read(1 << 20), b""):
f.write(chunk); h.update(chunk); n += len(chunk)
return h.hexdigest(), n
async def main(job_id):
result = await asyncio.to_thread(api_get, f"/jobs/{job_id}/result")
for i, art in enumerate(result["data"]["result"]["artifacts"]):
digest, n = await asyncio.to_thread(download, art["url"], f"{job_id}-{i}.bin")
want = art.get("checksum_sha256")
if want is None:
print(f"{art['id']}: no checksum published, {n} bytes")
elif want.lower() != digest:
sys.exit(f"{art['id']}: mismatch want={want} got={digest}")
else:
print(f"{art['id']}: ok, {n} bytes")
asyncio.run(main(sys.argv[1]))Running it
Run SUME_API_KEY=... python verify.py job_... once the job's terminal flag is true and result_ready is true. A 409 job_not_completed from the result route means the job is not finished yet; see when to stop polling and fetch.
When the digest does not match
Delete the partial file and retry the download once; a second mismatch is worth a support ticket with the job id and x-sume-request-id. Do not resubmit the paid job, because the output already exists. The standard-library hashlib.sha256 is documented in the Python docs.
Sources
Related posts
More in Developers
- Sume webhook signature header: why the sume-v1= prefix is checked
verifyWebhook only compares entries that start with sume-v1= and drops others, so a future scheme in the same header cannot break a receiver. A test proves it.
- v1/videos/models `created` is a catalog date, not a release date
Every model on Sume's /v1/videos/models shows created 1767225600, which is 2026-01-01. It is not when Gemini Omni 1.1 Flash or MiniMax H3 launched.
- Video filter stops at 300 seconds: which short-video lengths pass?
Sume's video filter refuses sources over 300 seconds. A 3-minute Short passes; a 10-minute TikTok ad source does not and needs a trim first.
- Video Router or /v1/videos for ported Sora code?
Both routes create the same Sume video jobs with the same model ids. /v1/videos is the OpenRouter-style wire; /v1/video-router/generate is the older flat one.
Written by Sume