TypeScript union for a Sume /v1/videos poll with an exhaustive switch
Model the five poll statuses as a discriminated union so unsigned_urls only exists on completed and error only on failed. Compiles in strict mode, 30 lines.

Type the Sume poll response as a union on status, and the compiler will stop you from reading unsigned_urls on a job that is not completed. The five statuses are pending, in_progress, completed, failed and cancelled. A switch with a never check in the default branch also fails the build if Sume adds a sixth.
The shape of the response
The route returns bare objects, not a { data } envelope, so the type is the response itself. Fields common to every state are id, polling_url, status, and the optional generation_id and model. The docs show unsigned_urls on completed jobs and error on failed ones, and the API source puts usage on any state once an amount exists.
| Status | Extra fields you can rely on |
|---|---|
| pending | none |
| in_progress | none |
| completed | unsigned_urls (one per output) |
| failed | error (a single string) |
| cancelled | none |
The types and the switch
Put the types in one file and import them where you poll. The function describe returns text for each case; the never assignment is the exhaustiveness check. In a strict TypeScript project this compiles as shown.
type Base = {
id: string;
polling_url: string;
generation_id?: string;
model?: string;
usage?: { cost: number | null };
};
export type VideoPoll =
| (Base & { status: "pending" | "in_progress" | "cancelled" })
| (Base & { status: "completed"; unsigned_urls: string[] })
| (Base & { status: "failed"; error?: string });
export function describe(p: VideoPoll): string {
switch (p.status) {
case "pending": return "queued";
case "in_progress": return "rendering";
case "completed": return `ready: ${p.unsigned_urls[0]}`;
case "failed": return `failed: ${p.error ?? "no reason given"}`;
case "cancelled": return "cancelled";
default: {
const unreachable: never = p;
return unreachable;
}
}
}Using it in a loop
With the union in place, the polling loop is a plain while over await getPoll(url), and the only place that reads unsigned_urls is the completed branch. The compiler will reject p.unsigned_urls in any other branch, which removes a whole class of undefined is not iterable errors in production code that nobody tested with a failed job.
Where the type stops
A type is a promise, not a check. The JSON that comes off the wire can differ from it, so validate at the boundary if the data matters. Sume maps any status it does not recognise to failed on this route, which means the union is complete today, but keep the default branch so a future status is a compile error and not a silent hole.
If you also read jobs from /v1/jobs, remember that surface spells it canceled and uses queued and processing. Keep a second union for it; the three vocabularies post has the mapping, and generating a typed client from the OpenAPI JSON covers the other route.
unsigned_urlsis a list because a model can return more than one output.erroris one string; there is no error object on the poll.- Do not reuse this type for webhook bodies, which use
eventandjob_id.
Sources
Related posts
More in Developers
- TypeScript exhaustive switch on /v1/videos statuses fails the build
A never-typed default case makes a new video job status a compile error, and a runtime parser rejects strings the types do not know. 26 lines of TypeScript.
- Unit-test your Omni price math before Oct 22: Python asserts for Sume
A runnable Python test for Sume's gemini-omni-flash-1.1 pricing: Decimal rates, the 3 to 10 second rule, and a one-cent check against the usage.cost on a poll.
- One Sume webhook route for job and run events, 204 on the rest
Route job.* and *.run.terminal events from one verified webhook handler and answer 204 for any unknown event, so a new type cannot trigger retries.
- urllib3 Retry on POST: retry a Sume submit only with a key
A urllib3 Retry config that retries 429, 502, 503 and 504 on a Sume submit, and a wrapper that refuses to send a POST without an Idempotency-Key.
Written by Sume