BACKGROUND REMOVAL API · GUIDE

How to Remove the Background from Images with an API

A developer's guide to background removal APIs: one request in, a transparent PNG out. Working cURL, Python and JavaScript code, plus batches, retries, limits and what to do now that remove.bg's self-serve API is ending.

Updated October 24, 2026 · 15 min read

Removing a background by hand takes a few minutes per photo. Doing it for every product upload, profile picture or thumbnail in your app takes a background removal API: one HTTP request in, a finished cut-out out.

This guide shows how to integrate one properly. You'll get working cURL, Python and JavaScript code, the output options worth knowing, how to handle slow images and bulk jobs, and how to retry without paying twice. The examples use removebackground.dev, our sister product, but the patterns apply to any provider.

How a background removal API works

You send an image, the provider runs a segmentation model that separates the subject from the background, and you get back a new image. The core of the result is an alpha channel: a per-pixel value that says how much of each pixel belongs to the subject. Fully opaque pixels are the subject, fully transparent pixels are background, and the values in between handle soft edges like hair, fur and glass.

From that matte, the API can return several things:

  • A transparent cut-out, usually a PNG or WebP with the background removed
  • The subject on a solid colour, flattened into a JPG for marketplaces that require white backgrounds
  • The mask itself, a grayscale image you can use in your own compositing pipeline
  • A cropped or resized version, trimmed to the subject and fitted to an exact size

The request is the easy part. A production integration also has to deal with images that take longer than usual, large batches, rate limits, retries and keeping the API key away from users.

What to look for in a background removal API

Before you write code, check these points against your own images and traffic.

CriterionWhat to checkWhy it matters
Edge qualityHair, fur, transparent objects and busy backgrounds on 20 to 50 of your own imagesThe difference between providers shows up in the hard cases
Input methodsFile upload, public URL, base64, raw bytesDecides whether you can call it from a queue worker without downloading images first
Output optionsFormat, background colour, crop, padding, exact size, mask outputSaves a second image-processing step on your side
Slow imagesWhat happens when an image takes longer than your HTTP timeoutWithout a job id you may pay twice when you retry
BulkBatch endpoint, batch size, how partial failures are reportedShapes how you process catalogue imports
BillingCharge per success or per request, subscription or credits, whether credits expireFailed images and retries add up at volume
LimitsFile size, resolution, rate limit, result storage timeShapes your queue and storage design
PrivacyHow long uploads and results are kept, whether images train modelsOften a hard requirement from customers

Step 1: Create an API key

Sign up at removebackground.dev and create a key on your account page. New accounts get 50 free credits, which is enough to run the examples below and test on your own images.

Store the key in an environment variable on your server:

export REMOVEBG_API_KEY="your_api_key"

Every call sends the key in the x-api-key header, and every call spends that account's credits. Create a separate key per app or environment so you can revoke one without breaking the others.

Step 2: Remove a background with cURL

The real-time endpoint is POST https://removebackground.dev/v1/remove. The quickest test is a file upload:

curl -X POST https://removebackground.dev/v1/remove \
  -H "x-api-key: $REMOVEBG_API_KEY" \
  -F 'image_file=@photo.jpg' \
  -o photo-no-bg.png

Most single images come back finished in the same response, typically in a few seconds, as a transparent PNG at full resolution.

The endpoint accepts the image in four ways. Send one of them:

InputHow to send it
image_fileMultipart form data. JPG, PNG or WebP, up to 25 MB
image_urlA public http(s) URL. The API downloads it, following up to 3 redirects, with a 15 second timeout
image_file_b64Base64 in a JSON body, with or without a data:image prefix, up to 25 MB after decoding
Raw bodyThe image bytes with Content-Type image/jpeg, image/png, image/webp or application/octet-stream

If a request carries several, image_file wins, then image_file_b64, then image_url, then the raw body. Options can be sent as form fields, JSON keys or query-string values.

One thing to watch: -o saves the body whatever the status. If the key is wrong or the image is invalid, your "PNG" is actually a JSON error. Check the status code before trusting the file.

Step 3: Handle 200, 202 and errors in Python

A background removal API can't promise that every image finishes within one HTTP request. When the queue is unusually busy and an image takes longer than about 55 seconds, removebackground.dev answers 202 Accepted with a job id instead of the image. The job keeps running, and you fetch the result with the id.

StatusBodyWhat to do
200The image (image/png, image/jpeg or image/webp)Save it. Use the Content-Type to pick the file extension
202JSON with id, status, status_url and result_urlWait a couple of seconds, then GET the result. Don't send the image again
4xx or 5xxJSON: {"error": {"code", "message"}}Read the code. Don't save the body as a picture

The key rule: never resend the image after a 202. That starts a second job and can cost a second credit. Here is a complete Python version that sends the image once and only retries the safe calls:

import os
import time
import uuid
import requests

API = "https://removebackground.dev"
KEY = os.environ["REMOVEBG_API_KEY"]  # keep the key on your server
EXT = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}


def save(res, stem):
    """Save an image response with the right extension; raise on anything else."""
    mime = res.headers.get("Content-Type", "").split(";")[0]
    if res.status_code != 200 or mime not in EXT:
        raise RuntimeError(f"HTTP {res.status_code}: {res.text[:500]}")
    path = stem + EXT[mime]
    with open(path, "wb") as f:
        f.write(res.content)
    return path


def remove_background(image_url, stem="result", max_wait=300):
    s = requests.Session()
    s.headers["x-api-key"] = KEY
    # One key per image: a retry with the same key returns the same job, never a second charge.
    idem = {"Idempotency-Key": str(uuid.uuid4())}

    for attempt in range(3):
        try:
            res = s.post(f"{API}/v1/remove", data={"image_url": image_url},
                         headers=idem, timeout=(10, 120))
            break
        except requests.ConnectionError:
            if attempt == 2:
                raise
            time.sleep(2)

    if res.status_code != 202:
        return save(res, stem)  # 200 is the image; anything else raises

    # 202: still processing. Wait for the same job.
    job_id = res.json()["id"]
    deadline = time.monotonic() + max_wait
    while time.monotonic() < deadline:
        time.sleep(2)  # the API sends Retry-After: 2
        res = s.get(f"{API}/v1/jobs/{job_id}/result", timeout=30)
        if res.status_code == 409 and res.json()["error"]["code"] == "not_ready":
            continue
        return save(res, stem)  # the image, or the job's own error
    raise TimeoutError(f"Job {job_id} is still running. Check /v1/jobs/{job_id} later.")


print(remove_background("https://example.com/photo.jpg"))

It needs Python 3 and requests (pip install requests). Two details are easy to miss. First, stopping your script doesn't stop the job: it may still finish and be charged, and the result shows up in your account's Studio. Second, GET /v1/jobs/:id/result answers 409 not_ready while the job is running, so treat that as "wait", not as a failure.

The same flow in JavaScript (Node.js)

The same flow in Node.js 18 or later, using the built-in fetch, FormData and Blob, with a local file upload:

import { readFile, writeFile } from "node:fs/promises"
import { randomUUID } from "node:crypto"

const API = "https://removebackground.dev"
const KEY = process.env.REMOVEBG_API_KEY // server side only
const EXT = { "image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp" }
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

async function save(res, stem) {
  const mime = (res.headers.get("content-type") || "").split(";")[0]
  if (res.status !== 200 || !EXT[mime]) {
    throw new Error("HTTP " + res.status + ": " + (await res.text()).slice(0, 500))
  }
  const path = stem + EXT[mime]
  await writeFile(path, Buffer.from(await res.arrayBuffer()))
  return path
}

export async function removeBackground(filePath, stem = "result", maxWaitMs = 300000) {
  const form = new FormData()
  form.append("image_file", new Blob([await readFile(filePath)]), "upload")
  form.append("format", "png")

  const res = await fetch(API + "/v1/remove", {
    method: "POST",
    // Reuse the same Idempotency-Key if you retry this POST.
    headers: { "x-api-key": KEY, "Idempotency-Key": randomUUID() },
    body: form,
  })
  if (res.status !== 202) return save(res, stem)

  const { id } = await res.json()
  const deadline = Date.now() + maxWaitMs
  while (Date.now() < deadline) {
    await sleep(2000)
    const result = await fetch(API + "/v1/jobs/" + id + "/result", {
      headers: { "x-api-key": KEY },
    })
    if (result.status === 409) {
      const body = await result.json()
      if (body.error && body.error.code === "not_ready") continue
      throw new Error("Job " + id + ": " + body.error.code)
    }
    return save(result, stem)
  }
  throw new Error("Job " + id + " is still running. Check /v1/jobs/" + id + " later.")
}

console.log(await removeBackground("photo.jpg"))

The API accepts CORS requests, which is handy for tools and testing. That doesn't make it safe to call from a web page with your key in it. More on that in the security section below.

Step 4: Control the output

Most apps don't want a raw full-resolution cut-out. They want a square product image on white, a small preview, or a mask. Asking the API for the final file saves you a second processing step. These are the options on /v1/remove:

ParameterValuesWhat it does
formatpng, jpg, webpOutput format. Default png. JPG has no transparency and is flattened onto bg_color, or white
bg_colorHex (ffffff, #fff), rgb(255, 255, 255) or a colour nameFills the background. Default transparent
sizepreview, medium, hd, fullMegapixel cap with aspect ratio kept, never upscaled: preview up to 0.25 MP, medium 1.5 MP, hd 4 MP, full is the original (up to 25 MP)
croptrue, false or a ratio like 1:1, 4:5, 16:9Trims to the subject. With a ratio, the box is expanded to that ratio around the subject
paddingPixels, a fraction or a percentageMargin added around the subject when cropping, up to 500 px
channelsrgba, rgb, alphaTransparent cut-out (default), opaque image, or a grayscale mask with white as the subject
width, height1 to 6000Exact output size in pixels, applied after crop. Never stretched
fitcontain, cover, scale-downHow the image fits when you set both width and height
roix1 y1 x2 y2 in px or %Only the subject inside this region is kept

Every option costs the same 1 credit, and unknown parameters are ignored silently, so a typo won't raise an error. A square marketplace image with the product centred and 10% padding looks like this:

curl -X POST https://removebackground.dev/v1/remove \
  -H "x-api-key: $REMOVEBG_API_KEY" \
  -F 'image_file=@product.jpg' \
  -F 'crop=1:1' -F 'padding=10%' \
  -F 'width=1000' -F 'height=1000' \
  -F 'format=jpg' -F 'bg_color=white' \
  -o product-square.jpg

A successful response also carries useful headers. X-Credits-Remaining shows your balance after the image. X-Matte-Confidence (0 to 1) says how decisive the mask is, and X-Matte-Ambiguous-Ratio gives the share of soft pixels such as hair, glass or motion blur. Logging those two lets you flag low-confidence cut-outs for human review instead of checking every image.

Step 5: Process images in bulk

For catalogue imports and backfills, don't fire hundreds of single requests. POST /v1/batch queues up to 100 public image URLs as one job, with the same options as /v1/remove, and answers 202 right away:

import os
import time
import uuid
import requests

API = "https://removebackground.dev"
s = requests.Session()
s.headers["x-api-key"] = os.environ["REMOVEBG_API_KEY"]

urls = ["https://example.com/front.jpg", "https://example.com/back.jpg"]

job = s.post(f"{API}/v1/batch",
             json={"image_urls": urls, "format": "png", "size": "hd"},
             headers={"Idempotency-Key": str(uuid.uuid4())}, timeout=30)
job.raise_for_status()
job_id = job.json()["id"]  # save it: it's how you resume after a restart

while True:
    time.sleep(3)
    status = s.get(f"{API}/v1/jobs/{job_id}", timeout=30).json()
    if status["done"]:
        break

print(status["status"], "credits charged:", status["credits_charged"])
for item in status["items"]:
    if item["status"] != "succeeded":
        print("failed:", item["input"], item.get("error"))
        continue
    img = s.get(item["result_url"], timeout=60)
    with open(f"out-{item['position']}.{item['format']}", "wb") as f:
        f.write(img.content)

A few rules from the docs to build around:

  • The job's status ends as succeeded, partial (some images failed) or failed. Each item in items has its own status and, on failure, an error code.
  • Entries that aren't http(s) URLs and repeated URLs are dropped and never charged. When either count is above 0, match results by input rather than by position.
  • You can have at most 3 batch jobs open per account. A batch stops counting once done is true.
  • If credits run out mid-batch, finished images stay paid and the rest are marked skipped with out_of_credits, free of charge.
  • The API has no cancel endpoint, webhooks or ZIP download, so polling is the pattern. Reading a job is free and isn't rate limited.

Prefer a script you can run as is? The batch with Python guide walks through a full folder-to-folder workflow.

Step 6: Retry safely and keep billing predictable

Network timeouts are where double charges come from. Your client gives up, retries, and now two jobs exist for one image. The fix is an Idempotency-Key header: any unique string up to 255 characters, such as a UUID, one per image or batch. If you send the same request again with the same key, you get the job the first one created, with the header Idempotent-Replayed: true, and no new charge. Reusing a key for a different image answers 409 idempotency_key_reused.

ResponseWhat to do
Network error, timeout or 500 on POSTResend the same request with the same Idempotency-Key
429 rate_limitedWait the seconds in Retry-After, then resend with the same key
429 too_many_batchesWait until one of your 3 open batches is done
400, 413 or 422Fix the request or the image. Resending it unchanged gives the same answer
401 or 402Fix the key or top up. Don't loop
409 not_ready on a resultWait 2 seconds and ask again. Don't resubmit the image
410 result_unavailableThe stored result was deleted. Processing again is a new job and costs 1 credit
502 on POSTThat job failed and wasn't charged. One retry is a new job

Billing follows a simple rule: 1 credit when an image succeeds, nothing when it fails. Requests rejected before processing, unreadable images and failures after the provider's own retries are free, and downloading a finished result again is free while it is stored.

Limits to design around

LimitValue
Image file25 MB, JPG, PNG or WebP, detected from the file's bytes
Input resolutionImages over 25 MP are scaled down to 25 MP before processing
Output sizeWidth and height each 1 to 6000 px
image_url downloadhttp(s) only, 15 second timeout, up to 3 redirects. Private and loopback addresses are refused
Wait in POST /v1/removeAbout 55 seconds, then 202 with a job id
Batch size100 image URLs per request
Open batches3 per account
Request rate60 requests a minute per account across the POST endpoints. A batch counts once, whatever its size
Result storage48 hours, then deleted

If you need more than 60 images a minute, the batch endpoint is the answer: one request covers up to 100 images. Store results in your own bucket as soon as you download them, because after 48 hours the result links answer 410. According to the provider's docs, uploaded images are not used to train models.

Migrating from the remove.bg API

remove.bg's standalone website and self-serve API end on 1 December 2026, 09:00 CET. Its API moves to Leonardo.Ai, which requires changing your integration. If you'd rather not rewrite anything, removebackground.dev has a remove.bg-compatible endpoint, and the migration is one line:

BeforeAfter
https://api.remove.bg/v1.0/removebghttps://removebackground.dev/v1.0/removebg

You keep the same X-Api-Key header (with a removebackground.dev key), the same parameter names, the same response headers and the same errors format:

curl -H "X-Api-Key: $REMOVEBG_API_KEY" \
  -F 'image_file=@photo.jpg' -F 'size=auto' \
  -F 'crop=true' -F 'crop_margin=10%' -F 'bg_color=ffffff' \
  -o no-bg.jpg https://removebackground.dev/v1.0/removebg

Know the differences before you switch:

  • No 202. The compatible endpoint waits up to 110 seconds, then returns a 503 timeout. The job keeps running and is charged if it succeeds, so blindly retrying that request starts a second job.
  • No idempotency keys on this endpoint. If you need safe retries, move to /v1/remove over time.
  • Some options are accepted but ignored: type and type_level (the subject is detected automatically), semitransparency, and the shadow options (no shadows are added).
  • Size is always 1 credit, including full resolution.

The migration guide has before and after code for cURL, Python, Node and PHP, plus steps for Zapier, Make and n8n.

Keep your API key off the client

This is the mistake that costs real money. If your key is in a web page, a mobile app, a public repository or a URL, anyone can read it and spend your credits.

  • To let your own users remove backgrounds, send their images through your backend and call the API from there.
  • Add your own sign-in and per-user spending limits in that backend.
  • Use one key per app or environment.
  • If a key leaks, revoke it on the account page. It stops working immediately. The provider stores only a hash of each key, so create a new one rather than trying to recover the old one.

Example: cut-outs for YouTube thumbnails

If you build tools for creators, thumbnails are an obvious use. A thumbnail is mostly seen at a small size, so the cut-out has to carry it: one face or product, big, with a clear edge against a simple background. YouTube recommends 3840 x 2160 px at 16:9 with a minimum width of 640 px, and accepts JPG or PNG up to 50 MB from a computer, but only 2 MB from the mobile app.

The API side is one call that returns a transparent PNG trimmed to the subject, ready to drop onto a 16:9 canvas:

curl -X POST https://removebackground.dev/v1/remove \
  -H "x-api-key: $REMOVEBG_API_KEY" \
  -F 'image_file=@me.jpg' \
  -F 'crop=true' -F 'padding=5%' \
  -o me-cutout.png

Shoot a still photo for the cut-out rather than grabbing a video frame, since frames are often motion-blurred, and keep the bottom-right corner clear because YouTube draws the video length there. The YouTube thumbnails page covers the design side, and our guide to getting your first 1,000 subscribers covers why thumbnails matter so much.

Removing backgrounds from ChatGPT, Claude or Cursor

Not every job needs code. removebackground.dev also runs an MCP server at https://removebackground.dev/mcp, so you can upload a photo in ChatGPT, Claude, Cursor, VS Code or n8n and ask for the background to be removed. In Claude Code, it's one command:

claude mcp add --transport http removebackground https://removebackground.dev/mcp

Then type /mcp in Claude Code to sign in. The server exposes three tools: remove_background, get_job for removals that take longer than a few seconds, and check_credits. Each image costs 1 credit, the same as the API.

What it costs

removebackground.dev is pay-as-you-go with no subscription. Credits never expire, failed images are free, and new accounts get 50 free credits. Through the API, 1 credit is one successful background removal at full resolution.

PackCreditsPricePer image
Starter4,000$17$0.0043
Pro15,000$49$0.0033
Scale40,000$99$0.0025

Prices are in USD, and unused credits are refundable within 30 days. Volume pricing above Scale is available on request. See the pricing page for current details.

Wrapping up

A solid background removal integration comes down to a few habits: keep the key on your server, check the status before saving the body, wait for the job instead of resending after a 202, send an Idempotency-Key with every POST, and use the batch endpoint for bulk work.

The full reference, including the OpenAPI 3.1 spec, is in the API docs. If you work with video as well as images, our comparison of YouTube transcript APIs and speech-to-text integration guide use the same production patterns for text.

Frequently asked questions

What is a background removal API?+

It is an HTTP endpoint that takes an image and returns the same image with the background removed, usually as a PNG with a transparent background. You send a file, a URL or base64, and the provider runs the segmentation model for you.

How do I remove an image background in Python?+

Use the requests library to POST the image to the API with your key in a header, then save the response body to a file when the status is 200. If the API can answer 202 for slow images, poll the job's result URL instead of resending the image. The Python example in this guide handles both cases.

How much does a background removal API cost?+

It depends on the provider. removebackground.dev charges 1 credit per successful image, with credit packs from $17 for 4,000 credits ($0.0043 per image) to $99 for 40,000 credits ($0.0025 per image). There is no subscription, credits never expire, failed images are free, and new accounts get 50 free credits.

Can I keep using my remove.bg code after the remove.bg API shuts down?+

remove.bg's self-serve API ends on 1 December 2026, 09:00 CET. removebackground.dev has a remove.bg-compatible endpoint, so you change the URL from https://api.remove.bg/v1.0/removebg to https://removebackground.dev/v1.0/removebg and use a removebackground.dev key in the same X-Api-Key header.

Can I call a background removal API directly from the browser?+

You shouldn't. Anyone can read an API key that ships in a web page or app. Send images through your own backend, keep the key there, and add your own sign-in and spending limits for your users.

How do I get a transparent PNG instead of a white background?+

Request PNG or WebP output and leave the background colour unset. JPG cannot store transparency, so a JPG result is always flattened onto a solid colour.

Keep reading

Start extracting transcripts for free

Convert any YouTube video to text in seconds. No credit card required.

TRY YOUTUBETRANSCRIPT.DEV →
How to Remove Background from Images with an API