Ruby Net::HTTP: submit a Wan 3.0 video job and poll until it finishes
A standard-library Ruby script that posts to /v1/videos with wan-3.0, polls the polling_url every 30 seconds and stops on completed, failed or cancelled.

In Ruby, POST to https://api.sume.com/v1/videos with model: "wan-3.0", read polling_url from the 202 response, and poll it until status is completed, failed or cancelled. The standard library is enough: net/http, json and uri, with no gem.
The script below prints the job id, the status on each poll, and the first download URL when the job completes.
The script
It reads the key from SUME_API_KEY, sends a 6-second 720p request and sleeps 30 seconds between polls, the interval the Sume docs example uses. Net::HTTP.get with a headers hash needs Ruby 3.0 or newer.
require "net/http"
require "json"
require "uri"
key = ENV.fetch("SUME_API_KEY")
headers = { "Authorization" => "Bearer #{key}", "Content-Type" => "application/json" }
uri = URI("https://api.sume.com/v1/videos")
body = { model: "wan-3.0", prompt: "A paper boat drifting down a rainy street, soft light",
duration: 6, resolution: "720p" }.to_json
res = Net::HTTP.post(uri, body, headers)
raise "submit failed: #{res.code} #{res.body}" unless res.code == "202"
job = JSON.parse(res.body)
puts "job #{job["id"]}"
loop do
sleep 30
st = JSON.parse(Net::HTTP.get(URI(job["polling_url"]), headers))
puts st["status"]
if st["status"] == "completed"
puts st["unsigned_urls"].first
break
end
abort("stopped: #{st["status"]} #{st["error"]}") if %w[failed cancelled].include?(st["status"])
endStatuses to handle
The poll returns pending, in_progress, completed, failed or cancelled (video generation docs). Treat the last two as terminal. On failed, the error field carries a public message, for example when an input media URL could not be downloaded.
| Status | Keep polling? | What to do |
|---|---|---|
| pending | Yes | Job is queued |
| in_progress | Yes | Generation is running |
| completed | No | Download from unsigned_urls[0] |
| failed | No | Read error; fix the input and submit again |
| cancelled | No | The job did not complete |
Download
The content endpoint, GET /v1/videos/{job_id}/content?index=0, needs your Bearer key and redirects to the artifact URL, so use a client that follows redirects and send the key only to api.sume.com. A 409 job_not_completed means keep polling, and a 409 job_failed means stop.
Cost
Wan 3.0 at 720p is $0.125 per second on Sume, so this 6-second script spends $0.75 (read 2026-10-07, pricing basis). The poll response reports the billed amount in usage.cost. For a webhook instead of polling, pass an HTTPS callback_url.
Practical notes
Two small hardening steps for production: add a deadline so the loop gives up after, say, 20 minutes, and wrap the submit in a retry that reuses the same Idempotency-Key header so a network error does not create two billed jobs.
Rails users can move this into a background job. Submit in one job, store the id, and either schedule a poll job or accept a callback_url webhook, so no worker sits asleep for minutes.
Sources
Related posts
More in Developers
- Scan your repo for retiring Google image and TTS ids (Python)
A 30-line Python scanner that finds Imagen 4, Nano Banana and Gemini TTS preview ids in your files and prints the replacement Google names for each one.
- SHA-256 the batch body into the Sume idempotency key for bulk chunks
Derive each bulk chunk's Idempotency-Key from a hash of its items so replays reuse the queue and edited rows get a new key instead of a 409 conflict.
- Turn a shot list into Gemini Omni requests: Python on Sume
Submit every line of a shot list as its own Omni job with stable Idempotency-Keys, then poll them in one pass. Runnable Python and the cost of a 3-shot list.
- Should my backend call Sume over hosted MCP or the REST API?
REST from a backend, hosted MCP from an agent client. Where they differ: auth, wait limits, REST-only Image 1.0 and Video 1.0, and write budgets.
Written by Sume