The Catalog Image Assembly Line: Cutouts, Cleanup, and 4x Upscaling with One API
A catalog image rarely needs just one operation.
The original photo may have a busy background, a price tag in the corner, and barely enough resolution for a marketplace zoom. The finished asset needs a clean cutout, no distractions, exact dimensions, and a larger file for the product page.
You can wire those steps together with separate services. Or you can treat the workflow as an assembly line: submit a job, keep its ID, poll until it reaches a terminal state, validate the result, and only then pass it to the next stage.
That second part—the unglamorous job handling—is what makes the pipeline dependable.
The workflow
A practical image pipeline usually looks like this:
- Remove the background with
POST /v1/image/remove-background. - Remove a remaining object with
POST /v1/image/remove-objectwhen needed. - Upscale with
POST /v1/image/upscale, or request exact output dimensions. - Check the output before replacing the source asset in your catalog.
PixelAPI exposes these operations behind one API key and one credit balance. The background-removal endpoint accepts either a multipart upload or an image_url. The object-removal endpoint accepts an image plus a text prompt or a mask. The upscale endpoint accepts a file or URL, a scale from 2 to 16, and optional target dimensions.
Keep the original file. Automated edits are not a reason to make your only master asset disposable.
Start with job handling
Here is a small Python client for the common asynchronous pattern. It handles immediate output URLs as well as queued jobs, checks terminal states, and applies a timeout.
import os
import time
import requests
BASE_URL = "https://api.pixelapi.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['PIXELAPI_KEY']}"}
TERMINAL_FAILURES = {"failed", "cancelled", "blocked"}
def wait_for_image(generation_id: str, timeout_seconds: int = 240) -> str:
deadline = time.monotonic() + timeout_seconds
while time.monotonic() < deadline:
response = requests.get(
f"{BASE_URL}/v1/image/{generation_id}",
headers=HEADERS,
timeout=30,
)
response.raise_for_status()
job = response.json()
status = job.get("status")
if status in {"completed", "done"} and job.get("output_url"):
return job["output_url"]
if status in TERMINAL_FAILURES:
raise RuntimeError(
job.get("error_message")
or job.get("friendly_message")
or f"Image job ended with status={status}"
)
time.sleep(2)
raise TimeoutError(f"Image job {generation_id} did not finish in time")
def output_or_wait(payload: dict) -> str:
if payload.get("output_url"):
return payload["output_url"]
return wait_for_image(payload["generation_id"])
def remove_background(image_path: str) -> str:
with open(image_path, "rb") as image_file:
response = requests.post(
f"{BASE_URL}/v1/image/remove-background",
headers=HEADERS,
files={"image": (os.path.basename(image_path), image_file)},
timeout=60,
)
response.raise_for_status()
return output_or_wait(response.json())
def upscale_url(image_url: str, scale: int = 4) -> str:
response = requests.post(
f"{BASE_URL}/v1/image/upscale",
headers=HEADERS,
data={"image_url": image_url, "scale": scale},
timeout=30,
)
response.raise_for_status()
return output_or_wait(response.json())
Use this as a starting point, not as a complete queueing system. A production worker should also persist the generation ID before retrying a request, cap retries, and record the operation and source asset alongside the result.
Text-to-image uses the same discipline
For generated assets, POST /v1/image/generate accepts a JSON body with prompt, model, width, and height. The currently documented default model is flux-schnell.
response = requests.post(
f"{BASE_URL}/v1/image/generate",
headers=HEADERS,
json={
"prompt": "A ceramic coffee mug on a pale stone surface, soft studio light",
"model": "flux-schnell",
"width": 1024,
"height": 1024,
},
timeout=30,
)
response.raise_for_status()
image_url = output_or_wait(response.json())
Do not generate a fresh asset on every application retry without first checking whether you already have a generation ID. Otherwise a network timeout can turn one logical request into several billable jobs.
For operations that support webhooks on your plan, a signed callback is usually cleaner than polling. Polling is still useful as a recovery path when a callback is delayed.
Exact dimensions need an explicit policy
The upscale route supports target_width, target_height, and fit_mode. This matters because "make it 1200 × 1200" is ambiguous when the input is rectangular.
You have three choices:
-
stretch: exact dimensions, but the image may distort. -
pad_white: preserve aspect ratio and add a white border. -
pad_transparent: preserve aspect ratio and add transparent padding.
For most marketplace assets, padding is safer than stretching. For print crops, you may want a separate crop-and-compose step instead. Whatever you choose, encode it in the job record so the same source does not produce different layouts across retries.
Add a small quality gate
Before publishing an output automatically, check things your application can verify:
- The URL downloads successfully.
- The content type and file extension are expected.
- The image decodes.
- The dimensions match your marketplace rules.
- The file is not empty or implausibly small.
- A transparent cutout actually contains an alpha channel.
Visual checks still matter for object removal and generative edits. A decoded image can be perfectly valid and still contain a bad fill or altered product detail. Keep a review queue for high-value listings rather than treating an HTTP 200 as proof of quality.
Cost should be visible per operation
A single API balance makes accounting easier, but your own job records should still store the operation, credits used, and output asset ID. That lets you answer useful questions later: Which marketplace workflow costs the most? Which step is retried most often? Which supplier photos need the most cleanup?
As published on November 10, 2026, PixelAPI's Starter examples include 1.2 credits for FLUX.1 Schnell generation, 9 credits for a 4x upscale, 12 credits for object removal, and 18 credits for background removal. Pricing can change by route, plan, and options, so use the live pricing endpoint rather than hard-coding those numbers into a cost forecast.
For comparison, Replicate's official pricing page currently lists FLUX.1 Schnell at $3 per thousand output images. PixelAPI lists its Starter example at $0.0012 per image. That is a like-for-like published price check for this one generation route—not a claim that every editing operation has the same price relationship.
The useful abstraction is the pipeline, not the API call
The individual requests are straightforward. The reliability comes from everything around them: stable asset IDs, saved generation IDs, bounded retries, explicit dimension rules, output validation, and a review path for uncertain edits.
Once those pieces are in place, adding another operation is mostly a routing decision. Your catalog system sees one assembly line, while the underlying tools can evolve independently.
PixelAPI's API documentation has the current request fields and endpoint list, and the pricing page explains plan limits and retention.
Top comments (0)