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.

6 min readSume
All posts

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.

Net::HTTP timeouts from the Ruby documentation, and the Sume reading, read 2026-10-02.
SettingWhat the Ruby docs sayFor a Sume job
open_timeoutSeconds to wait for a connection to open; initially 60.Set it low (the recipe uses 10); a failed connect is safe to retry.
read_timeoutSeconds to wait for one block to be read.Status calls are short; the recipe uses 30.
NeitherNeither 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.

  • 429 on a status call: sleep for the retry-after header and continue. The recipe does.
  • A failed job: /result answers 409 job_not_completed, so the recipe stops with the status; read GET /v1/jobs/{id} for the public error.
  • Loop exhausted: the job may still be running. Store status_url and resume later, or cancel while cancelable is true.
  • Any other non-200 on status: raise and log the response's x-sume-request-id.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume