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.

5 min readSume
All posts

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.

Job states and calls for a Timeline render (read 2026-10-07 from the Sume docs)
StepCallWhat you get
Plan (optional)POST /v1/timeline-1.0/planduration_seconds, segment_count, billable_minutes, estimated_cost_usd_micros
SubmitPOST /v1/timeline-1.0/render with Idempotency-KeyA job with a request_id
PollGET /v1/jobs/:id/statusterminal and result_ready flags
ReadGET /v1/jobs/:id/resultvideo_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

All Developers posts

Written by Sume