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.

5 min readSume
All posts

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.

Recast cost per clip on Sume at list times 1.25 (read 2026-10-02)
Source length768p1080p
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

All Developers posts

Written by Sume