H3 Max Recast in Python: submit, poll and read the result
A Python script for Sume's h3-max-recast: submit with an Idempotency-Key, poll until the job is terminal, read the result. Cost per clip included.

To run H3 Max Recast from Python on Sume, POST to /v1/video-router/generate with model: "h3-max-recast", a source video_url, one to four reference_image_urls, the source length in duration and mode: "async", then poll /v1/jobs/{id}/status until it is terminal and read /v1/jobs/{id}/result. The script below does exactly that with the requests package.
It follows the "client subscribe" pattern from Sume's Jobs and results page: submit async, poll with the server's next_poll_after_seconds, and read the result once. The model contract comes from the Video Router docs, and the description of what Recast does from fal's H3 Max Recast page (read 2026-10-02).
import os, sys, time, requests
BASE = "https://api.sume.com"
AUTH = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
def recast(video_url, photos, seconds, key):
body = {"model": "h3-max-recast", "video_url": video_url,
"reference_image_urls": photos, "duration": seconds,
"resolution": "768p", "mode": "async"}
r = requests.post(f"{BASE}/v1/video-router/generate", json=body,
headers={**AUTH, "Idempotency-Key": key}, timeout=60)
r.raise_for_status()
body = r.json()
job = body.get("request_id") or body["id"]
while True:
s = requests.get(f"{BASE}/v1/jobs/{job}/status", headers=AUTH).json()
if s.get("terminal"):
break
time.sleep(s.get("next_poll_after_seconds") or 10)
if s.get("sume_status") != "completed":
raise RuntimeError(f"job {job} ended as {s.get('sume_status')}")
return requests.get(f"{BASE}/v1/jobs/{job}/result", headers=AUTH).json()
if __name__ == "__main__":
video, photo, secs, key = sys.argv[1:5]
print(recast(video, [photo], int(secs), key))What does the script assume?
It needs SUME_API_KEY in the environment and takes four arguments: a public https URL for the source video, a public https URL for one person photo, the source length in whole seconds (5 to 30), and an idempotency key. Get the length by probing the clip first with video inspect; do not guess.
It sends 768p, Sume's default. Switch to "1080p" for the higher tier. The submit response is read for id, and I have not confirmed the exact result field names for a finished Recast job in the docs, so the script prints the whole result JSON instead of pulling a field. Look at that once, then pick the field you want.
Why is there an Idempotency-Key?
Recast jobs are paid, and a network timeout on submit can leave you unsure whether the job exists. Sume's docs say retrying the submit with the same Idempotency-Key returns the original job instead of billing a second one, and that you must not submit a new paid job for the same intent. The script takes the key as an argument for that reason: generate one value per intended clip, such as a UUID you store beside the job, and reuse it on every retry of that same clip.
If your own process dies mid-poll, do not resubmit. Keep the job id and resume polling it; the doc is explicit that a local timeout is not a reason to repeat the paid request.
What does a run cost?
Recast is billed per second of output. fal lists $0.30 per second at 768p and $0.45 at 1080p; Sume bills list times 1.25, which is $0.375 and $0.5625 per second. Sume reserves the cost on submit from the duration you send, rounded up to whole seconds.
| Source length | 768p | 1080p |
|---|---|---|
| 5 s | $1.88 | $2.81 |
| 12 s | $4.50 | $6.75 |
| 30 s | $11.25 | $16.88 |
What fails, and where do you see it?
Bad bodies are refused at submit, before any reservation; raise_for_status surfaces that as an HTTP error, and the response names the field. The list is in the rejected-fields post. A job that starts and then fails ends with a failed or canceled status, which the script turns into an exception that carries the job id.
The script has no timeout on the overall wait. Add a deadline if you run it in CI, but make it stop polling, not resubmit. For a general Python pattern on a different video model, see the MiniMax H3 Python job post.
Sources
Related posts
More in Developers
- H3 Max Recast request rejected: every field Sume refuses
Sume refuses H3 Max Recast bodies with aspect_ratio, generate_audio, bitrate_mode, frame or audio references, or a duration outside 5-30 s. The full list.
- Hedra legacy avatar API needs 4 calls; Sume avatar video needs 1 POST
Hedra's legacy avatar guide creates and uploads two assets, then generates and polls. Sume's talking-video route is one POST with a handle and a script.
- HeyGen bulk status for 100 videos at once vs Sume job webhooks
HeyGen added bulk status reads of up to 100 ids for videos, lipsyncs, translations and assets. Sume reads jobs one by one or pushes signed terminal webhooks.
- HeyGen POST /v3/templates makes a template from video; Sume has none
HeyGen's October API entry turns a video into a private template. Sume has no template object for avatar video: you keep the request JSON and avatar handle.
Written by Sume