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
- Ruby Thread and Queue: four workers submit Wan 3.0 clips
Ruby worker pool for POST /v1/videos: a Queue of shots, four Threads, Net::HTTP and one Idempotency-Key per shot. Tested on Ruby with a stub server.
- Rust reqwest: POST /v1/images on Sume and branch on 200 or 202
Call Sume's image API from Rust with reqwest::blocking and serde_json; one 40-second client timeout and a match on 200, 202 and errors.
- Retry a timed-out video submit without paying twice: Idempotency-Key
Your client timed out after submitting a 30-second video job? Retry with the same Idempotency-Key and Sume returns the original job, not a second bill.
- Save a finished Seedance clip to your own storage, not the result URL
When a Seedance 2.5 or Wan 3.0 job completes, read /v1/jobs/{id}/result, take the artifact url and content_type, and copy the file into your own storage.
Written by Sume