Go: net/http client for a 30-second Wan 3.0 clip, 30 lines

A 30-line Go program using only the standard library: submit wan-3.0 for 30 seconds, poll the job, write clip.mp4. Reserve and per-second rate included.

3 min readSume
All posts

A Go client for a 30-second Wan 3.0 clip fits in 30 lines of standard library: net/http for three calls and encoding/json to read id and status. The program below requests 16:9 at 720p, which Sume reserves at $3.75 ($0.125 per second).

main.go

It passes go vet as written. One call helper sets the bearer token and sends the optional idempotency key; it panics on any status of 300 or above so a failed submit is never read as a job.

package main
import ("encoding/json"; "io"; "net/http"; "os"; "strings"; "time")

func call(method, url, payload, idem string) []byte {
	req, _ := http.NewRequest(method, url, strings.NewReader(payload))
	req.Header.Set("Authorization", "Bearer "+os.Getenv("SUME_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	if idem != "" {
		req.Header.Set("Idempotency-Key", idem)
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil || res.StatusCode >= 300 {
		panic("request failed")
	}
	out, _ := io.ReadAll(res.Body)
	return out
}

func main() {
	const base = "https://api.sume.com/v1/videos"
	var job struct{ ID, Status string }
	json.Unmarshal(call("POST", base, `{"model":"wan-3.0","prompt":"A drone-style pass over a harbor at dawn",
		"duration":30,"resolution":"720p","aspect_ratio":"16:9"}`, "wan-go-001"), &job)
	for job.Status == "pending" || job.Status == "in_progress" {
		time.Sleep(5 * time.Second)
		json.Unmarshal(call("GET", base+"/"+job.ID, "", ""), &job)
	}
	if job.Status != "completed" { panic("job ended as " + job.Status) }
	os.WriteFile("clip.mp4", call("GET", base+"/"+job.ID+"/content?index=0", "", ""), 0o644)
}

Design choices

  • struct{ ID, Status string } matches the JSON keys id and status because Go's decoder compares field names without regard to case.
  • Only the POST carries an Idempotency-Key; if the process dies between submit and poll, rerun with the same key and you get the same job back instead of a second reserve.
  • The loop ends on any status except pending and in_progress, then the program checks for completed.

What the same call costs elsewhere in the ladder

Only the resolution string changes the price:

Sume list price, read 2026-10-08
resolution30-second clipClips per $20 balance
480p$1.87510
720p$3.755
1080p$7.502

Running several at once

Goroutines make it easy to fire ten submits together. Sume's plan limits decide how many jobs are accepted at once, so a burst can meet 429 queue_full while the balance is fine. Cap the number of in-flight submits with a buffered channel, give every clip its own Idempotency-Key, and treat a 429 as a reason to wait and retry the same key.

The balance still has to cover the sum of the holds: ten 720p clips need $37.50 available, and the shortfall check happens per submit.

Next step

Put the call behind a worker that records each job id and its usage.cost once the job completes, and set a client timeout suited to your environment. For a batch, give every clip its own idempotency key and submit in small groups so a 429 only delays the tail.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume