Download a 4K Gemini Omni clip from /content: redirect, 409 and retry

GET /v1/videos/{id}/content answers 302 to the file, or 409 job_not_completed while rendering. Python that reads the redirect without leaking your API key.

4 min readSume
All posts

To download a finished Sume video, call GET /v1/videos/{id}/content?index=0. It answers with a redirect to the artifact, or with 409 job_not_completed while the job still runs. A safe client reads the Location header itself and fetches the file without sending your Authorization header to the media host.

What a 4K Omni clip changes

Gemini Omni Flash 1.1 is in the Sume catalog at 3 to 10 seconds with 360p, 720p, 1080p and 4K, 16:9 or 9:16, and native synced audio. A 4K file is large. Google's own Omni docs say that videos larger than 4MB should be delivered by URI instead of inline data, so plan for a download step, not a base64 field.

On Sume the poll response carries unsigned_urls, and each entry points at the content route of the same job. The route is authenticated with your key and redirects to the stored artifact.

There is a second reason to avoid inline delivery in your own design. A 4K clip held in memory, or encoded as text, multiplies your peak memory per worker. Streaming the redirect target straight to disk, as the code below does with copyfileobj, keeps a worker at a flat memory profile no matter how many 4K clips it fetches in a day.

The three answers you will see

The content route has a small, stable set of outcomes. Treat them differently, because only one is worth retrying.

GET /v1/videos/{id}/content outcomes (Sume docs, read 2026-10-05)
HTTPCodeMeaningRetry?
302noneRedirect to the artifact URLFollow once
409job_not_completedJob still runningYes, after a poll
409job_failedTerminal failureNo, read the job error
404job_not_foundUnknown or foreign jobNo

Download without forwarding the key

Python's urllib follows redirects by default and carries your headers along. To keep the bearer token on api.sume.com only, turn redirects off, read Location, then fetch it bare.

import os, shutil, sys, time, urllib.error, urllib.request

class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, *a, **k):
        return None

def download(job_id, out="clip.mp4", tries=6):
    url = f"https://api.sume.com/v1/videos/{job_id}/content?index=0"
    req = urllib.request.Request(url, headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"]})
    opener = urllib.request.build_opener(NoRedirect)
    for n in range(tries):
        try:
            opener.open(req, timeout=30)
        except urllib.error.HTTPError as e:
            if e.code in (301, 302, 303, 307, 308):
                with urllib.request.urlopen(e.headers["Location"], timeout=120) as r, open(out, "wb") as f:
                    shutil.copyfileobj(r, f)
                return out
            if e.code == 409 and b"job_not_completed" in e.read():
                time.sleep(min(5 * 2 ** n, 60))
                continue
            raise
    raise TimeoutError("still rendering after retries")

print(download(sys.argv[1]))

Why 409 job_not_completed is not an error

Sume documents that code as retryable with a next action of polling status. That is the opposite of job_failed, which carries retryable false and tells you to inspect events. A client that retries both will loop forever on a render that never existed, so branch on the code, not on the status number.

Run it as python download.py job_id after your poll loop reports completed. If you call it early, the backoff above waits 5, 10, 20, 40, 60 and 60 seconds after each failed try.

The 409 retry loop has a ceiling on purpose. Six tries with doubling waits is about three minutes of patience. If a job is still not complete after that, you are not in a download problem, you are in a poll problem, so go back to the status route and let the job tell you its state before you ask for bytes again.

Checks before you ship it

  • Store the job id, not the redirect URL. The redirect target is a delivery detail.
  • Pass index only when the model makes several outputs. The default is 0.
  • Download promptly and keep your own copy. Retention differs by vendor, and the file you need next month should live in your storage.
  • Edit mode for Omni takes a source video of 10 seconds or less on Google's side, so trim inputs before you submit.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume