Tcl http package: submit a Gemini Omni video job and follow the 302

A 22-line Tcl script using the http, json and tls packages submits a 3 s 360p Gemini Omni job on Sume ($0.1125), polls it and follows the content redirect.

4 min readSume
All posts

Tcl still sits inside EDA flows, network gear scripts and test harnesses where nobody will add a Python dependency. It ships with an HTTP client, and that is enough for Sume's video API, which is plain JSON over HTTPS. The script below submits the cheapest kind of job, Gemini Omni Flash at 3 seconds and 360p, which Sume prices at $0.0375 a second, so $0.1125 for the clip.

It needs three packages. http is in the core distribution, tls provides HTTPS, and json comes from tcllib, which some minimal installs leave out. Install tcllib first if package require json fails.

The script

Set SUME_API_KEY and run tclsh video.tcl. I ran it against a local stand-in server that mimics the 202, poll and 302 sequence, with the poll delay shortened, and it wrote the file and printed the cost. It was not run against the live API.

package require http
package require json
package require tls
http::register https 443 ::tls::socket
set base [expr {[info exists ::env(SUME_API_BASE)] ? $::env(SUME_API_BASE) : "https://api.sume.com"}]
set auth [list Authorization "Bearer $::env(SUME_API_KEY)"]
set body {{"model":"gemini-omni-flash-1.1","prompt":"A lighthouse in a storm","duration":3,"resolution":"360p","aspect_ratio":"16:9"}}
set t [http::geturl $base/v1/videos -query $body -headers [concat $auth {Content-Type application/json Idempotency-Key tcl-omni-0001}]]
if {[http::ncode $t] != 202} { puts stderr [http::data $t]; exit 1 }
set poll [dict get [json::json2dict [http::data $t]] polling_url]
while 1 {
    after 30000
    set job [json::json2dict [http::data [http::geturl $poll -headers $auth]]]
    set status [dict get $job status]
    if {$status in {failed cancelled}} { puts stderr $status; exit 1 }
    if {$status eq "completed"} break
}
set r [http::geturl [lindex [dict get $job unsigned_urls] 0] -headers $auth]
foreach {k v} [http::meta $r] { if {[string tolower $k] eq "location"} { set loc $v } }
set r [http::geturl $loc]
set f [open out.mp4 wb]; puts -nonewline $f [http::data $r]; close $f
puts "cost [dict get $job usage cost]"

Why the redirect is handled by hand

Sume's finished job lists unsigned_urls, and the first entry is a /content?index=0 URL on the API that answers 302 with the location of the file. The API request needs your key. The storage address it redirects to does not, and should not receive it, so the script makes the second request with no headers.

Tcl reports header names as the server sent them, so the loop compares location in lower case instead of indexing the dictionary with a fixed capital L. That small loop is the difference between working against one server and working against all of them.

What the job reports

The 202 response carries polling_url, and the script uses that value as given rather than building a path. The poll body has status, one of pending, in_progress, completed, failed or cancelled. When it is completed, usage.cost is the amount Sume billed for the job, which the script prints. Polling every 30 seconds is the cadence Sume's guide suggests.

Request values in the Tcl script, from Sume's video guide (read 2026-10-05)
FieldValueReason
modelgemini-omni-flash-1.1Cheapest Omni setting
duration3Shortest Omni length
resolution360pPriced at $0.0375 a second
Idempotency-Keytcl-omni-0001A resend returns the same job

Before you extend it

The script exits on failed or cancelled and has no retry for 402 or 429, which you should add before running it unattended. For the full catalog of ids, lengths and resolutions, call GET /v1/videos/models and read it before hard-coding a model, as in /blog/video-catalog-input-matrix-python-frame-and-reference-fields.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume