851-labs/background-remover turns a product, portrait, or pet photo into a transparent PNG with a single API call — no prompt, no mask, no aspect-ratio settings. This recipe covers the exact request shape, a working curl command, a Python script you can drop into a backend, and the one schema mistake that trips people up.
What you need
- An hiapi account and an API key from the dashboard.
- One image hosted at a public HTTPS URL. The task API takes URLs, not file uploads — if your source image only exists locally, put it on any CDN or object storage first.
All requests go to the unified task endpoint:
POST https://api.hiapi.ai/v1/tasks
Authorization: Bearer sk-<your-key>
Content-Type: application/json
The request shape (and the one rule that matters)
The model id is 851-labs/background-remover, passed bare. Its input schema has exactly one field:
| Field | Type | Required | Notes |
|---|---|---|---|
image_url | string | yes | Must match ^https?://[^\s]+$ — a directly reachable http(s) URL. |
That's the entire schema. Two things worth knowing before you write any code:
- There is no prompt, mask, background-color, or aspect-ratio field. This endpoint does one job — foreground/background separation — and nothing else. If you need to recolor or resize afterward, that's a second call to a different model.
- Extra fields are rejected, not ignored. Carry over a
formatorsizefield from another model's payload and you get400 INVALID_REQUEST: <root>: additional properties 'format' not allowedbefore the task even starts.
Minimal working example: curl
Create the task:
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-<your-key>" \
-H "Content-Type: application/json" \
-d '{
"model": "851-labs/background-remover",
"input": {
"image_url": "https://your-cdn.example.com/product-shot.jpg"
}
}'
A successful submission returns a task id inside data:
{"code": 200, "data": {"taskId": "tk-hiapi-..."}}
Poll for the result:
curl https://api.hiapi.ai/v1/tasks/tk-hiapi-... \
-H "Authorization: Bearer sk-<your-key>"
While running, data.status moves through queued → handling → archiving. On completion it flips to "success" and the transparent PNG appears in data.output:
{
"code": 200,
"data": {
"status": "success",
"output": [{"url": "https://.../result.png", "expireAt": "..."}]
}
}
The output URL is short-lived (note the expireAt). Download the bytes as soon as the task succeeds — don't hotlink or persist the raw URL.
Production-ready Python: submit, poll, download
import time
import requests
API_BASE = "https://api.hiapi.ai/v1/tasks"
API_KEY = "sk-<your-key>"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def remove_background(image_url: str) -> bytes:
# 1. Create the task
resp = requests.post(API_BASE, headers=HEADERS, json={
"model": "851-labs/background-remover",
"input": {"image_url": image_url},
}, timeout=60)
body = resp.json()
if resp.status_code != 200 or not (body.get("data") or {}).get("taskId"):
raise RuntimeError(f"create failed [{resp.status_code}]: {body}")
task_id = body["data"]["taskId"]
# 2. Poll until terminal state
deadline = time.time() + 300
while time.time() < deadline:
task = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS,
timeout=30).json().get("data") or {}
if task.get("status") == "success":
outputs = task.get("output") or []
if not outputs or not outputs[0].get("url"):
raise RuntimeError(f"task {task_id} succeeded but returned no output URL")
# 3. Download immediately — the URL expires
png = requests.get(outputs[0]["url"], timeout=120)
png.raise_for_status()
return png.content
if task.get("status") == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
time.sleep(3)
raise TimeoutError(f"task {task_id} still running after 300s")
if __name__ == "__main__":
png = remove_background("https://your-cdn.example.com/product-shot.jpg")
with open("cutout.png", "wb") as f:
f.write(png)
print(f"saved cutout.png ({len(png)} bytes)")
Background removal is a light image task, so it typically finishes well inside the 300-second deadline above — the timeout is a safety net, not the expected runtime.
Callbacks instead of polling
For a web backend, register a callback at task-creation time instead of polling in a request handler. The callback object sits at the top level, next to model:
{
"model": "851-labs/background-remover",
"input": { "image_url": "https://your-cdn.example.com/product-shot.jpg" },
"callback": {
"url": "https://your-app.example.com/hooks/hiapi",
"when": "final"
}
}
callback.when only supports "final" — you get exactly one POST when the task reaches success or fail, not progress events. Key your handler on taskId so a redelivered webhook doesn't double-process, and keep a periodic sweep over GET /v1/tasks/<id> for any id your endpoint never confirmed.
Error handling: the errors that matter
400 INVALID_REQUEST at creation — the message names the exact field:
| Message | Cause |
|---|---|
invalid input: image_url: missing required field "image_url" | input was empty or the key was misspelled |
image_url: 'not-a-url' does not match pattern '^https?://[^\s]+$' | The value isn't a well-formed http(s) URL |
<root>: additional properties 'format' not allowed | You sent a field this model doesn't accept |
These are permanent — retrying the same payload fails forever. Fix the field and resend.
401 permission_denied at creation — the key itself is the problem, not the request body:
{"error": {"code": "permission_denied", "message": "...", "request_id": "...", "type": "hiapi_error"}}
Check the key's model permissions in the dashboard.
status: "fail" on the task — creation succeeded but the run itself failed (usually the backend couldn't fetch image_url). Read data.error.code / message; this class of failure is safe to retry once with backoff.
A GET for an unknown or expired task id returns 404 with {"code": 404, "data": null, "message": "task not found"} — treat that as terminal in any reconciliation sweep, not as "still pending."
Where to go next
- 851-labs/background-remover model page — live examples and current pricing.
- Remove image backgrounds with prompt-based editing — a Seedream-based alternative if you also need to change the background rather than just strip it.
- hiapi async task API docs — full create/poll/callback reference.
- hiapi authentication docs — API key setup and header format.
- Pricing — current per-image rates.
FAQ
Do I need a prompt?
No. Supply one image_url and the model separates the foreground from the background on its own.
Is the output actually transparent? Yes — the result is a PNG with an alpha channel, ready to composite onto any background.
Can I change the aspect ratio, colors, or background in the same call? No. This endpoint only removes the background. For recoloring, resizing, or replacing the background, chain a second call to an image-editing model using the transparent PNG as input.
Why do I get "additional properties '...' not allowed"?
The schema takes exactly one field, image_url. Any field carried over from another model's payload — format, size, background_color, anything — is rejected outright rather than silently ignored.
How should I save the result?
Download it as soon as the task reaches status: "success". The output URL is signed and carries an expireAt; it isn't meant for long-term hotlinking.
What does it cost? Pricing is usage-based per image. Check the current rate on the pricing page.









