Ruby Net::HTTP: POST to Sume images and save the file

Net::HTTP.start with use_ssl and read_timeout, one POST to /v1/images, a string comparison on res.code, and File.binwrite for the image. No gems needed.

4 min readSume
All posts

Open one session with Net::HTTP.start, post the JSON, and compare res.code as a string. Ruby's Net::HTTP reference shows Net::HTTP.start(hostname, use_ssl: true, read_timeout: 60) for HTTPS sessions and shows the response code as a string such as "200", with the content in body. That string detail is the usual bug: res.code == 200 is always false.

The call is the Sume image API: POST /v1/images, a bearer key, and a JSON body. A 200 has data[].url; a 202 has a job envelope instead.

The whole script

Run it with SUME_API_KEY=... ruby save.rb. The standard library covers everything, so no gem is needed. The read_timeout of 60 is above the 30-second wait that Sume applies to this endpoint.

require "net/http"
require "json"
require "uri"

key = ENV["SUME_API_KEY"].to_s
abort "SUME_API_KEY is empty" if key.empty?
body = { model: "openai/gpt-image-2.5", prompt: "a red kettle on a white table",
         quality: "low", output_format: "png" }.to_json
headers = { "Authorization" => "Bearer #{key}", "Content-Type" => "application/json" }
res = Net::HTTP.start("api.sume.com", use_ssl: true, read_timeout: 60) do |http|
  http.post("/v1/images", body, headers)
end
abort "status #{res.code}: #{res.body}" unless res.code == "200"
url = URI(JSON.parse(res.body)["data"][0]["url"])
File.binwrite("out.png", Net::HTTP.get(url))
puts "saved out.png"

Branch on 202 before parsing

The script aborts on anything other than 200. In a real worker, treat "202" as accepted: parse data.status_url, poll it with a growing delay until the status is completed, failed or canceled, then read the result_url. The jobs guide says not to resend the paid request when your own wait ends.

A 502 is a terminal failure of a synchronous request. Its error envelope names a code and a next_action, so print the body as the script does. Requesting a parameter that the model's catalog does not list returns 400 unsupported_parameter, which is also worth surfacing instead of retrying.

Billing is per image. The usage.cost in a 200 body is the USD amount, so JSON.parse(res.body)["usage"]["cost"] is the field to log next to the file name.

For several images in one call, send n and loop over data: the request takes 1 to 10, with a lower ceiling on some models, so check the catalog at GET /v1/images/models first. Name the files from the index, and write each with File.binwrite so PNG bytes are not altered by newline translation on Windows. Add a short sleep and a retry cap if you loop over many prompts, because each call is a paid generation and a bug in a loop is expensive.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume