Python: chain generate, cutout and upscale with one polling helper
One Python helper submits any Sume job with mode async, polls status until terminal, and returns the artifact URL. Three calls chain image, RMBG and upscale.

To chain image generation, background removal and upscale in Python, write one helper that submits a job with mode: "async", polls status_url until terminal is true, and returns the first artifact URL. Then call it three times and pass each result into the next request. The chain costs the generation price plus $0.0225 plus $0.20.
The helper
The job pattern is the same on every Sume route. A submit returns a job envelope with status_url and result_url. Poll the status, obey next_poll_after_seconds when it is present, and read the result only after the job is completed. A status that is not completed means you read the failure from the job record instead of retrying the paid submit.
import os, time, requests
BASE = "https://api.sume.com"
H = {"Authorization": "Bearer " + os.environ["SUME_API_KEY"]}
def run(path, body):
r = requests.post(BASE + path, headers=H, json={**body, "mode": "async"}, timeout=60)
r.raise_for_status()
env = r.json().get("data", r.json())
while True:
s = requests.get(env["status_url"], headers=H, timeout=60).json()
s = s.get("data", s)
if s.get("terminal"):
break
time.sleep(s.get("next_poll_after_seconds") or 3)
if s.get("sume_status") != "completed":
raise RuntimeError("job did not complete: " + str(s))
res = requests.get(env["result_url"], headers=H, timeout=60).json()
res = res.get("data", res)
return (res.get("result") or res)["artifacts"][0]["url"]
shot = run("/v1/images", {"model": "openai/gpt-image-2.5", "quality": "low",
"prompt": "white ceramic mug on a plain table, studio light"})
cut = run("/v1/rmbg-1.0/remove", {"image_url": shot})
big = run("/v1/image-upscale-1.0/upscale", {"image_url": cut, "upscale_factor": 2})
print(big)What each call costs
The three prices are the ones in the Sume catalog. The generation uses the GPT Image 2.5 low row. The cutout and the upscale are fixed per image. Run the script once on a single product, print usage.cost if your result carries it, and compare with the table.
| Step | Route | Price |
|---|---|---|
| Generate, low quality | POST /v1/images | $0.02475 |
| Remove background | POST /v1/rmbg-1.0/remove | $0.0225 |
| Upscale | POST /v1/image-upscale-1.0/upscale | $0.20 |
| One product through the chain | $0.24725 |
Things to adapt before a batch
The script handles one product. For a batch, keep a dict of SKU to job ids, write it to disk after each submit, and skip any SKU that already has a result. Never loop over a failed SKU with a new paid submit until you have read the first job's error.
Read the first result by hand before you loop. Print the whole result of the first job once and confirm that the artifact list is where the helper expects it. The helper checks both a data wrapper and a result wrapper, but a printed sample is the only proof for your account.
Add an Idempotency-Key header derived from the SKU and step (for example sku-0042-cutout) so that a network retry returns the original job. Sume's error docs say not to retry unsafe submits without one.
Order of operations
The chain above upscales after the cutout. That keeps the cost the same, but it is worth reading the post on order and alpha because upscaling a PNG that has alpha has its own edge cases. The shorter two-call example covers generation and cutout only.
Failure handling worth adding
A 402 insufficient_credits response means the wallet cannot cover the request. Stop the loop instead of retrying, because every later submit will fail the same way. A 429 queue_full means the workspace has too many jobs in flight; wait and resubmit with the same idempotency key. A provider_capacity_exceeded 503 is also safe to retry later with the same key.
The helper above raises on a job that ends in any state other than completed. In a batch, catch that exception per SKU, write the job id and error to your ledger, and continue with the next SKU. A failed image generation is not charged, so a clean retry does not double the cost.
- Cap in-flight jobs at a number below your workspace limit.
- Sleep for the value in
next_poll_after_secondswhen the status carries one. - Log
request_idfrom error bodies, since support asks for it.
Sources
Related posts
More in Developers
- Python match on a Sume /v1/videos poll: five statuses, one handler
Python 3.10 structural matching on the poll dict: wait on pending and in_progress, return on completed, raise on failed or cancelled. Runs under asyncio.run.
- Parse Retry-After as seconds or HTTP-date before retrying a Sume 429
A 21-line Python helper that reads retry-after as integer seconds or an HTTP-date, caps the wait, and falls back to exponential delay when the header is absent.
- Save a Sume job's result artifacts in Python by content type
Fetch GET /v1/jobs/:id/result and save each artifact with an extension from content_type, not the URL. Standard library only, with a text-result guard.
- Python: submit, poll and download one Omni Flash clip (v1/videos)
A short Python script that submits a Gemini Omni Flash 1.1 request to Sume's /v1/videos, polls the job until it completes and saves the MP4.
Written by Sume