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.

4 min readSume
All posts

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

  • model is h3-max-recast.
  • video_url is the source clip. Recast inspects it and bills its length, which must be between 5 and 30 seconds.
  • reference_image_urls holds one to four photos, one per new person.
  • resolution is 768p (the default) or 1080p.
  • mode: "async" returns a job id straight away. The legacy video-router route takes these flat fields, while POST /v1/videos takes an input_references array 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 once sume_status is completed. A failed or canceled job 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.retryable before 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

All Developers posts

Written by Sume