HiAPI
Blog

Tutoriale

Rimuovere lo sfondo delle immagini con l'API: editing basato su prompt

Come usare i modelli image-to-image su hiapi per sostituire o rimuovere lo sfondo di un'immagine: codice minimo, modalità batch e dettagli dello schema che rendono la composizione delle foto prodotto impeccabile.

Aug 6, 2026
Rimuovere lo sfondo delle immagini con l'API: editing basato su prompt

I tradizionali strumenti di rimozione dello sfondo offrono una sola operazione: ritagliare il soggetto lasciando un'area trasparente (o un riempimento a tinta unita). La rimozione dello sfondo basata su prompt con modelli image-to-image, invece, permette di ottenere in una sola chiamata sia il risultato del ritaglio sia lo sfondo sostituito: uno sfondo da studio bianco senza giunture, una scena di vita reale, un colore puro del brand, perché il modello rigenera pixel per pixel lo sfondo attorno al soggetto preservato, invece di limitarsi a calcolare una maschera alpha.

Questa guida mostrerà esattamente come farlo su hiapi: quali modelli image-to-image eccellono davvero nella rimozione dello sfondo, la struttura di richiesta/risposta su cui basare il codice e i pattern di produzione (callback, retry, gestione degli errori) che evitano che i job batch contaminino silenziosamente il tuo catalogo prodotti.

Prerequisiti:

  • Ottieni la tua chiave API hiapi dalla tua dashboard
  • Python 3.9+ con requests installato (pip install requests)
  • Le tue immagini sorgente devono essere ospitate su un URL pubblico: l'API dei task di hiapi recupera le immagini tramite URL, non tramite upload di file, quindi i percorsi locali non sono validi. Va bene qualsiasi object storage o CDN.

Come funziona la rimozione dello sfondo come task image-to-image

Ogni generazione su hiapi, che sia immagine, video o audio, segue lo stesso ciclo di vita asincrono dei task: invii un task, ottieni un taskId, poi o esegui il polling del risultato o lo ricevi tramite un URL di callback. Il contratto completo è nella riferimento API dei task asincroni; ai fini della rimozione dello sfondo, le parti importanti sono:

  1. Una POST /v1/tasks con model + input (il tuo prompt, l'URL dell'immagine sorgente e la forma dell'output) restituisce {"code":200,"data":{"taskId":"tk-hiapi-..."}}. A questo punto non c'è ancora alcun campo status.
  2. GET /v1/tasks/{taskId} restituisce data.status, con uno dei valori queued, handling, archiving, success, fail. Solo success e fail sono stati terminali.
  3. Quando lo stato è success, data.output è un array di {"url","type","expireAt"}. url è un link firmato a breve scadenza (expireAt è un timestamp Unix): scaricalo subito, non memorizzare l'hotlink.

Per quanto riguarda il modello, usa l'endpoint image-to-image e fai in modo che il prompt descriva lo sfondo sostitutivo, invece di dire semplicemente "rimuovi lo sfondo": questi modelli rispondono molto meglio a obiettivi concreti che a istruzioni negative:

Remove the background completely and replace it with a seamless, pure white
studio background. Keep the subject's shape, edges, lighting, and colors
exactly unchanged. Do not add shadows, reflections, or any other objects
that were not in the original image.

Esempio minimo: un'immagine in input, sfondo sostituito

seedream-5.0-lite/image-to-image è l'opzione più economica e sufficiente per questo scopo: accetta prompt, image_urls, aspect_ratio e resolution (solo 2K o 4K; questo endpoint non ha il livello 1K, e campi sconosciuti come n o seed vengono rifiutati con un 400).

import os
import time
import requests

API = "https://api.hiapi.ai/v1"
HEADERS = {
    "Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}",
    "Content-Type": "application/json",
}

BACKGROUND_PROMPT = (
    "Remove the background completely and replace it with a seamless, pure "
    "white studio background. Keep the subject's shape, edges, lighting, and "
    "colors exactly unchanged. Do not add shadows, reflections, or any other "
    "objects that were not in the original image."
)

def create_task(model: str, task_input: dict) -> str:
    resp = requests.post(f"{API}/tasks", headers=HEADERS,
                          json={"model": model, "input": task_input}, timeout=60)
    resp.raise_for_status()
    return resp.json()["data"]["taskId"]

def wait_task(task_id: str, timeout_s: int = 180, poll_s: int = 3) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        resp = requests.get(f"{API}/tasks/{task_id}", headers=HEADERS, timeout=30)
        resp.raise_for_status()
        task = resp.json()["data"]
        if task["status"] == "success":
            return task
        if task["status"] == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task {task_id} failed: {err.get('code')} {err.get('message')}")
        time.sleep(poll_s)
    raise TimeoutError(f"task {task_id} still {task['status']} after {timeout_s}s")

task_id = create_task("seedream-5.0-lite/image-to-image", {
    "prompt": BACKGROUND_PROMPT,
    "image_urls": ["https://your-cdn.example.com/product-photo.jpg"],
    "aspect_ratio": "1:1",
    "resolution": "2K",
})
task = wait_task(task_id)
image_bytes = requests.get(task["output"][0]["url"], timeout=60).content

with open("product-no-bg.png", "wb") as f:
    f.write(image_bytes)

Questo è l'intero ciclo: invio, polling, download. Per una rapida verifica senza codice prima di integrarlo in una pipeline, AI Product Photo Lab esegue lo stesso tipo di sostituzione dello sfondo nel browser.

Elaborazione batch di un catalogo prodotti

I cataloghi prodotti reali contengono decine o centinaia di immagini, e l'API dei task non ha un endpoint batch nativo: devi espandere manualmente gli invii dei singoli task ed eseguirne il polling in parallelo:

from concurrent.futures import ThreadPoolExecutor, as_completed

def remove_background(image_url: str, aspect_ratio: str = "1:1") -> str:
    task_id = create_task("seedream-5.0-lite/image-to-image", {
        "prompt": BACKGROUND_PROMPT,
        "image_urls": [image_url],
        "aspect_ratio": aspect_ratio,
        "resolution": "2K",
    })
    task = wait_task(task_id)
    return task["output"][0]["url"]

source_urls = [
    "https://your-cdn.example.com/sku-1001.jpg",
    "https://your-cdn.example.com/sku-1002.jpg",
    # ...
]

results = {}
with ThreadPoolExecutor(max_workers=5) as pool:
    futures = {pool.submit(remove_background, url): url for url in source_urls}
    for future in as_completed(futures):
        url = futures[future]
        try:
            results[url] = future.result()
        except Exception as exc:
            results[url] = None
            print(f"failed: {url}: {exc}")

Mantieni max_workers moderato: eseguire polling troppo frequentemente su molti task concorrenti consuma solo la quota di richieste e non accelera la generazione, perché il tempo reale di ogni task è determinato principalmente dall'inferenza del modello, non dalla frequenza del polling.

Rafforzamento per la produzione: composizione precisa, callback, idempotenza, errori

Preserva la composizione esatta del prodotto. Per il lavoro sui cataloghi, di solito non puoi permetterti che il modello ricomponga o ritagli leggermente il soggetto: una scarpa centrata nell'input deve rimanere centrata nell'output. seedream-5.0-pro/image-to-image supporta un valore di aspect_ratio che il livello lite non supporta: match_input_image, che blocca la tela di output alle dimensioni dell'immagine di input, invece di forzare un rapporto fisso. Accetta inoltre fino a 10 image_urls per chiamata e limita resolution a 1K/2K (un set di risoluzioni diverso da lite: non dare per scontato che gli schema degli endpoint seedream siano identici, ognuno ha le proprie enumerazioni). Per un lavoro di pura rimozione e sostituzione dello sfondo in cui la composizione deve corrispondere esattamente, questo è il livello da scegliere, a un costo per immagine più elevato: vedi le tariffe attuali.

Se hai bisogno di fare riferimento a più angolazioni sorgente in una sola volta (ad esempio la vista frontale e laterale dello stesso prodotto), invece di elaborare un'immagine per chiamata, nano-banana-2 è un'alternativa ragionevole: accetta immagini di riferimento tramite image_input (non image_urls) e ha resolution che può essere 1K/2K/4K.

Usa i callback invece del polling. Per qualsiasi job batch su scala reale, i webhook sono preferibili al polling: aggiungi un oggetto callback come fratello di input (non annidato al suo interno, POST /v1/tasks rifiuta quella struttura):

task_input = {
    "prompt": BACKGROUND_PROMPT,
    "image_urls": [image_url],
    "aspect_ratio": "1:1",
    "resolution": "2K",
}
resp = requests.post(f"{API}/tasks", headers=HEADERS, json={
    "model": "seedream-5.0-lite/image-to-image",
    "input": task_input,
    "callback": {"url": "https://your-service.example.com/hooks/hiapi", "when": "final"},
}, timeout=60)

Quando un task raggiunge lo stato terminale, hiapi invia una POST al tuo URL di callback con lo stesso oggetto data che restituirebbe GET /v1/tasks/{taskId}. Se vuoi verificare l'autenticità del callback lato server, consulta l'header HMAC opzionale X-HiAPI-Signature nel riferimento dei task asincroni.

Idempotenza. Non esiste un campo documentato idempotency_key su POST /v1/tasks: implementa l'idempotenza a livello di applicazione. Usa un piccolo archivio (una riga di database, una chiave Redis o anche un file locale per script one-shot), con chiave l'hash di (image_url, prompt, model), e prima di inviare un nuovo task verifica se quella chiave ha già un taskId o un URL di output memorizzato. Se il tuo job batch si interrompe a metà e viene rieseguito, potrai evitare di pagare due volte per le immagini già elaborate, invece di aumentare silenziosamente i costi.

Gestione degli errori: tre forme distinte, da non trattare allo stesso modo:

  • Validazione della richiesta (input errato alla creazione): HTTP 400, struttura piatta: {"code":400,"error_code":"INVALID_REQUEST","message":"...","data":null}. Significa che il tuo dizionario input non corrisponde allo schema del modello: controlla se ci sono nomi di campo scritti male o valori di enumerazione errati.
  • Errore di autenticazione: HTTP 401, con una struttura diversa e annidata: {"error":{"code":"permission_denied","message":"...","request_id":"...","type":"hiapi_error"}}. La chiave API è mancante o scaduta, non è un errore della richiesta: non riprovare, correggi la chiave.
  • Errore a livello di task (la richiesta è valida, ma la generazione fallisce): si manifesta durante il polling: data.status == "fail", data.error descrive la causa. Questo è ritentabile; un errore upstream temporaneo potrebbe avere successo se reinviato.
if task["status"] == "fail":
    err = task.get("error") or {}
    # log err["code"] / err["message"], then decide: retry, skip, or alert

Non raggruppare tutti e tre in un unico except Exception: un 401 ritentato in un ciclo consuma solo il tuo rate limit e non avrà mai successo, mentre un fail a livello di task potrebbe effettivamente riuscire al secondo tentativo.

Prossimi passi

Domande frequenti

Rimuove davvero lo sfondo o lo copre soltanto? Funzionalmente entrambe le cose: il modello rigenera l'intera tela attorno al soggetto preservato, quindi non lascia maschere residue o bordi frastagliati come gli strumenti di ritaglio basati sul rilevamento dei bordi. Il prezzo da pagare è che devi specificare esplicitamente nel prompt di "mantenere tutto il resto invariato", altrimenti il modello ricreerà volentieri lo stile anche del soggetto.

Posso ottenere un PNG trasparente invece di uno sfondo a tinta unita? Richiedilo direttamente nel prompt (ad esempio "sostituisci lo sfondo con trasparente" o "isola il soggetto su sfondo trasparente") e richiedi un output PNG dove il modello supporta il campo output_format: il supporto varia da modello a modello, quindi controlla la pagina del modello specifico prima di darlo per scontato.

Perché la mia immagine viene restituita ricomposta invece di corrispondere all'originale? Probabilmente stai usando un livello il cui valore di enumerazione aspect_ratio non include match_input_image: nei livelli lite/4.5 scegli un rapporto fisso, che può ritagliare o aggiungere bande nere rispetto all'immagine sorgente. Passa a seedream-5.0-pro/image-to-image e imposta aspect_ratio su match_input_image.

Quanto costa ogni immagine? Il prezzo varia in base al modello e al livello di risoluzione e cambia nel tempo: consulta le tariffe, invece di codificare i numeri nel tuo codice o nella documentazione.

E se l'URL della mia immagine non è ancora pubblico? L'API dei task recupera le immagini tramite URL lato server, quindi l'URL deve essere accessibile senza autenticazione. Carica prima l'immagine su un bucket pubblico o un percorso CDN: URL firmati/privati o percorsi localhost falliranno alla creazione del task o (peggio) falliranno silenziosamente durante l'elaborazione.

Inizia a creare con artefatti IA persistenti

Ottieni la tua chiave API e chiama modelli di immagini, video e audio tramite un'unica API basata sui task.

Leggi la documentazione API