Stream a Sume video download in Python: .part file, then rename
Download /v1/videos/{id}/content with requests stream=True, write a .part file and rename on success. A lost 30 s clip costs $1.875 to $17.334 to re-buy.

Download a finished Sume clip with GET /v1/videos/{id}/content?index=0, your API key in the header, requests stream=True, and write to a .part file that you rename only after the last chunk. A half-written MP4 that looks complete is the failure to design against, because re-making a 30 s clip costs real money.
What a lost file costs
The content endpoint needs your key, and the poll response lists it in unsigned_urls, so the file is a plain authenticated download once the job is completed. If your process dies mid-write and a later step treats the partial file as done, you pay for the render again.
The figures below are billable USD for the 30 s clips most likely to be downloaded in a batch.
| Model and resolution | Rate per second | 30 s total |
|---|---|---|
| wan-3.0 480p | $0.0625 | $1.875 |
| wan-3.0 720p | $0.125 | $3.75 |
| wan-3.0 1080p | $0.25 | $7.50 |
| seedance-2.5 720p | $0.5778 | $17.334 |
The download function
Stream in 1 MiB chunks, so memory stays flat for any clip size. The (10, 60) timeout is connect then read, which stops a stalled socket without cutting a slow but moving one.
import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
def save(job_id, path, index=0):
url = f"https://api.sume.com/v1/videos/{job_id}/content"
tmp = path + ".part"
with requests.get(url, headers=HEADERS, params={"index": index},
stream=True, timeout=(10, 60)) as r:
if r.status_code == 409:
raise RuntimeError(r.json()["error"]["code"])
r.raise_for_status()
with open(tmp, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 20):
f.write(chunk)
os.replace(tmp, path)
return pathGotchas
A 409 means the job is not ready: job_not_completed is retryable, job_failed is not. Poll the job until status is completed before you call the content endpoint, then branch on the error code instead of retrying everything.
Compare the bytes written with the Content-Length header when the server sends one, and keep the job id until the rename succeeded. The docs state no expiry for result URLs, so copy the file to your own storage if you need it long term.
Sources
Related posts
More in Developers
- Sume STT: omit duration_seconds and it reserves 1 minute, not 9
A 9-minute file is $0.09 of Sume STT, but without duration_seconds the reserve is 1 minute, $0.01. Send 540. The field takes 1 to 600 seconds.
- Sume 415 unsupported_media_type: read details.received_content_type
A 415 from the Sume API means the body was not sent as application/json. Which client defaults cause it, how to read the details field, and the one-line fix.
- sume/auto on a retried submit: same job, model still sume/auto
A retry of a sume/auto video with the same Idempotency-Key returns the original job, price and route. A Python check, plus why the family is never disclosed.
- sume/auto and aspect_ratio 8:1: name Nano Banana 2.1 instead
sume/auto does not tell you which family ran, and the repo gives it the GPT Image ratio list, which has no 4:1 or 8:1. Name google/nano-banana-2.1 for strips.
Written by Sume