Remove Image Backgrounds in Python with an API (requests, 20 Lines)
You can remove backgrounds in Python with rembg on your own machine, but you will spend the afternoon on model downloads, CPU time and edge cases. If you would rather ship, an API call is 20 lines with nothing but the requests library. This tutorial uses the bgclear API because its request format is the remove.bg one, so the code also documents the migration if you are coming from there.
In this guide
Setup: one key, one library
Create a key on the API dashboard (the first key includes 10 free full-resolution credits; preview-size results are always free), export it, and install requests.
export BGCLEAR_API_KEY=bgc_live_...
pip install requestsSingle image: file in, transparent PNG out
The endpoint returns the image bytes directly, so saving the result is one line.
import os, requests
API = "https://www.bgclear.ai/api/v1/remove"
HEADERS = {"Authorization": f"Bearer {os.environ['BGCLEAR_API_KEY']}"}
def remove_background(path: str, out: str, size: str = "full") -> None:
with open(path, "rb") as f:
r = requests.post(API, headers=HEADERS,
files={"image_file": f},
data={"size": size, "format": "png"},
timeout=60)
if r.status_code != 200:
raise RuntimeError(r.json()["error"]["message"])
with open(out, "wb") as f:
f.write(r.content)
print(f"{out}: charged {r.headers['X-Credits-Charged']}, "
f"remaining {r.headers['X-Credits-Remaining']}")
remove_background("photo.jpg", "photo-no-bg.png")size="preview" (up to 800 px) is free and is the right setting while you test edge quality; full is native resolution and costs one credit; auto picks full for images up to 4 MP. Add "bg_color": "ffffff" to get a white background instead of transparency, or "crop": "true" to trim to the subject's bounding box.
URL input and JSON responses
If the image already lives at a URL (a product feed, S3, a CMS), skip the download and let the API fetch it. Ask for JSON with the Accept header and you get metadata plus a hosted result URL that stays valid for 24 hours — useful when the result goes straight into a database or another service.
r = requests.post(API,
headers={**HEADERS, "Accept": "application/json"},
json={"image_url": "https://example.com/shoe.jpg", "size": "auto",
"format": "webp", "bg_color": "ffffff"},
timeout=60)
data = r.json()
# {'id': '…', 'url': 'https://www.bgclear.ai/api/v1/results/…', 'width': 1600,
# 'height': 1067, 'credits_charged': 1, 'credits_remaining': 9, 'processing_ms': 1840}A folder script with safe retries
Two details turn a loop into something you can run unattended. An Idempotency-Key header makes a retried request return the cached result instead of charging again (for 24 hours), and checking the balance up front avoids a half-processed folder.
import hashlib, pathlib, time, requests
def key_for(path: pathlib.Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def process_folder(src="in", dst="out"):
pathlib.Path(dst).mkdir(exist_ok=True)
for path in sorted(pathlib.Path(src).glob("*.jp*g")):
out = pathlib.Path(dst) / (path.stem + ".png")
if out.exists():
continue
for attempt in range(3):
with path.open("rb") as f:
r = requests.post(API,
headers={**HEADERS, "Idempotency-Key": key_for(path)},
files={"image_file": f}, data={"size": "full"}, timeout=90)
if r.status_code == 200:
out.write_bytes(r.content); break
if r.status_code == 429:
time.sleep(int(r.headers.get("X-RateLimit-Reset", "5"))); continue
if r.status_code == 402:
raise SystemExit("out of credits: https://www.bgclear.ai/api-pricing/")
print(path.name, r.status_code, r.json()["error"]["message"]); break
process_folder()The rate limit is 60 requests a minute per key; the loop above sleeps until the window resets on a 429 rather than hammering.
Images over 4 MP: async jobs
Synchronous requests at full size are capped at 4 megapixels so they finish within 30 seconds. Larger images (up to 50 MP) go through jobs: same inputs, immediate 202, then poll or receive a callback.
JOBS = "https://www.bgclear.ai/api/v1/jobs"
with open("huge-photo.jpg", "rb") as f:
job = requests.post(JOBS, headers=HEADERS, files={"image_file": f},
data={"size": "full"}, timeout=60).json()
while True:
status = requests.get(f"{JOBS}/{job['job_id']}", headers=HEADERS).json()
if status["status"] in ("done", "failed"):
break
time.sleep(2)
if status["status"] == "done":
png = requests.get(status["url"]).content # result URLs need no auth, valid 24 h
open("huge-no-bg.png", "wb").write(png)
else:
print("failed:", status["error"]) # failed jobs are not chargedFor hundreds of images, POST /api/v1/jobs/batch takes up to 50 image_urls per call, and an X-Callback-Url header replaces polling — see the bulk guide.
Errors you will actually see
Every error is JSON with a stable code: invalid_request (400 — wrong or missing parameter, or the URL could not be fetched), unsupported_format (400), image_too_large (400 — over 25 MB or 50 MP), insufficient_credits (402), rate_limited (429), processing_failed (500/504) and gpu_unavailable (503 with Retry-After). Log r.json()["error"]["message"] and the X-Job-Id header; both are what support will ask for. The full list is in the docs.
Frequently asked questions
Is there a Python SDK?
Not needed: the API is plain multipart or JSON over HTTPS, and the requests snippets above are the whole integration. They also work unchanged with httpx.
How is this different from rembg?
rembg runs a model on your machine — free per image, but you manage model downloads, CPU or GPU time and quality tuning. The API returns a result in a few seconds with no ML dependencies, and preview-size results are free for testing.
Can I get a white background instead of transparent?
Yes — pass bg_color=ffffff (any hex works). Combine with format=jpg for the smallest marketplace-ready file.
What does a full-resolution image cost?
One credit. Packs are $9 for 100, $39 for 500 and $129 for 2,000; credits never expire and previews are free.