Save a finished Seedance clip to your own storage, not the result URL
When a Seedance 2.5 or Wan 3.0 job completes, read /v1/jobs/{id}/result, take the artifact url and content_type, and copy the file into your own storage.

Read GET /v1/jobs/{id}/result once the job reports result_ready: true, take each entry of result.artifacts (it has id, url, type, and content_type), and copy the file into your own storage. Store your copy's location next to the Sume job id, so you never depend on a single URL later.
Sume returns Sume media URLs, never raw provider URLs, and a result exists only after completion. Before that, /result answers 409 job_not_completed rather than an empty body.
What a result holds
A completed video job returns the envelope below inside data. The type tells you what you have, and content_type gives the file extension to use.
{
"id": "job_...",
"status": "completed",
"result": {
"artifacts": [
{
"id": "artf_...",
"url": "https://media.sume.com/artifacts/...",
"type": "video",
"content_type": "video/mp4"
}
]
}
}Copy it
Stream to disk in chunks so a 30-second 1080p file does not sit in memory. Swap the file write for an upload call to your storage client. Name the file after the job id so a retry overwrites instead of duplicating.
import os, requests
BASE = "https://api.sume.com"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
def save(job_id: str, out_dir: str = ".") -> list[str]:
r = requests.get(f"{BASE}/v1/jobs/{job_id}/result", headers=H, timeout=30)
r.raise_for_status()
paths = []
for i, art in enumerate(r.json()["data"]["result"]["artifacts"]):
ext = art["content_type"].split("/")[-1]
path = os.path.join(out_dir, f"{job_id}-{i}.{ext}")
with requests.get(art["url"], stream=True, timeout=120) as dl:
dl.raise_for_status()
with open(path, "wb") as f:
for chunk in dl.iter_content(1 << 20):
f.write(chunk)
paths.append(path)
return paths
print(save("job_123"))What to keep
Keep the job id, the model id, and the prompt with the file. GET /v1/jobs/{id}/events gives a public timeline you can use to debug later, but it does not show provider task ids or raw provider URLs.
If you only need the file on a laptop, sume jobs download <job_id> --output-dir ./out does the same copy from the CLI.
Sources
Related posts
More in Developers
- Save a sidecar JSON with every Sume mp4: job id, model, cost, prompt
Export finished video jobs with a Python script that downloads each mp4 and writes a sidecar JSON with job id, model, cost and the prompt you saved.
- Scan your repo for retiring Google image and TTS ids (Python)
A 30-line Python scanner that finds Imagen 4, Nano Banana and Gemini TTS preview ids in your files and prints the replacement Google names for each one.
- Seedance 2.5 30-second clip via API: the request, poll and price
Send model seedance-2.5 with duration 30 to POST /v1/videos on Sume, poll the job, and expect about $17 at 720p 16:9. Request and Python poll loop.
- Seedance 2.5 30-second job: set a 20-minute client deadline in Python
A 30-second Seedance 2.5 job outlives any HTTP timeout. Submit async to Sume, poll with next_poll_after_seconds, and set the deadline in your client.
Written by Sume