Recast Idempotency-Key from a hash of the request (Python)
Derive the Idempotency-Key from the canonical request body so a retry is a replay and an edited request is a new job. Python code for Recast on /v1/videos.

A Recast job is priced per second of source video. fal lists H3 Max Recast at $0.30 per second at 768p and $0.45 per second at 1080p (read 2026-10-03), and Sume bills the list price times 1.25. A 30 second clip submitted twice by a flaky client is a real double charge, so the submit call needs a key that makes a retry harmless.
Sume's Idempotency-Key header does that. Send the same key with the same request and you get the original job back with the same id. Send the same key with a different request and you get 409 idempotency_conflict. The sentence that matters in practice is the second one: a hand-typed key reused after you edit the prompt fails instead of silently submitting the old job.
Let the request name its own key
The safest key is a function of the body. Serialize the request with sorted keys and fixed separators, hash it, and use the digest. An identical retry produces the identical key, and any change to a photo URL, the resolution or the prompt produces a different one, so the 409 case cannot occur by accident. The take argument is a deliberate salt: bump it when you want a new take of the same request, because a second take of an unchanged body would otherwise replay the first.
import hashlib, json, os, urllib.request
API = "https://api.sume.com"
def idem_key(body: dict, take: int = 1) -> str:
canon = json.dumps(body, sort_keys=True, separators=(",", ":"))
return f"recast-t{take}-" + hashlib.sha256(canon.encode()).hexdigest()[:32]
def submit(body: dict, take: int = 1) -> dict:
req = urllib.request.Request(
f"{API}/v1/videos", json.dumps(body).encode(), method="POST",
headers={"x-api-key": os.environ["SUME_API_KEY"],
"content-type": "application/json",
"Idempotency-Key": idem_key(body, take)})
with urllib.request.urlopen(req, timeout=60) as r:
return json.load(r)
body = {"model": "h3-max-recast", "resolution": "768p", "input_references": [
{"type": "video_url", "video_url": {"url": "https://media.sume.com/source.mp4"}},
{"type": "image_url", "image_url": {"url": "https://media.sume.com/host.jpg"}}]}
first, again = submit(body), submit(body)
assert first["id"] == again["id"], "same body, same key: one job"
print(first["id"], submit(body, take=2)["id"])What the sample proves
- Two submits of one body return one job id, which the assert checks.
take=2changes only the prefix, so you pay for a second job on purpose.- The key is 32 hex characters plus a short prefix, short enough to log on every call.
- Hash the normalized request, not the headers or a timestamp. A timestamp in the key defeats the point.
Edge cases to plan for
A job refused with queue_full releases its key, so replaying the same request under the same key creates a fresh job once capacity returns. That is the behaviour you want for retry loops. Identical requests share one job, so add an order id to the hashed body if two orders must be billed separately.
If your source or photo URLs are signed and expire, hash a stable asset id instead of the URL. Otherwise a retry after the signature rotates looks like a new request and spends again. The video guide lists every field the /v1/videos surface accepts, and the jobs guide explains how replays and polling fit together.
Sources
Related posts
More in Developers
- Recast photo URL rejected: public HTTPS, no login in the URL
A Recast request is refused when a reference photo URL is private, signed or carries a username and password. What Sume accepts and how to fix each case.
- Recast sync wait caps at 30 seconds: use a webhook for long sources
Sync and subscribe modes wait at most 30 seconds, so submit h3-max-recast jobs as async or webhook and poll /v1/jobs/{id}/status as a fallback.
- Recast in TypeScript: upload your own files, submit, poll (SDK)
Upload a source MP4 and a host photo with uploadFile, submit h3-max-recast with generateVideoRouter and poll getApiJobStatus. Uses the published @sume-com/sdk.
- Recraft V4 on Sume returns WebP only: convert to PNG or JPEG in Python
Recraft V4 on Sume outputs WebP and takes no references. A Pillow converter for PNG or JPEG, with transparency flattened onto white for JPEG delivery.
Written by Sume