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.

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
- Which Sume audio calls can sync-wait 30 s? A map vs 150 ms claims
Detach, timeline audio, ingest, music and TTS: which can return in a 30 s sync wait, which are async jobs, and what a 150 ms end-to-end claim leaves out.
- Talking Photo From 3 Minutes of Audio: Fabric Cost and File Size
Turn a still and a 180-second recording into one talking-photo video: Sume Fabric cost at 720p and 480p, the 10 MB audio cap, and WAV vs MP3 size.
- Tcl http package: submit a Gemini Omni video job and follow the 302
A 22-line Tcl script using the http, json and tls packages submits a 3 s 360p Gemini Omni job on Sume ($0.1125), polls it and follows the content redirect.
- Test a Sume webhook receiver: stale timestamp and rotated secret
Two tests every receiver needs: a delivery older than 300 seconds must be refused, and a header with two sume-v1 entries must verify when either secret matches.
Written by Sume