remove.bg is closing its standalone site on December 1, 2026, at 9:00 CET. Its API docs say background removal moves to Leonardo.Ai (also part of Canva) from that day, and its pricing page says unused credits expire at the same moment. If a script, a shop plugin or a Zapier step of yours calls api.remove.bg/v1.0/removebg, it needs a new home in the next six weeks.
This post is the practical part: what the options cost, what changes in the call, and the code for one of them, ours (we make the API4AI Background Removal API), next to the remove.bg call you have now.
Three ways out
-
Leonardo.Ai is remove.bg's official successor. It's a new API rather than the same one at a new address: JSON requests with a Bearer key, the image as a URL or base64 instead of a multipart upload, and the result as a link or base64 in JSON. Its migration guide lists what isn't supported, including
format=zipand raw image bytes in the response. Background removal costs $0.1047 an image. -
Photoroom has built its API to be a drop-in: per its own migration post, you change the endpoint URL and the key, and the main parameters (
image_file,crop,bg_color,format,size) carry over. $0.02 an image on its Basic plan. - A different API with a thin adapter. That's the rest of this post. The request changes a little, the response changes more, and an 80-line Python function hides both from the rest of your code.
If you want zero code changes, Photoroom is the honest answer. If you can spend half an hour on an adapter, read on.
What it costs per month
List prices, read on each company's public page on Oct 21, 2026. For remove.bg, the smallest subscription that covers the volume (it stops on Dec 1, so this is what you pay now, not an option).
| Images a month | remove.bg (subscription) | Leonardo.Ai | Photoroom (Basic) | API4AI |
|---|---|---|---|---|
| 1,000 | $189 (1,200-credit plan) | $104.70 | $20 | $5 |
| 10,000 | $989 | $1,047 | $200 | $50 |
| 50,000 | $3,950 | $5,235 | $1,000 | $250 |
Photoroom offers volume discounts on its Enterprise plan from 200,000 images a year, and we give a volume price from about 15,000 images a month (write to hello@api4.ai). We're not the cheapest background removal API on the market, and we don't claim to be. What we offer: pay as you go with no subscription, a balance that never expires, no GPU to run, commercial use of the results (see our Terms of Use), and $5 of free credit when you sign up, enough for 1,000 images.
What changes in the call
| remove.bg | API4AI | |
|---|---|---|
| Endpoint | POST https://api.remove.bg/v1.0/removebg |
POST https://api4ai.cloud/img-bg-removal/v1/results |
| Key |
X-Api-Key header |
X-API-KEY header (header names aren't case-sensitive, so the same line works with the new key) |
| A file | multipart field image_file
|
multipart field image
|
| A link | image_url |
url |
| Result | image bytes in the body | JSON, with the PNG as base64 in results[0].entities[]
|
| A photo it can't process | an HTTP error status |
HTTP 200 with status.code = "failure" and a reason |
| Input size | up to 25 megapixels with size=auto (its docs) |
JPEG or PNG, under 16 MB, up to 4096×4096 |
size, format, crop, bg_color
|
parameters | the result is always a full-size PNG with a transparent background; crop, color and JPG/WebP are a few lines of Pillow (in the adapter below) |
| Your own background image |
bg_image_file, bg_image_url
|
image-bg, url-bg
|
Shadows, type, roi, scale, position, ZIP output |
supported | not available |
The last row matters: if you rely on remove.bg's shadows or its car and product modes, check the result on your own photos before you switch. The demo on our Background Removal page takes any JPEG or PNG without an account.
The same call, before and after
cURL
remove.bg:
curl -H "X-Api-Key: $REMOVEBG_KEY" \
-F "image_file=@car.jpg" \
-F "size=auto" \
-f https://api.remove.bg/v1.0/removebg -o no-bg.png
API4AI (the PNG is inside the JSON, so jq picks it out and base64 decodes it):
curl -s -X POST "https://api4ai.cloud/img-bg-removal/v1/results" \
-H "X-API-KEY: $API4AI_KEY" \
-F "image=@car.jpg" \
| jq -r '.results[0].entities[] | select(.kind == "image") | .image' \
| base64 --decode > no-bg.png
Python
remove.bg:
import requests
r = requests.post(
"https://api.remove.bg/v1.0/removebg",
headers={"X-Api-Key": REMOVEBG_KEY},
files={"image_file": open("car.jpg", "rb")},
data={"size": "auto"},
)
r.raise_for_status()
open("no-bg.png", "wb").write(r.content)
API4AI:
import base64
import requests
r = requests.post(
"https://api4ai.cloud/img-bg-removal/v1/results",
headers={"X-API-KEY": API4AI_KEY},
files={"image": open("car.jpg", "rb")},
)
r.raise_for_status()
result = r.json()["results"][0]
if result["status"]["code"] != "ok": # HTTP 200 can still be a failure
raise RuntimeError(result["status"]["message"])
image = next(e for e in result["entities"] if e["kind"] == "image")
open("no-bg.png", "wb").write(base64.b64decode(image["image"]))
Node (18+)
remove.bg:
import fs from "node:fs";
const form = new FormData();
form.append("size", "auto");
form.append("image_file", await fs.openAsBlob("car.jpg"), "car.jpg");
const res = await fetch("https://api.remove.bg/v1.0/removebg", {
method: "POST",
headers: { "X-Api-Key": process.env.REMOVEBG_KEY },
body: form,
});
if (!res.ok) throw new Error(`${res.status}: ${res.statusText}`);
fs.writeFileSync("no-bg.png", Buffer.from(await res.arrayBuffer()));
API4AI:
import fs from "node:fs";
const form = new FormData();
form.append("image", await fs.openAsBlob("car.jpg"), "car.jpg");
const res = await fetch("https://api4ai.cloud/img-bg-removal/v1/results", {
method: "POST",
headers: { "X-API-KEY": process.env.API4AI_KEY },
body: form,
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
const result = (await res.json()).results[0];
if (result.status.code !== "ok") throw new Error(result.status.message);
const image = result.entities.find((e) => e.kind === "image");
fs.writeFileSync("no-bg.png", Buffer.from(image.image, "base64"));
The one line not to skip
When our service can't process a photo (it's corrupted, isn't really a JPEG or PNG, or is bigger than 4096×4096), it still answers HTTP 200. The JSON says "failure" and gives the reason, for example "Resolution is too big: 6000x4000. Max allowed resolution: 4096x4096."
remove.bg code usually checks only the HTTP status, because that's how remove.bg reports errors. Port that check as it is and a failed photo looks like a success that wrote nothing. Every sample above checks status.code; keep that line when you paste them into your code.
A stand-in for remove_bg()
If remove.bg calls are spread across your code, it's easier to replace the function than every call. This one takes the main remove.bg arguments, returns image bytes like remove.bg's response body, and does crop, bg_color and JPG/WebP output locally with Pillow. Anything it can't do raises an error that names the argument, so nothing is silently dropped.
"""A stand-in for the remove.bg call, on the API4AI Background Removal API.
pip install requests pillow
export API4AI_KEY=... # from portal.api4.ai
Your code keeps calling remove_bg(...) with the main remove.bg arguments and
gets image bytes back. Anything it can't do raises an error with the name of
the argument, instead of being silently ignored.
"""
import base64
import io
import os
import re
import time
import requests
from PIL import Image, ImageColor
URL = "https://api4ai.cloud/img-bg-removal/v1/results"
class RemoveBgError(Exception):
pass
def _call(image_file, image_url, tries=4):
headers = {"X-API-KEY": os.environ["API4AI_KEY"]}
for attempt in range(tries):
if image_file:
with open(image_file, "rb") as f:
r = requests.post(URL, headers=headers, files={"image": f}, timeout=120)
else: # a link goes in as a multipart field too
r = requests.post(URL, headers=headers, files={"url": (None, image_url)}, timeout=120)
if r.status_code in (429, 500, 502, 503, 504) and attempt < tries - 1:
time.sleep(2 ** attempt) # 1, 2, 4 s
continue
if r.status_code != 200:
raise RemoveBgError(f"HTTP {r.status_code}: {r.text[:200]}")
result = r.json()["results"][0]
# A photo the service can't process still comes back as HTTP 200.
if result["status"]["code"] != "ok":
raise RemoveBgError(result["status"]["message"])
image = next(e for e in result["entities"] if e["kind"] == "image")
return Image.open(io.BytesIO(base64.b64decode(image["image"])))
def remove_bg(image_file=None, image_url=None, size="auto", format="png",
bg_color=None, crop=False, **other):
"""Returns the cut-out as bytes, like the body of a remove.bg response."""
if other:
raise RemoveBgError("not supported: " + ", ".join(sorted(other)))
if size not in ("auto", "full"):
raise RemoveBgError(f"size={size!r}: results come back at full size")
if bool(image_file) == bool(image_url):
raise RemoveBgError("pass image_file or image_url")
if format.lower() not in ("png", "jpg", "webp"):
raise RemoveBgError(f"format={format!r}: use png, jpg or webp")
png = _call(image_file, image_url) # RGBA, background transparent
if crop:
png = png.crop(png.getchannel("A").getbbox())
if bg_color or format.lower() == "jpg": # JPG has no transparency
color = bg_color or "ffffff"
if re.fullmatch(r"[0-9a-fA-F]{6}", color):
color = "#" + color
flat = Image.new("RGBA", png.size, ImageColor.getrgb(color))
png = Image.alpha_composite(flat, png)
out = io.BytesIO()
if format.lower() == "jpg":
png.convert("RGB").save(out, "JPEG", quality=92)
else:
png.save(out, format.upper())
return out.getvalue()
if __name__ == "__main__":
import sys
with open("no-bg.png", "wb") as f:
f.write(remove_bg(image_file=sys.argv[1]))
Use it the way you used remove.bg:
from removebg_adapter import remove_bg, RemoveBgError
png = remove_bg(image_file="car.jpg", size="auto")
jpg = remove_bg(image_url="https://example.com/shoe.jpg", bg_color="ffffff", crop=True, format="jpg")
Three things it decides for you, which you may want to change:
-
sizeacceptsautoandfullonly. Our results come back at the size of the input, so the smaller, cheaper remove.bg sizes have no match; the function says so rather than returning a full-size image you didn't ask for. -
JPG without
bg_colorgets a white background, because JPG can't be transparent. - Retries: a 429 or a 5xx waits 1, 2, then 4 seconds. If you call it from many threads at once, keep an eye on 429s.
No-code: Zapier, Make and n8n
If a no-code flow has a remove.bg step, you don't need a new app or template to replace it. Swap that step for the tool's plain HTTP request (Webhooks by Zapier, Make's HTTP module, n8n's HTTP Request node): POST to https://api4ai.cloud/img-bg-removal/v1/results, an X-API-KEY header with your key, and a multipart form field url with a public link to the photo (or image with the file). The cut-out comes back as base64 in results[0].entities[0].image. In n8n, the Convert to File node turns it into a file; in Make, the toBinary() function does; in Zapier you'll need a short Code step.
Before Dec 1
- Put five of your hardest photos through the demo: glass, hair, white on white.
- Get a key on the API4AI developer portal. No credit card, $5 of free credit, pay as you go after that at $5 per 1,000 images, and the balance never expires.
- Swap the call or drop in the adapter, and run a day's photos through both before your remove.bg credits run out.
The full reference is in the docs, and the price table with the sources is on our moving from remove.bg page.
Which remove.bg parameter would you miss most if it went away?
Top comments (0)