Product shadow
A clean cutout floating on white looks unfinished the moment it sits next to a photograph with real light in it — nothing grounds it to a surface. This endpoint studies the shape of a cutout and casts a soft, directionally consistent shadow beneath it, the kind a photographer would have captured on a light table instead of a flat product shot.
Run it online
Run this on our servers with your account. Free tools run in your browser; this one bills your KIT balance per the price above.
The tell that gives away a cutout
Remove a background and what's left is technically correct but visually dishonest: an object with no contact point, no weight, floating in a void. Human vision reads shadows before it reads almost anything else in a scene to judge depth and grounding, so a shadowless product photo registers as fake even to viewers who couldn't say why. Adding one back is less a decorative flourish than a repair to that missing depth cue.
What the endpoint actually does
POST a cutout image to /image/shadow — ideally one with a transparent or already-removed background — and the task analyzes the silhouette to build a shadow shape that follows the object's contours rather than a generic blurred oval underneath it. The result comes back as an image with the shadow composited in, ready to drop straight onto a white background, a lifestyle scene, or a marketplace listing template.
Why shadows in photography aren't arbitrary
Studio photographers spend real budget on light tables, reflectors and diffusion precisely to get a shadow that reads as natural rather than harsh or fake — a shadow that's too dark, too sharp-edged or pointed the wrong way is as suspicious to a shopper's eye as no shadow at all. This task aims for that same soft, plausible falloff, the kind that suggests an object actually rests on a surface under real light rather than sitting on a layer in an editor.
Fitting into a catalog pipeline
Most teams reach this endpoint right after a background removal step: strip the background, then cast the shadow, then composite onto whatever backdrop the listing needs. Because it's asynchronous, a batch of a few images and a batch of ten thousand SKUs both submit the same way — send each image, collect a task_id, and let results land by webhook as they finish, or pull them from a signed link if a script prefers to poll.
Pricing and what happens on failure
The cost is a small flat fee per request plus a per-image rate, published and predictable whether you're finishing one hero shot or an entire catalog refresh. If a shadow can't be generated the task retries automatically up to three times before returning a clear error, and a request that never succeeds is never billed — so a large batch only costs what actually completed.
What you can do with it
Marketplace listing photos
A seller uploads cutout product shots and casts a consistent shadow on all of them so the catalog looks shot in one studio session instead of assembled from mismatched sources.
E-commerce catalog refresh
A retailer migrating to a new template pipes existing cutouts through the endpoint to add depth before placing them on the new page backgrounds.
Ad creative production
A performance marketing team drops a shadowed product onto seasonal backgrounds for dozens of ad variants without re-shooting anything.
Print and packaging mockups
A design team building packaging mockups adds a grounded shadow so a rendered product sits convincingly on a mockup surface.
FAQ
How do I add a shadow to a product photo with this API?
POST a cutout image, ideally with a transparent background, to /image/shadow and the task returns the same image composited with a natural drop shadow beneath the object.
Does the input need a transparent background first?
A background-removed cutout gives the cleanest result since the endpoint works from the object's silhouette; pair it with a background removal endpoint if your source images still have a background.
Is the shadow generic or shaped to the object?
The shadow follows the object's actual contours rather than dropping a generic blurred oval underneath, which is what keeps it looking physically plausible.
Is the shadow API free to use?
There's no free tier — free tiers get abused and slow everyone down. Access runs on a prepaid ForHosting KIT balance: top up from $10.00 (it never expires) and each request is charged at its published price, so a call with no balance returns HTTP 402. No subscription, no tokens, no invented credits, and a failed task is never charged.
How do I get the result back?
The task returns a task_id immediately and processes asynchronously; collect the shadowed image via signed webhook, or from a signed link valid for 24 hours.
Can I process a whole catalog in bulk?
Yes, each image is submitted as its own request and the endpoint is fully asynchronous, so a script can queue thousands of cutouts and collect results as each one finishes.
Is this endpoint available now?
It's in final deployment and will be accepting jobs soon; the endpoint and pricing on this page are the ones that will go live.
What happens if shadow generation fails?
The task retries automatically up to three times before returning a clear error, and a failed request is never charged.
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, and soon from our app, email and Telegram.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/image/shadow \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"image":"https://ejemplo.com/imagen.jpg"}'const res = await fetch("https://api.kit.forhosting.com/image/shadow", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"image": "https://ejemplo.com/imagen.jpg"
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/image/shadow",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"image": "https://ejemplo.com/imagen.jpg"
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/image/shadow", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"image":"https://ejemplo.com/imagen.jpg"}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"image":"https://ejemplo.com/imagen.jpg"}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/image/shadow", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"image": "https://ejemplo.com/imagen.jpg"
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "image.shadow",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_mb | 15 |
max_megapixels | 12 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |