HiAPI
Blog

Tutoriale

Costruisci applicazioni AI multi-modello su hiapi con un'unica API unificata

Oct 8, 2026
Costruisci applicazioni AI multi-modello su hiapi con un'unica API unificata
Modello
seedance-2.5

Cosa costruirai

La maggior parte dei team finisce per chiamare tre o quattro provider AI diversi per un singolo prodotto: un modello text-to-image per le miniature, un modello text-to-video per i brevi video social, e magari un modello TTS per la voce fuori campo. Ogni provider ha il proprio SDK, il proprio schema di autenticazione, la propria logica di polling. hiapi riunisce tutto in un unico modello di endpoint: POST /v1/tasks crea un task, GET /v1/tasks/:id interroga il task, e questo vale per ogni modello di generazione della piattaforma.

Questo tutorial dimostra il modello di integrazione con tre modelli reali e attualmente attivi — z-image (text-to-image), qwen-image-2.0 (text-to-image, schema diverso) e seedance-2.5 (text-to-video) — così vedrai chiaramente dove l'API è identica e dove cambia solo l'oggetto input di ciascun modello.

Prerequisiti

  • Una API key hiapi ottenuta dalla console
  • curl o qualsiasi client HTTP in grado di inviare JSON
  • Nessun SDK necessario — ogni esempio qui sotto è una chiamata HTTP pura

Ogni richiesta richiede un header:

Authorization: Bearer <your-api-key>

Se la key è non valida o mancante, viene restituito un errore di tipo 401 prima di raggiungere qualsiasi modello:

{
  "error": {
    "code": "permission_denied",
    "message": "invalid api key",
    "request_id": "req_...",
    "type": "hiapi_error"
  }
}

Non esiste alcuna modalità senza key o anonima — ogni chiamata a /v1/tasks deve trasportare un bearer token valido. Per la rotazione delle key e gli scope, vedi Autenticazione.

Esempio minimo funzionante

1. Creare un task

La struttura della chiamata di creazione è sempre la stessa: un id model e un oggetto input specifico del modello. Ecco come creare un task immagine con z-image:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "z-image",
    "input": {
      "prompt": "a minimalist workspace, soft morning light, isometric illustration",
      "aspect_ratio": "16:9"
    }
  }'

La risposta arriva immediatamente — la generazione è asincrona:

{
  "code": 200,
  "message": "ok",
  "data": {
    "taskId": "tk-hiapi-xxxxxxxxxxxx",
    "status": "queued"
  }
}

Ora segui lo stesso schema con qwen-image-2.0. Stesso endpoint, stessa struttura esterna — cambia solo input (questo modello accetta una stringa size invece di aspect_ratio):

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen-image-2.0",
    "input": {
      "prompt": "a minimalist workspace, soft morning light, isometric illustration",
      "size": "2048*2048"
    }
  }'

Poi crea un task video con seedance-2.5/text-to-video — sempre la stessa struttura esterna, ora con duration e un enum aspect_ratio più ampio:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2.5/text-to-video",
    "input": {
      "prompt": "a paper airplane gliding through a sunlit office, slow motion",
      "duration": 5,
      "aspect_ratio": "16:9"
    }
  }'

Questo è il punto centrale: la struttura esterna del task non cambia mai, cambia solo input. Ogni modello su hiapi documenta il proprio schema input nella sua pagina del modello — controlla sempre lo schema del modello specifico prima di costruire una nuova integrazione, perché campi come aspect_ratio, size e duration non sono intercambiabili tra modelli diversi.

2. Polling del risultato

curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-xxxxxxxxxxxx \
  -H "Authorization: Bearer $HIAPI_API_KEY"

status attraversa in sequenza queued → handling → archiving, per poi giungere allo stato terminale success o fail. In caso di successo, data.output è un array — la maggior parte dei modelli immagine/video restituisce un singolo elemento, ma un singolo task può restituire più tipi di output (ad esempio un modello video che restituisce sia video che thumbnail):

{
  "code": 200,
  "message": "ok",
  "data": {
    "taskId": "tk-hiapi-xxxxxxxxxxxx",
    "status": "success",
    "output": [
      { "type": "image", "url": "https://...", "expireAt": "2026-10-09T00:00:00Z" }
    ]
  }
}

expireAt è importante — gli URL di output sono temporanei per impostazione predefinita. Scarica i byte non appena il task è completato (o ricaricali sul tuo storage). Se hai bisogno che la piattaforma conservi i file più a lungo, imposta storage: "persistent" al momento della creazione oppure effettua l'upgrade successivamente — vedi Storage.

In caso di errore, ottieni un oggetto error invece di output:

{
  "code": 200,
  "message": "ok",
  "data": {
    "taskId": "tk-hiapi-xxxxxxxxxxxx",
    "status": "fail",
    "error": { "code": "...", "message": "..." }
  }
}

Il seguente ciclo di polling minimo in Python funziona allo stesso modo per tutti i modelli sopra:

import time
import requests

def run_task(model: str, input_payload: dict, api_key: str) -> list[dict]:
    headers = {"Authorization": f"Bearer {api_key}"}
    create = requests.post(
        "https://api.hiapi.ai/v1/tasks",
        headers=headers,
        json={"model": model, "input": input_payload},
    ).json()
    task_id = create["data"]["taskId"]

    time.sleep(3)  # initial wait before first poll
    while True:
        poll = requests.get(
            f"https://api.hiapi.ai/v1/tasks/{task_id}", headers=headers
        ).json()
        status = poll["data"]["status"]
        if status == "success":
            return poll["data"]["output"]
        if status == "fail":
            raise RuntimeError(poll["data"]["error"])
        time.sleep(4)  # 3-5s between polls; use 5-10s for video models

Modelli per la produzione

Usare i callback al posto del polling in produzione

Il polling va bene per script o strumenti CLI, ma a qualsiasi volume di traffico reale è preferibile usare i callback: passa un oggetto callback al momento della creazione del task e hiapi invierà via POST il risultato allo stato terminale al tuo endpoint, invece di farti ripetere GET /v1/tasks/:id in un ciclo.

{
  "model": "z-image",
  "input": { "prompt": "...", "aspect_ratio": "1:1" },
  "callback": { "url": "https://yourapp.com/webhooks/hiapi", "when": "final" }
}

when: "final" significa che riceverai una sola POST, attivata allo stato terminale (success o fail) — senza il rumore degli stati intermedi handling/archiving. Se hai configurato un callback e non ricevi nulla, verifica che il tuo endpoint sia raggiungibile pubblicamente e restituisca rapidamente un 2xx; per la causa più comune di callback persi silenziosamente, vedi questo articolo correlato.

Retry idempotenti

Le turbolenze di rete sono inevitabili. Se ritenti una chiamata POST /v1/tasks dopo un timeout senza protezioni, potresti creare (e pagare) task duplicati. Invia un header Idempotency-Key (qualsiasi stringa fino a 255 byte, tipicamente un UUID generato per ogni richiesta logica): i retry con la stessa key restituiscono il task originale invece di crearne uno nuovo:

curl -s -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Idempotency-Key: 8f14e45f-ceea-467e-bd5f-...-9a2c" \
  -H "Content-Type: application/json" \
  -d '{"model": "z-image", "input": {"prompt": "...", "aspect_ratio": "1:1"}}'

Gestire gli errori di validazione per modello

Poiché ogni modello ha il proprio schema input, il modello di fallimento più comune è un 400 su un campo — un campo valido per un modello ma non per un altro, ad esempio inviare aspect_ratio a un modello che si aspetta size:

{
  "code": 400,
  "data": null,
  "error_code": "INVALID_REQUEST",
  "message": "invalid input: aspect_ratio: value must be one of '1:1', '4:3', '3:4', '16:9', '9:16'"
}

Se stai costruendo un router multi-modello (che lasci a un utente o alla tua logica scegliere il modello a runtime), mantieni un piccolo registro di builder di input per modello, anziché una struttura payload condivisa:

MODEL_INPUT_BUILDERS = {
    "z-image": lambda prompt, **kw: {
        "prompt": prompt, "aspect_ratio": kw.get("aspect_ratio", "1:1")
    },
    "qwen-image-2.0": lambda prompt, **kw: {
        "prompt": prompt, "size": kw.get("size", "2048*2048")
    },
    "seedance-2.5/text-to-video": lambda prompt, **kw: {
        "prompt": prompt,
        "duration": kw.get("duration", 5),
        "aspect_ratio": kw.get("aspect_ratio", "16:9"),
    },
}

def build_task_payload(model: str, prompt: str, **kw) -> dict:
    return {"model": model, "input": MODEL_INPUT_BUILDERS[model](prompt, **kw)}

Così le particolarità di ciascun modello sono concentrate in un unico punto, invece che sparse in ogni punto di chiamata. Un taskId inesistente o di un altro account restituisce un normale 404:

{ "code": 404, "message": "task not found", "data": null }

Mentre un 503 temporaneo va ritentato con backoff esponenziale, invece di esporlo direttamente all'utente — i limiti di rate e di capacità sono documentati in Rate limits.

Letture correlate

Domande frequenti

Ogni modello richiede un SDK o una API key diversi? No. Una sola API key, un solo modello di endpoint (/v1/tasks), valido per ogni modello della piattaforma. Cambia solo l'oggetto input nel corpo della richiesta.

Perché subito dopo aver creato un task la risposta non contiene ancora output? La generazione è asincrona. La chiamata di creazione restituisce solo taskId e status: "queued" — devi fare polling su GET /v1/tasks/:id (oppure usare un callback) finché status non raggiunge success o fail.

Posso chiamare l'API senza autenticazione per un test rapido? No — ogni chiamata a /v1/tasks richiede una key valida in Authorization: Bearer. Le chiamate di produzione non hanno modalità senza key o demo; genera prima una key dalla console.

Cosa succede se faccio polling dopo la scadenza dell'URL di output? Il record del task stesso rimane, ma per i task non persistenti output[].url non sarà più risolvibile dopo expireAt. Scarica subito i file al momento del successo, oppure imposta storage: "persistent" alla creazione se hai bisogno di conservarli a lungo termine.

Come faccio a sapere quali campi accetta un modello? Consulta la pagina del modello sotto Modelli — ognuna documenta il suo schema input esatto. Non dare per scontato che aspect_ratio, size o duration siano comuni tra modelli; come mostrato sopra, z-image e qwen-image-2.0 sono già incoerenti su come esprimono la forma dell'immagine.

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