Same prompt, four Sume video models: a Python script that logs cost
Submit one prompt to wan-3.0, minimax-h3, minimax-h3-max and seedance-2.5 on /v1/videos, poll each job, and print usage.cost per clip. Runnable as written.

To compare video models fairly, send the same prompt, duration and aspect ratio to each Sume model id, wait for every job, and record usage.cost from the finished job. The script below does that for wan-3.0, minimax-h3, minimax-h3-max and seedance-2.5 at 480p and 6 seconds, using only the /v1/videos calls documented on the video generation page. Run it once, look at the four clips, then decide where to spend.
A 480p run is the cheap end of each row, so it tests the prompt and motion, not the final look.
Why these four at 480p
All four rows accept 480p, a 6-second duration and 16:9, so one request body is valid for each. Their ranges differ at the edges: wan-3.0 allows 2 to 30 seconds, the MiniMax rows 5 to 15, and seedance-2.5 4 to 30. A 6-second clip sits inside all three, and that is why the script uses it.
| Model id | Duration range | Resolutions | Audio |
|---|---|---|---|
wan-3.0 | 2 to 30 s | 480p, 720p, 1080p | native |
minimax-h3 | 5 to 15 s | 480p, 768p | native stereo, always on |
minimax-h3-max | 5 to 15 s | 480p, 768p, 1080p | native stereo, always on |
seedance-2.5 | 4 to 30 s | 480p, 720p, 1080p | native |
The script
It reads the key from SUME_API_KEY, gives each submit its own Idempotency-Key, polls every 20 seconds, and prints the status and cost. Stop it with Ctrl-C; jobs already submitted keep running and holding a reserve until they finish.
import os, time, requests
BASE = "https://api.sume.com/v1/videos"
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
PROMPT = "A potter shapes a bowl on a wheel, warm window light"
MODELS = ["wan-3.0", "minimax-h3", "minimax-h3-max", "seedance-2.5"]
jobs = {}
for m in MODELS:
body = {"model": m, "prompt": PROMPT, "resolution": "480p",
"duration": 6, "aspect_ratio": "16:9"}
r = requests.post(BASE, json=body, timeout=60,
headers={**H, "Idempotency-Key": f"cmp-{m}-001"})
r.raise_for_status()
jobs[m] = r.json()["polling_url"]
while jobs:
for m, url in list(jobs.items()):
j = requests.get(url, headers=H, timeout=30).json()
if j["status"] in ("completed", "failed", "cancelled"):
print(m, j["status"], j.get("usage", {}).get("cost"))
del jobs[m]
time.sleep(20)Reading the output
usage.cost is the Sume billable amount, per the Sume differences table. A failed job has no finished clip to price, and its reserve is released. Keep the four MP4s next to the printed costs and judge them blind.
- Same prompt does not mean same take: no row accepts a seed, so each run is one sample.
- The cheapest clip is not the best value if it needs three re-rolls.
- A second run with new Idempotency-Keys is a second set of samples; reusing the key returns the original job.
Next step
After picking a row, move to 720p or 768p and a longer duration. See the Video Router page for per-model limits and Jobs and results for the status lifecycle. If you prefer a webhook to polling, callback_url takes an HTTPS address and the signature headers are described in Video generation.
Sources
Related posts
More in Developers
- What to save from a Sume run when batch results expire at 30 days
OpenAI keeps batch output 30 days, Anthropic 29, Gemini 6 weeks. Which Sume run receipt fields to store so your records outlive any vendor retention window.
- Why a Sume scheduled run's events_url is null, and what to poll
Action runs always return events_url null; Format runs expose a phase timeline. What to poll for a scheduled run and how to read skipped.
- isTerminalJobStatus vs isTerminalRunStatus: skipped only ends runs
The Sume SDK has two terminal checks. Jobs end on completed, failed or canceled; runs also end on skipped. Reusing one for both breaks a custom poll loop.
- Seedance 1.5 Pro retires Nov 11: pin a live Sume model id
ElevenLabs says ByteDance retires Seedance 1.5 Pro on Nov 11, 2026. Pin an id Sume's video catalog lists today and verify its limits first.
Written by Sume