After the background is removed, products on a plain background can look like they are floating. A soft drop shadow fixes that. It gives the product weight and makes the image look like a studio shot instead of a cutout. FAPIhub's shadow endpoint removes the background and adds the shadow in one request.

The request

The /v2/rembg/shadow/ endpoint takes three settings besides the image:

  • shadow_color: RGBA color of the shadow, for example (0, 0, 0, 150). The last value is opacity from 0 to 255.
  • offset: how far the shadow moves in pixels, as (x, y).
  • blur_radius: how soft the shadow edge is.
curl -X POST https://fapihub.com/v2/rembg/shadow/ \
  -H "ApiKey: YOUR_API_KEY" \
  -F "image=@sneaker.jpg" \
  -F "shadow_color=(0, 0, 0, 150)" \
  -F "offset=(10, 10)" \
  -F "blur_radius=15" \
  --output sneaker-shadow.png
import requests

with open("sneaker.jpg", "rb") as image_file:
    response = requests.post(
        "https://fapihub.com/v2/rembg/shadow/",
        headers={"ApiKey": "YOUR_API_KEY"},
        files={"image": image_file},
        data={
            "shadow_color": "(0, 0, 0, 150)",
            "offset": "(10, 10)",
            "blur_radius": 15,
        },
        timeout=60,
    )
response.raise_for_status()

with open("sneaker-shadow.png", "wb") as out:
    out.write(response.content)

Settings that look natural

Styleshadow_coloroffsetblur_radius
Soft catalog shadow(0, 0, 0, 90)(6, 6)20
Standard drop shadow(0, 0, 0, 150)(10, 10)15
Strong, dramatic(0, 0, 0, 200)(18, 18)8
Tinted (on colored backgrounds)(40, 30, 90, 110)(8, 10)18

Tips for believable shadows

  • Lower opacity beats a darker color. Real shadows are rarely solid black. Start around 90 to 150 alpha.
  • Softer for bigger images. Scale blur_radius and offset with the image size. Values that suit 1,000 px look harsh at 3,000 px.
  • Match the light direction. If the original photo was lit from the left, a shadow falling to the right looks correct. Keep one direction across the catalog.
  • Tint on colored backgrounds. A shadow slightly darker than the background color looks more natural than gray.

Where shadows help most

Shoes, bags, bottles, furniture, and electronics benefit the most because they normally sit on a surface. Flat items like clothing laid flat or printed paper usually look better with a very light shadow or none.

Test settings on your own products in the playground. If you sell on marketplaces with strict main-image rules, check them first. Our Amazon image guide explains what is allowed.

Scale shadow settings with image size

The tips above say to scale offset and blur_radius with the image. In practice that means tuning once at a reference size and deriving the values for everything else, so a 1,000 px thumbnail and a 3,000 px hero image get the same look.

from PIL import Image

REFERENCE_WIDTH = 1000
BASE = {"offset": (10, 10), "blur_radius": 15, "alpha": 150}


def shadow_params(path: str) -> dict:
    with Image.open(path) as img:
        scale = img.width / REFERENCE_WIDTH
    dx, dy = (round(v * scale) for v in BASE["offset"])
    return {
        "shadow_color": f"(0, 0, 0, {BASE['alpha']})",
        "offset": f"({dx}, {dy})",
        "blur_radius": max(1, round(BASE["blur_radius"] * scale)),
    }


print(shadow_params("sneaker.jpg"))
# {'shadow_color': '(0, 0, 0, 150)', 'offset': '(20, 20)', 'blur_radius': 30} for a 2,000 px image

Note the formats: shadow_color and offset are sent as tuple strings, while blur_radius is an integer. A malformed value returns a 400 invalid_image error.

Give the shadow room

If the product touches the edge of the frame, the shadow has nowhere to go and gets cut off. Add a margin of plain background before uploading. Pad with a color close to the existing background so the subject detection is not confused by a hard border.

from PIL import Image, ImageOps

with Image.open("sneaker.jpg") as img:
    margin = round(max(img.size) * 0.08)  # 8% on each side
    padded = ImageOps.expand(img.convert("RGB"), border=margin, fill=(255, 255, 255))
    padded.save("sneaker-padded.jpg", quality=95)

Processing a whole catalog with consistent settings

Shadows only look professional when they are identical across a product grid. Keep the settings in one place, run the catalog through a single script, and never tweak values per image by hand. This version uses a small thread pool and stops immediately if the quota runs out.

import time
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path

import requests

API_URL = "https://fapihub.com/v2/rembg/shadow/"
HEADERS = {"ApiKey": "YOUR_API_KEY"}
SETTINGS = {"shadow_color": "(0, 0, 0, 90)", "offset": "(6, 6)", "blur_radius": 20}
OUT_DIR = Path("shadowed")
OUT_DIR.mkdir(exist_ok=True)


def process(path: Path) -> str:
    target = OUT_DIR / f"{path.stem}.png"
    if target.exists():
        return f"skip {path.name}"
    for attempt in range(4):
        with path.open("rb") as image_file:
            r = requests.post(API_URL, headers=HEADERS, files={"image": image_file},
                              data=SETTINGS, timeout=60)
        if r.ok:
            target.write_bytes(r.content)
            return f"ok {path.name}"
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", 5)))
        elif r.status_code >= 500:
            time.sleep(2 ** attempt)
        elif r.status_code == 402:
            raise SystemExit("Out of credits, stopping the batch")
        else:
            return f"fail {path.name}: {r.json()['error']['code']}"
    return f"fail {path.name}: retries exhausted"


with ThreadPoolExecutor(max_workers=4) as pool:
    for line in pool.map(process, sorted(Path("products").glob("*.jpg"))):
        print(line)

Skipping files that already have output makes the script safe to re-run after an interruption without paying twice. Our bulk processing guide goes deeper on queues and concurrency.

Placing the result on your catalog background

Check the output before you build around it. If the PNG you get back has an alpha channel, flatten it onto the exact background your store uses so the shadow blends into that color instead of whatever the viewer's page shows. This also lets you save a smaller JPEG for opaque product pages.

from PIL import Image

STORE_BACKGROUND = (255, 255, 255)

with Image.open("sneaker-shadow.png") as img:
    if img.mode in ("RGBA", "LA"):
        canvas = Image.new("RGB", img.size, STORE_BACKGROUND)
        canvas.paste(img, mask=img.getchannel("A"))
        img = canvas
    img.convert("RGB").save("sneaker-shadow.jpg", quality=92)

The same step in Node.js with Sharp is sharp("sneaker-shadow.png").flatten({ background: "#ffffff" }).jpeg({ quality: 92 }).toFile("sneaker-shadow.jpg").

Common shadow problems

  • Double shadows: if the original photo already has a strong cast shadow, it may be partly kept with the subject and then a second shadow is added. Reshoot with flatter lighting, or use a lighter shadow_color alpha.
  • Glass and clear plastic: transparent products have soft, partial edges, so a dark shadow can show through them. Use low opacity and a larger blur_radius.
  • Inconsistent direction across a grid: one product with a shadow to the left among others to the right looks like a mistake. Keep one offset for the whole catalog.
  • Harsh edges: a small blur_radius with high opacity reads as a sticker. Raise the radius before lowering the opacity further.

Cost of a shadow pipeline

The shadow endpoint is on every plan and each call is one request. A store with 1,500 products and four photos per product needs 6,000 requests for the first pass, which fits within the Pro plan's 7,500 monthly requests at $15. Budget a few hundred extra requests for testing settings, since every test call counts. For a one-off catalog cleanup with no ongoing volume, Per Usage at $10 per 1,000 requests ($60 for 6,000) may suit better because those requests never expire. More on fitting image work into a store workflow is in the e-commerce use case.

Frequently asked questions

Does the shadow endpoint also remove the background?

Yes. It detects the product, removes the original background, and adds the drop shadow in the same request, so you do not need to call /v2/rembg/ first.

What does the last number in shadow_color mean?

It is the alpha value, from 0 (invisible) to 255 (fully opaque). Most natural-looking product shadows sit between about 90 and 150.

Can I make the shadow fall upward or to the left?

The offset is an (x, y) pair in pixels. The examples in this guide use positive values, which in standard image coordinates push the shadow right and down and suit a light source at the top left. Preview any other direction in the playground before using it in a batch.

Are shadows allowed on marketplace main images?

Rules differ by marketplace and change over time, so check each one's current image requirements. Many expect a pure white background for the main image, and some are stricter about added effects than others.