H3 Max Recast in TypeScript: submit, poll, read the result
A runnable Node script for Recast on Sume: submit with an Idempotency-Key, poll status honoring next_poll_after_seconds, then fetch the result.

The loop in short
Recast is a long job: fal prices it per second of source video, so a 30-second clip is not an instant answer. Sume's Jobs and results page says the 30-second sync wait is a budget for the HTTP request, not a limit on the job, and that anything that can outlast it should be submitted async and polled.
The loop is three steps. Submit with mode: async and an Idempotency-Key. Poll GET /v1/jobs/{id}/status until terminal is true, waiting next_poll_after_seconds when it is present. Read GET /v1/jobs/{id}/result when result_ready is true.
The script
Run it with Node 18 or newer through a TypeScript runner such as tsx. It uses the built-in fetch, wraps everything in main() so nothing needs top-level await, and exits when the key is missing. Replace the two URLs and set duration to the source length rounded up.
const base = "https://api.sume.com";
const key = process.env.SUME_API_KEY ?? "";
const headers = { Authorization: `Bearer ${key}`, "Content-Type": "application/json" };
const sleep = (s: number) => new Promise((r) => setTimeout(r, s * 1000));
async function main(): Promise<void> {
if (!key) throw new Error("Set SUME_API_KEY first");
const submit = await fetch(`${base}/v1/video-router/generate`, {
method: "POST",
headers: { ...headers, "Idempotency-Key": "recast-ts-001" },
body: JSON.stringify({
model: "h3-max-recast",
video_url: "https://example.com/source.mp4",
reference_image_urls: ["https://example.com/new-host.jpg"],
resolution: "768p",
duration: 12,
mode: "async",
}),
});
const sent = await submit.json();
if (!submit.ok) throw new Error(JSON.stringify(sent));
const id: string = sent.request_id ?? sent.job?.id;
for (;;) {
const s = await (await fetch(`${base}/v1/jobs/${id}/status`, { headers })).json();
if (s.terminal) {
if (!s.result_ready) throw new Error(`Job ended: ${s.sume_status}`);
break;
}
await sleep(s.next_poll_after_seconds ?? 10);
}
const result = await fetch(`${base}/v1/jobs/${id}/result`, { headers });
console.log(JSON.stringify(await result.json(), null, 2));
}
main().catch((e) => { console.error(e); process.exit(1); });What each part protects
- The Idempotency-Key makes a retried submit return the original job instead of billing a second one.
- The
terminalcheck stops the loop on completed, failed or canceled; reading the result of a job that did not complete returns409 job_not_completed. - A client-side timeout does not cancel the job. It keeps running and billing, so store the id before you start polling.
durationmust be the real source length, rounded up and between 5 and 30. fal's page, read 2026-10-03, lists $0.30 per second at 768p and $0.45 at 1080p, so the number drives the price.
Running it
Install tsx or use any TypeScript runner that supports Node's built-in fetch, set SUME_API_KEY in the shell, replace the two example URLs with public HTTPS files, and run the file. The first thing to check is the submit response: a 400 means a field was refused, for example a photo URL that is not public or a duration outside 5 to 30, and a 402 means the balance cannot cover the reservation.
Once the job is accepted the id is all you need. If your process dies, start a new one, read GET /v1/jobs/{id}/status, and carry on from there. Do not submit again, because a second submit is a second paid swap unless you reuse the same Idempotency-Key.
Sources
Related posts
More in Developers
- Hatchet durable event wait: let a Sume webhook wake the task
A Hatchet durable task can wait for an event instead of polling. Send Sume's signed terminal webhook into that event, and keep a status read as the fallback.
- Hatchet durable tasks: checkpoint a Sume job id and never pay twice
A Hatchet durable task replays from its last checkpoint after a crash. Make the Sume submit step safe with one Idempotency-Key, then wait on the job id.
- Hedged requests on a paid video API: same key, one job
Can you hedge a slow Sume submit to cut tail latency? Only with the same Idempotency-Key. Why a second key is a second bill, and what 409 in use means.
- Hey API openapi-ts: generate a Sume client from reference/json
Point @hey-api/openapi-ts at https://api.sume.com/reference/json, pin the version, and send one credential header. When the official SDK is the shorter route.
Written by Sume