The 202 from POST /v1/videos has four fields: where the rest arrives
The Sume submit response returns only id, polling_url, status and model. Usage, unsigned_urls and error come on the poll. A parser that expects no more.

The 202 Accepted body from POST /v1/videos has four fields: id, polling_url, status and model. The status is pending. There is no usage, no unsigned_urls and no error yet; those arrive on the poll at polling_url as the job moves on.
What each field is for
The shape matches the OpenRouter video API, so a client written from those docs reads it without change. polling_url is an absolute URL on api.sume.com, so you can pass it straight to your HTTP client. model echoes what you asked for: a pinned id comes back as that id, and sume/auto comes back as sume/auto.
| Field | On the 202 | On the poll |
|---|---|---|
| id | yes | yes |
| polling_url | yes | yes |
| status | pending | pending, in_progress, completed, failed or cancelled |
| model | yes | yes |
| generation_id | no | yes (same value as id) |
| unsigned_urls | no | completed jobs |
| usage.cost | no | once an amount exists |
| error | no | failed jobs |
A parser that expects four fields
Code that reads usage.cost from the 202 will get undefined, and code that assumes unsigned_urls[0] exists right after submit will throw. The parser below takes only what the 202 carries, and it refuses a response without a polling_url, which is the sign that the submit failed.
It also checks the status code, because a 400, 401, 402, 404, 415 or 429 returns the standard error envelope with an error object, not these four fields.
type Submitted = { id: string; pollingUrl: string; model?: string };
export async function submit(body: object, key: string): Promise<Submitted> {
const res = await fetch("https://api.sume.com/v1/videos", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SUME_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": key,
},
body: JSON.stringify(body),
});
const json = await res.json();
if (res.status !== 202 || typeof json.polling_url !== "string") {
const e = json.error ?? {};
throw new Error(`${res.status} ${e.code ?? "?"} request_id=${e.request_id ?? "?"}`);
}
return { id: json.id, pollingUrl: json.polling_url, model: json.model };
}Why it is short
OpenRouter returns the same thin body on submit, and Sume copies it on purpose: the point of this surface is that a client written from the OpenRouter docs works after you change the base URL and the key. Anything that needs the job to finish, such as a URL or a price, belongs to the poll, where the job has the data.
The base path is https://api.sume.com/v1/videos, with no /api segment, and the model ids are bare catalog ids, which are the two places a ported client usually needs a change.
What to do next
Save id and your idempotency key before you start polling, so a crash between the two does not lose the job. Then poll polling_url about every 30 seconds, as the docs suggest, or wait for a callback_url delivery. The two work together: the webhook is the fast path and the poll is the backup.
An idempotent replay of the submit returns the original job, so calling submit again with the same key after a timeout is safe. Replays and conflicts are covered in the 409 conflict post, and the field list of the finished poll is in the status vocabulary post.
- The 202 is not a promise of success; the job can still fail.
- Do not show a price from the 202; read
usage.costfrom the poll. - Do not assume
statusisqueued: that word belongs to/v1/jobs.
Sources
Related posts
More in Developers
- Threads API video rules: H.264, AAC, edit lists, and what Sume covers
Meta's Threads page lists MP4 or MOV, H.264 or HEVC, AAC, no edit lists and a front moov atom. Which of these Sume documents, and which you must check.
- Threads API video max is 300 s: split a 21-minute talk into parts
The Threads API page lists video up to 300 seconds and 1 GB. Split a 21-minute recording into five trim requests with a small Python script. $0.02 per trim.
- Threads video aspect ratio runs 0.01:1 to 10:1: Sume Timeline sizes
Meta's Threads page allows ratios from 0.01:1 to 10:1 with 9:16 recommended and 1920 px max width. Sizes that Sume Timeline can output inside those bounds.
- TikTok 500 MB cap and 10 minutes: the bitrate that fits is 6.67 Mbps
TikTok's non-Spark in-feed ad page allows 10 minutes and 500 MB or less. A full 10 minute ad fits only at 6.67 Mbps or below. The arithmetic and a Sume render.
Written by Sume