A 3-minute Timeline render: poll job status, don't sleep a fixed time
Timeline renders are async by default. Submit with an Idempotency-Key, poll /v1/jobs/:id/status until terminal, then read /result. Cost: 3 minutes is $0.30.

Submit the render, then poll GET /v1/jobs/:id/status until its terminal flag is true, and read GET /v1/jobs/:id/result when result_ready is true. Sume documents no render time for Timeline, so do not hard-code a sleep; the docs describe a polling flow, and the default mode is async. A 3-minute render reserves ceil(180 / 60) = 3 output minutes, which is $0.30 at $0.10 per minute.
YouTube's three-minute Shorts page says Shorts can be up to 3 minutes, which is why a 180-second render is a common target; the cost arithmetic is the same for any length.
The flow
The render needs an Idempotency-Key header, and all sources must be media.sume.com artifacts, imported first through POST /v1/media-imports. The submit returns a job with type: timeline_render and model: sume/timeline-1.0. When the job is result_ready, the result has kind: timeline_render, video_url, duration_seconds, segment_count, billable_minutes and optional warnings[].
A longer program with many slots is chunked: render.strategy defaults to auto, which chunks past 12 segments. A render that sets single above 12 slots is refused with render_strategy_unsafe. The job is worker ffmpeg only, with no provider inference.
| Step | Call | What you get |
|---|---|---|
| Plan (optional) | POST /v1/timeline-1.0/plan | duration_seconds, segment_count, billable_minutes, estimated_cost_usd_micros |
| Submit | POST /v1/timeline-1.0/render with Idempotency-Key | A job with a request_id |
| Poll | GET /v1/jobs/:id/status | terminal and result_ready flags |
| Read | GET /v1/jobs/:id/result | video_url, billable_minutes, warnings[] |
A polling loop
The script below polls, obeys next_poll_after_seconds when the response has it and otherwise backs off, and stops when terminal is true. It reads the result only when result_ready is true and prints the status otherwise. Pass the job id as an argument.
import asyncio, os, sys, urllib.request, json
BASE = "https://api.sume.com/v1/jobs/"
HEAD = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}
def get(path):
req = urllib.request.Request(BASE + path, headers=HEAD)
return json.load(urllib.request.urlopen(req))
async def main():
job = sys.argv[1]
delay = 2
for _ in range(40):
status = get(job + "/status")
if status.get("terminal"):
ok = status.get("result_ready")
print(get(job + "/result") if ok else status)
return
delay = status.get("next_poll_after_seconds") or min(delay * 1.5, 15)
await asyncio.sleep(delay)
print("gave up; keep the job id", job)
asyncio.run(main())Safe retries
If your submit call times out, send the same request again with the same Idempotency-Key. That is what the key is for, and it protects you from paying for a second render. Keep the key and the job id together in your records so a restart of your own script can resume polling rather than submitting again.
For a webhook instead of polling, the communication fields (mode, webhook_url, wait_timeout_seconds) are shared by the Sume job endpoints; see the Sume jobs docs for details.
Cost before you start
Use the plan call first for a long render. It is free of an idempotency key and creates no job, and for 180 seconds of audio it reports 3 billable minutes and 300,000 micros of estimated cost, which is $0.30.
What can make a render fail
Most refusals happen at submit, before any job exists: an off-host URL is refused with unsupported_media_source, a dead one with source_not_found, a first slot that does not start at 0 with timeline_must_start_at_zero, and ffmpeg keys such as codec with a 400. Those are rejected at admit, so no job is created.
If a job reaches a terminal state without a result, the status response says so through the same terminal flag with result_ready false. Read /events for the sequence of what happened, and fix the program before you resubmit under a new idempotency key.
Soft warnings, such as a padded or looped short source, arrive in warnings[] on a successful result and are not failures; log them, but do not retry because of them.
A long render in practice
For a 180-second program with, say, 30 slots, expect the job to run as chunks, because auto chunks past 12 segments. You do not manage the chunks; you still poll one job. Keep the job id and the key, and set a generous attempt limit in your loop with a growing delay, as in the script above, so a slow render does not turn into a flood of status calls.
Sources
Related posts
More in Developers
- Image-to-video not starting on my photo: frame_images vs references
Your photo is a reference, not a first frame, when it goes in input_references. Use frame_images with first_frame on Sume /v1/videos to pin the opening shot.
- Japanese speech to text API: Sume STT with language_code ja
Transcribe Japanese audio with Sume STT: send language_code ja, read word times, and test a sample first. $0.01 per audio minute, 10 minute jobs.
- Job id or run id? Which Sume endpoint to poll for each product
Jobs, Format runs, Actions and Agent Completions have different ids, poll URLs and webhook events. Which to poll for each product, and which SDK helper to call.
- Let browsers start Sume jobs through your server, not with your key
Browsers must never hold a Sume API key. A server route authenticates the user, checks the input, derives an Idempotency-Key and returns only the status URL.
Written by Sume