How Sume Bills a Failed AI Video Job: Reserve, Refund, Retry

A video job reserves its price at submit and refunds on failure. See what Idempotency-Key does on a retry and when to download before storage expires.

5 min readSume
All posts

Video generation fails more often than text, because jobs run minutes and depend on an upstream provider. What matters to a buyer is whether a failure costs money and whether a retry can double-charge. Here is how Sume's video routes handle both, from its docs.

Reserve then refund

Every video model on Sume is billed at provider list times 1.25. The amount is reserved from the workspace USD balance when you submit, and a failed job refunds it. On a completed job, the poll response carries usage.cost, which is the billable amount, not the provider's list price. The job states are pending, in_progress, completed, failed and cancelled.

This is the opposite of a credit scheme with minimum charges. Runway's pricing page, for example, lists minimums for some models, such as 56 credits for Aleph 2. Sume's reserve is the price of the clip you asked for.

Retries and Idempotency-Key

Send an Idempotency-Key header on POST /v1/videos and a replay returns the original job rather than creating and charging a second one. Auto requests are deterministic under replay, so the same key prices and routes identically. Generate one key per intended clip, not per attempt, and persist it before the first request goes out.

A new key is a new job. If you want a different take of the same prompt, use a new key; no Sume video model accepts a seed, so a re-run is not a reproduction.

Download early

Google's Veo page says generated videos are stored for 2 days. Do not assume longer for any host: fetch the file as soon as the status is completed and keep your own copy. The script below submits, polls, downloads and prints the billed amount.

import os, sys, time, uuid
import requests

key = os.environ.get("SUME_API_KEY", "")
if not key:
    sys.exit("Set SUME_API_KEY first")
auth = {"Authorization": f"Bearer {key}"}
body = {"model": "wan-3.0", "prompt": "A ceramic mug rotating on a desk, soft daylight",
        "duration": 4, "resolution": "480p", "aspect_ratio": "16:9"}
r = requests.post("https://api.sume.com/v1/videos", json=body, timeout=60,
                  headers={**auth, "Idempotency-Key": str(uuid.uuid4())})
r.raise_for_status()
job = r.json()
while job["status"] in ("pending", "in_progress"):
    time.sleep(15)
    job = requests.get(job["polling_url"], headers=auth, timeout=60).json()
if job["status"] != "completed":
    sys.exit(f"{job['status']}: {job.get('error')}")
video = requests.get(job["unsigned_urls"][0], headers=auth, timeout=300)
with open("clip.mp4", "wb") as f:
    f.write(video.content)
print("saved clip.mp4, billed", job["usage"]["cost"])

Webhooks instead of polling

Pass an HTTPS callback_url and Sume POSTs on a terminal state, with x-sume-webhook-timestamp and x-sume-webhook-signature headers over the raw JSON body. Verify the signature with a non-empty secret, and treat the poll endpoint as the source of truth if a delivery is missed. Job status is also at GET /v1/jobs/{id}/status.

See the model checklist for choosing what to submit.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume