Most background removal APIs return a finished cutout, a transparent PNG with the background gone. But many workflows need the mask instead: a grayscale image that tells you which pixels belong to the subject. This guide explains what a mask is and when it is the better choice.
Mask vs cutout
| Transparent cutout | Mask | |
|---|---|---|
| What you get | The subject's pixels with a transparent background | A black-and-white image: white = subject, black = background |
| Original pixels | Included | Not included. You apply the mask to your own image |
| Best for | Ready-to-use assets | Editing, compositing, AI pipelines, custom effects |
Gray values in the mask are partial transparency. They appear along soft edges like hair and fur, and they are what make a cutout look natural instead of jagged.
Getting a mask
Send the image to /v2/rembg/mask/. Like the main endpoint, it accepts an optional model (falcon, aurora, or ghost).
curl -X POST https://fapihub.com/v2/rembg/mask/ \
-H "ApiKey: YOUR_API_KEY" \
-F "image=@photo.jpg" \
--output mask.pngRequesting a mask from Python or Node.js
The response body is the mask image itself, so save the bytes directly. Because masks are what everything downstream depends on, it is worth comparing models on a sample of your own images. Try each model and keep the one that looks best for your content.
import requests
for model in ("falcon", "aurora", "ghost"):
with open("photo.jpg", "rb") as image_file:
response = requests.post(
"https://fapihub.com/v2/rembg/mask/",
headers={"ApiKey": "YOUR_API_KEY"},
files={"image": image_file},
data={"model": model},
timeout=60,
)
response.raise_for_status()
with open(f"mask-{model}.png", "wb") as out:
out.write(response.content)
import { readFile, writeFile } from "node:fs/promises";
const form = new FormData();
form.append("image", new Blob([await readFile("photo.jpg")]), "photo.jpg");
form.append("model", "falcon");
const response = await fetch("https://fapihub.com/v2/rembg/mask/", {
method: "POST",
headers: { ApiKey: process.env.FAPIHUB_API_KEY },
body: form,
});
if (!response.ok) throw new Error(JSON.stringify(await response.json()));
await writeFile("mask.png", Buffer.from(await response.arrayBuffer()));
Comparing three models on one image costs three requests, so run the comparison on a small, representative set rather than your whole library.
Applying the mask yourself
With Pillow you can turn the original image plus the mask into a cutout at full original quality:
from PIL import Image
original = Image.open("photo.jpg").convert("RGB")
mask = Image.open("mask.png").convert("L")
if mask.size != original.size:
mask = mask.resize(original.size, Image.LANCZOS)
original.putalpha(mask)
original.save("cutout.png")
Things you can do with a mask
Edit only the background
Invert the mask to select the background, then desaturate it, darken it, or replace it, while the subject stays untouched. This is how "color splash" effects are made.
from PIL import Image, ImageOps
gray_background = ImageOps.grayscale(original.convert("RGB")).convert("RGB")
color_splash = Image.composite(original.convert("RGB"), gray_background, mask)
color_splash.save("color-splash.jpg")
Edit only the subject
Use the mask as-is to brighten, sharpen, or color-correct the person or product without changing the scene.
Feed AI and design tools
Inpainting models, generative fill, and many design tools accept a mask to define the area to change. A precise subject mask gives them a clean boundary.
Measure the subject
The mask's bounding box tells you exactly where the subject is. Use it for auto-cropping, centering, or checking that a product fills enough of the frame.
Store masks to save requests
Once you have a mask, you can create any number of variations locally without calling the API again: different backgrounds, blurs, crops, or effects. For catalogs that get redesigned often, saving the mask next to the original is a cheap way to keep options open. See our quality guide for tips on checking mask edges.
Refining a mask
The mask you get back is a starting point. A few standard operations cover most of what downstream tools need.
Hard edges (binary mask)
Some tools, such as certain inpainting pipelines or vector tracing, expect pure black and white with no gray. Threshold the mask. A value around the middle keeps the subject's shape; move it up to tighten the selection or down to loosen it.
from PIL import Image
mask = Image.open("mask.png").convert("L")
binary = mask.point(lambda v: 255 if v >= 128 else 0)
binary.save("mask-binary.png")
Grow, shrink, and feather
Growing (dilating) the mask by a few pixels is useful before inpainting, so the model also repaints the soft transition zone. Shrinking (eroding) removes a thin outline of leftover background. Feathering softens the edge for smoother composites.
from PIL import Image, ImageFilter
mask = Image.open("mask.png").convert("L")
grown = mask.filter(ImageFilter.MaxFilter(9)) # roughly 4 px outward
shrunk = mask.filter(ImageFilter.MinFilter(5)) # roughly 2 px inward
feathered = mask.filter(ImageFilter.GaussianBlur(2))
Filter sizes must be odd numbers. Scale them with image resolution, the same way you would scale any pixel-based setting.
Inverting for inpainting
Inpainting and generative fill tools usually treat white as "the area to change." If you want to regenerate the background and keep the subject, invert the mask first with ImageOps.invert(mask). If you want to change the subject, use it as-is. Check your tool's documentation, because conventions differ.
Auto-cropping with the mask
The bounding box idea from above in code. Thresholding first ignores faint gray noise so the box hugs the real subject, and a margin keeps the result from feeling cramped.
from PIL import Image
photo = Image.open("photo.jpg")
mask = Image.open("mask.png").convert("L").resize(photo.size)
box = mask.point(lambda v: 255 if v > 20 else 0).getbbox() # (left, top, right, bottom) or None
if box is None:
raise ValueError("No subject found in mask")
left, top, right, bottom = box
pad = round(0.05 * max(right - left, bottom - top))
crop = photo.crop((
max(0, left - pad), max(0, top - pad),
min(photo.width, right + pad), min(photo.height, bottom + pad),
))
crop.save("photo-cropped.jpg", quality=92)
coverage = sum(1 for v in mask.getdata() if v > 127) / (mask.width * mask.height)
print(f"Subject covers {coverage:.0%} of the frame")
The coverage number is a simple quality gate for catalogs: flag images where the product is tiny, or where the mask is nearly empty because detection failed. This is a common step in e-commerce image pipelines.
Storing masks correctly
- Save as 8-bit grayscale PNG. Convert to mode
Lbefore saving. Grayscale PNGs are small and lossless. - Never store masks as JPEG. Compression artifacts shift gray values along edges, which shows up as halos when you apply the mask later.
- Keep the original dimensions. If your pipeline resizes the photo, store the mask at the photo's original size and resize both together.
- Record the model. Store which model produced each mask, so you can reprocess a subset if you switch later.
Mask endpoint pricing and errors
The mask endpoint is on every plan and each call is one request. Since one mask can produce unlimited local variants, it is often the cheapest option for workflows that need several outputs per image. For example, 3,000 product photos that each need a cutout, a white-background version, and a cropped thumbnail cost 3,000 requests with masks, which fits in the Pro plan's 7,500 monthly requests at $15, versus 9,000 requests if you called a separate endpoint for each output. If a request fails, the JSON error code tells you what to do: invalid_image (400) means fix the input, insufficient_credits (402) means the quota is used up, and rate_limit_exceeded (429) means wait for the Retry-After seconds. Server errors (5xx) are not counted against your quota. The production best practices guide covers retry logic in detail.
Frequently asked questions
Is the mask the same size as my input image?
Always check rather than assume. The Pillow examples above compare sizes and resize the mask to match the original before applying it, which handles either case safely.
Why does my mask have gray pixels?
Gray values represent partial transparency along soft edges such as hair, fur, and motion blur. Keep them for natural composites, or threshold the mask if your tool needs pure black and white.
Which model should I use for masks?
It depends on your images. The mask endpoint accepts falcon (the default), aurora, and ghost. Run a sample of your own photos through each and keep the one that looks best.
Can I get a mask and a cutout from one request?
Each endpoint returns one image. Request the mask, then build the cutout locally with putalpha as shown above. That gives you both for the cost of one request.