Ruby Net::HTTP: submit and poll a Sume job with one key
A Ruby recipe using only Net::HTTP: submit a Sume image job with an Idempotency-Key, set open and read timeouts, poll status_url, and return the artifact URLs.

Ruby needs no gem to call Sume: Net::HTTP from the standard library can submit a job with mode: "async" and an Idempotency-Key, then poll status_url until terminal is true. Set open_timeout and read_timeout so a stuck socket does not hang the poller, and treat a timeout as a reason to poll again, never as a reason to submit a second paid job.
The timeout semantics are from the Ruby docs for Net::HTTP; the job contract is from Jobs and results and the API reference. All read 2026-10-02.
What does the Ruby recipe look like?
Set SUME_API_KEY and run ruby poll.rb. One helper builds every request, so the Authorization header is attached in one place. The submit expects the async 202; the recipe raises on anything else, including a 200 that carries a finished image inline.
require "net/http"
require "json"
BASE = "https://api.sume.com"
def call(klass, url, body = nil, headers = {})
uri = URI(url)
req = klass.new(uri, headers.merge("Authorization" => "Bearer #{ENV.fetch("SUME_API_KEY")}"))
req.body = body
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 10, read_timeout: 30) { |h| h.request(req) }
end
def generate(prompt, idem_key)
body = { model: "sume/auto", prompt: prompt, mode: "async" }.to_json
res = call(Net::HTTP::Post, "#{BASE}/v1/images", body, "Content-Type" => "application/json", "Idempotency-Key" => idem_key)
raise "submit #{res.code}: #{res.body}" unless res.code == "202"
job = JSON.parse(res.body)["data"]
delay = job["next_poll_after_seconds"] || 2
st = nil
200.times do
sleep delay
s = call(Net::HTTP::Get, job["status_url"])
if s.code == "429" then delay = (s["retry-after"] || delay * 2).to_f; next end
raise "status #{s.code}" unless s.code == "200"
st = JSON.parse(s.body)["data"]
break if st["terminal"]
delay = st["next_poll_after_seconds"] || [delay * 2, 30].min
end
raise "not completed: #{st && st["sume_status"]}" unless st && st["sume_status"] == "completed"
r = JSON.parse(call(Net::HTTP::Get, job["result_url"]).body)
r.dig("data", "result", "artifacts").map { |a| a["url"] }
end
puts generate("a red panda astronaut", "panda-order-8823-v1")Which Net::HTTP timeouts matter?
The Ruby docs describe two settings the recipe passes to Net::HTTP.start.
| Setting | What the Ruby docs say | For a Sume job |
|---|---|---|
open_timeout | Seconds to wait for a connection to open; initially 60. | Set it low (the recipe uses 10); a failed connect is safe to retry. |
read_timeout | Seconds to wait for one block to be read. | Status calls are short; the recipe uses 30. |
| Neither | Neither is a job deadline. | The deadline is the loop bound; the job outlives your timeouts. |
How does the key protect a retried submit?
If Net::HTTP raises a timeout on the POST, you do not know whether Sume accepted the job. Call generate again with the same Idempotency-Key: the docs say the retry returns the original job instead of billing a second one. The key must come from your business intent, such as an order id plus a revision, and must be reused only for the same operation and payload; a different payload under the same key returns 409 idempotency_conflict.
What should the loop do on errors?
The recipe handles errors this way, and you can extend it from here.
429on a status call: sleep for theretry-afterheader and continue. The recipe does.- A failed job:
/resultanswers409 job_not_completed, so the recipe stops with the status; readGET /v1/jobs/{id}for the public error. - Loop exhausted: the job may still be running. Store
status_urland resume later, or cancel whilecancelableis true. - Any other non-200 on status: raise and log the response's
x-sume-request-id.
Sources
Related posts
More in Developers
- Feed scraped product copy to a Format run: input, not instruction
Putting a supplier's text into a Format's instruction lets it steer the run. Sume's input field is treated as data, with a 64-key and 2 MiB limit.
- script_run budgets: call, paid-call and timeout limits on MCP
script_run caps a Sume MCP program by timeout (5-55 s), max_calls and max_paid_calls, and stops with a named error code. Defaults, ceilings and what to do next.
- Seedance 2.5 first frame plus references in one request: 400
On Sume's Video Router, image_url plus reference_*_urls returns 400 for seedance-2.5. Pick image-to-video or reference-to-video; /v1/videos lets frames win.
- Seedance 2.5 references: input_references or reference_image_urls?
POST /v1/videos takes frame_images and input_references for seedance-2.5; the Video Router takes image_url and reference_*_urls. The wrong shape gets 400.
Written by Sume