Swift URLSession: call Sume's image API and branch on 200 or 202

Use URLSession.data(for:) with async/await, cast the response to HTTPURLResponse, and read statusCode before you touch data[0].url on Sume's /v1/images.

4 min readSume
All posts

Call URLSession.shared.data(for:) and cast the URLResponse to HTTPURLResponse to read statusCode. Apple's URLSession documentation describes the async data(for:) method, which returns the data and the response together. For Sume's image API you need the status code before the body, because the same endpoint answers 200 with an image and 202 with a job.

The wait is up to 30 seconds. A 200 has data[].url. A 202 has the job envelope with status_url and result_url. A 502 is a terminal failure with an error envelope.

A script you can run

Save this as main.swift and build it with swiftc main.swift; top-level await works in that file. It exits on an empty key, prints the body on any status other than 200, and writes out.png.

import Foundation

guard let key = ProcessInfo.processInfo.environment["SUME_API_KEY"], !key.isEmpty else {
  fatalError("SUME_API_KEY is empty")
}
var req = URLRequest(url: URL(string: "https://api.sume.com/v1/images")!)
req.httpMethod = "POST"
req.timeoutInterval = 60
req.setValue("Bearer \(key)", forHTTPHeaderField: "Authorization")
req.setValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpBody = try JSONSerialization.data(withJSONObject: [
  "model": "openai/gpt-image-2.5", "prompt": "a red kettle on a white table",
  "quality": "low", "output_format": "png",
])
let (data, response) = try await URLSession.shared.data(for: req)
guard let http = response as? HTTPURLResponse else { fatalError("no response") }
guard http.statusCode == 200 else {
  print("status \(http.statusCode): \(String(decoding: data, as: UTF8.self))")
  exit(1)
}
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
let first = (json["data"] as! [[String: Any]])[0]
let (png, _) = try await URLSession.shared.data(from: URL(string: first["url"] as! String)!)
try png.write(to: URL(fileURLWithPath: "out.png"))
print("saved out.png")

Why set the timeout yourself?

Set timeoutInterval above the 30-second wait so your own limit cannot fire first and make a working job look like a failure. If you do time out locally, do not send the create call again: the first job keeps running and billing, and the jobs guide tells you to poll the job by id instead.

For an iOS app, keep the key on your server and call Sume from there. A key inside an app binary can be extracted, and every image call bills the workspace that owns the key. The usage.cost field on a 200 is the amount in USD, which your server can log per user.

A last Swift detail: JSONSerialization is used above so the sample needs no model types, but for production code, define a Codable struct for the request and one for the data array. Decode the 202 envelope with its own struct, so a missing data array becomes a typed case in your code and not a crash on a force-cast. Log the status code and the job id from every 202, so a support request can quote them.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume