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.

Recast takes two kinds of input that usually start life on your disk: a source video and one to four photos of the new people. The API wants public HTTPS URLs for both, so the first job is getting local files to URLs. The TypeScript SDK covers all three steps: upload, submit and poll.
This post uses only exports that ship in the published @sume-com/sdk 0.2.0 package: createSumeClient, uploadFile, generateVideoRouter and getApiJobStatus. Newer helpers such as waitForJob exist on the main branch but were not in the 0.2.0 tarball published on 2026-08-02, so the sample polls by hand.
What the request needs
modelish3-max-recast.video_urlis the source clip. Recast inspects it and bills its length, which must be between 5 and 30 seconds.reference_image_urlsholds one to four photos, one per new person.resolutionis768p(the default) or1080p.mode: "async"returns a job id straight away. The legacy video-router route takes these flat fields, whilePOST /v1/videostakes aninput_referencesarray instead.
Upload, submit, poll
uploadFile runs three calls for you: it creates an upload slot, PUTs the bytes and completes the asset, then returns the asset with its url. The sample sends an Idempotency-Key header, so a retried submit cannot create a second paid job. The loop reads terminal from the status response and waits at least as long as next_poll_after_seconds asks, with a 2-second floor.
import { readFile } from "node:fs/promises";
import { createSumeClient, generateVideoRouter, getApiJobStatus, uploadFile } from "@sume-com/sdk";
const client = createSumeClient({ apiKey: process.env.SUME_API_KEY ?? "" });
async function up(path: string, type: string) {
const file = new Blob([await readFile(path)], { type });
return (await uploadFile({ client, file, filename: path })).url;
}
const body = {
model: "h3-max-recast",
video_url: await up("source.mp4", "video/mp4"),
reference_image_urls: [await up("host.jpg", "image/jpeg")],
resolution: "768p" as const,
mode: "async" as const,
};
const headers = { "Idempotency-Key": "recast-source-001-host-a" };
const sub = await generateVideoRouter({ client, body, headers });
if (!sub.data) throw new Error(JSON.stringify(sub.error));
const id = sub.data.data.job.id;
for (;;) {
const s = await getApiJobStatus({ client, path: { id } });
if (!s.data) throw new Error(JSON.stringify(s.error));
const { terminal, sume_status, next_poll_after_seconds } = s.data.data;
if (terminal) { console.log(id, sume_status, s.data.data.result_url); break; }
await new Promise((r) => setTimeout(r, Math.max(2, next_poll_after_seconds ?? 0) * 1000));
}Things to know before you run it
- Run it as an ES module under Bun or a TypeScript runner with top-level await. Node needs a loader such as
tsx. - The result is at
result_url, which you fetch with the same API key oncesume_statusiscompleted. Afailedorcanceledjob is also terminal, so check the status before you fetch. - The sample has no timeout or error branch on purpose. For production, add a deadline to the loop and read
error.retryablebefore you resubmit. - Price depends on the source length and resolution. See the model docs for the current limits.
The SDK reference is at docs.sume.com/sdk, and the polling contract, including terminal, is in the jobs guide. The package page is npm.
Sources
Related posts
More in Developers
- 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.
- Redis SET NX EX to dedupe Sume webhooks by request_id
SET key value NX EX returns OK for the first delivery and nil for a duplicate. Claim a Sume webhook request_id, process it, then mark it done or release it.
- Redis lease so one worker polls each Sume job
Take a lease with SET NX EX and a random token, release it with a compare-and-delete script. One poller per Sume job saves reads, and duplicates stay harmless.
- Reel judders after mixing 24 and 30 fps clips: set Timeline output fps
Timeline repeats or drops frames when output fps differs from a source. Learn output_fps_resamples_sources, how the default is chosen, and when to pin 30.
Written by Sume