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.

5 min readSume
All posts

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.

Cost of one pass through the chain, as of 2026-10-08
StepRoutePrice
Generate, low qualityPOST /v1/images$0.02475
Remove backgroundPOST /v1/rmbg-1.0/remove$0.0225
UpscalePOST /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_seconds when the status carries one.
  • Log request_id from error bodies, since support asks for it.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume