Video trim is async by default: mode sync waits 30 s, then poll

Video trim returns a job at once by default. Send mode sync to wait up to 30 s for a 200, else get 202 and poll status, then result. Never resubmit.

5 min readSume
All posts

Video trim defaults to async mode. To wait for the result in the same request, send mode sync: the docs say the handler waits up to 30 seconds and returns 200 with the completed job, or 202 with the job if it did not finish in time. In both cases you pay $0.02 once, so never repeat the create just because a wait ended (as of 2026-10-08).

Three outcomes

Which path you take is decided by the mode and by how long the cut takes.

Trim response paths, read 2026-10-08
ModeFinishes in 30 sDoes notCharge
async (default)202 with a queued job202 with a queued job$0.02
sync200 with the completed job202, then poll$0.02
Retry with the same Idempotency-KeySame job backSame job backNo second charge

Poll status, then read the result

Per the jobs docs, statuses are queued, processing, completed, failed and canceled. Stop polling on completed, failed or canceled, use exponential backoff, and fetch the result only after completion; an early read returns a conflict, not an empty result. The script prints the raw status body and stops on a terminal word.

import os, time, urllib.request

KEY = os.environ.get("SUME_API_KEY", "")
if not KEY:
    raise SystemExit("set SUME_API_KEY")
JOB = os.environ.get("JOB_ID", "")
if not JOB:
    raise SystemExit("set JOB_ID")

def get(path):
    req = urllib.request.Request("https://api.sume.com/v1/jobs/" + JOB + path,
                                 headers={"Authorization": "Bearer " + KEY})
    with urllib.request.urlopen(req, timeout=30) as r:
        return r.read().decode()

delay = 1.0
for _ in range(8):
    body = get("/status")
    print(body)
    if any(w in body for w in ("completed", "failed", "canceled")):
        break
    time.sleep(delay)
    delay = min(delay * 2, 30)
print(get("/result"))

Why not resubmit

A local timeout is not a failed job. The jobs docs say the job can keep running and billing while your client has stopped waiting, and that you should not submit the original paid request again for that reason alone. With the same Idempotency-Key a repeat returns the original job, but a new key creates a second $0.02 job. Keep the key and the job id in your own records, and poll the id you were given.

Choosing a mode

Use sync for a quick interactive cut, such as a 15-second teaser from a short source, where one round trip is convenient and a 202 is the rare case you handle by polling. Use the async default for batches, since the create returns at once and you can poll in your own time. Either way the code path must handle both a completed job and a queued one, because sync is allowed to return 202. The exact cut of 59 seconds is $0.02 in each mode.

Reading the finished job

When the job is complete, GET /v1/jobs/:id/result returns the trim result: a new video_url that is never the source, duration_seconds, actual_start_seconds and precision, plus any warnings. For a 59-second exact cut, duration_seconds should read 59 and actual_start_seconds should equal your start. If the end point passed the end of the source, the warning trim_clamped_to_source tells you the file is shorter than you asked for. Store the new video_url, since the clip is a fresh artifact and the next step, such as a caption job or a Timeline slot, takes that URL.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume