Tutorial
Crea aplicaciones de IA multimodelo con una única API unificada en hiapi
- Modelo
- seedance-2.5
Qué vas a construir
La mayoría de los equipos acaban llamando a tres o cuatro proveedores de IA distintos para un solo producto: un modelo de texto a imagen para las miniaturas, un modelo de texto a vídeo para los clips cortos de redes sociales y, quizá, un modelo TTS para la narración. Cada proveedor tiene su propio SDK, su propio esquema de autenticación y su propia lógica de sondeo. hiapi reúne todo esto en un único patrón de endpoint: POST /v1/tasks crea la tarea y GET /v1/tasks/:id la consulta, y esto se aplica a todos los modelos generativos de la plataforma.
Este tutorial demuestra ese patrón de integración con tres modelos reales que están actualmente en línea —z-image (texto a imagen), qwen-image-2.0 (texto a imagen, con un schema distinto) y seedance-2.5 (texto a vídeo)— para que veas con claridad dónde la API es idéntica y dónde solo cambia el objeto input de cada modelo.
Requisitos previos
- Una API key de hiapi obtenida desde la consola
curlo cualquier cliente HTTP capaz de enviar JSON- No hace falta SDK: todos los ejemplos siguientes son llamadas HTTP puras
Cada petición necesita una cabecera:
Authorization: Bearer <your-api-key>
Si la key no es válida o falta, se devuelve un error de tipo 401 antes de llegar a ningún modelo:
{
"error": {
"code": "permission_denied",
"message": "invalid api key",
"request_id": "req_...",
"type": "hiapi_error"
}
}
No existe un modo sin clave ni anónimo: cada llamada a /v1/tasks debe llevar un bearer token válido. Para la rotación y el alcance de las claves, consulta Autenticación.
Ejemplo mínimo funcional
1. Crear una tarea
La estructura de la llamada de creación es siempre la misma: un id de model y un objeto input específico del modelo. A continuación se crea una tarea de imagen 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 respuesta llega de inmediato: la generación es asíncrona:
{
"code": 200,
"message": "ok",
"data": {
"taskId": "tk-hiapi-xxxxxxxxxxxx",
"status": "queued"
}
}
Ahora usa el mismo patrón con qwen-image-2.0. El mismo endpoint y la misma estructura externa: solo cambia el input (este modelo recibe una cadena size en lugar de 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"
}
}'
Y ahora crea una tarea de vídeo con seedance-2.5/text-to-video: la misma estructura externa de nuevo, añadiendo duration y un enum de aspect_ratio con más valores:
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"
}
}'
Esta es la idea central: la estructura externa de la tarea nunca cambia, solo cambia el input. Cada modelo de hiapi documenta su propio schema de input en su página de modelo; consulta siempre el schema del modelo concreto antes de construir una nueva integración, porque campos como aspect_ratio, size y duration no son intercambiables entre modelos.
2. Consultar el resultado mediante sondeo
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-xxxxxxxxxxxx \
-H "Authorization: Bearer $HIAPI_API_KEY"
El status pasa por queued → handling → archiving y finalmente alcanza un estado terminal success o fail. En caso de éxito, data.output es un array: la mayoría de los modelos de imagen y vídeo devuelven un solo elemento, pero una misma tarea puede devolver varios tipos de salida (por ejemplo, un modelo de vídeo que devuelve a la vez video y thumbnail):
{
"code": 200,
"message": "ok",
"data": {
"taskId": "tk-hiapi-xxxxxxxxxxxx",
"status": "success",
"output": [
{ "type": "image", "url": "https://...", "expireAt": "2026-10-09T00:00:00Z" }
]
}
}
expireAt es importante: las URL de salida son temporales de forma predeterminada. Descarga los bytes (o vuelve a subirlos a tu propio almacenamiento) en cuanto la tarea termine. Si necesitas que la plataforma conserve los archivos durante más tiempo, configura storage: "persistent" al crear la tarea o mejóralo después; consulta Almacenamiento.
Si la tarea falla, recibirás un objeto error en lugar de output:
{
"code": 200,
"message": "ok",
"data": {
"taskId": "tk-hiapi-xxxxxxxxxxxx",
"status": "fail",
"error": { "code": "...", "message": "..." }
}
}
El siguiente bucle de sondeo mínimo en Python funciona igual para todos los modelos anteriores:
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
Patrones para producción
Usar callbacks en producción en lugar de sondeo
El sondeo va bien para scripts o herramientas de CLI, pero con cualquier volumen de tráfico real se recomiendan los callbacks: pasa un objeto callback al crear la tarea y hiapi enviará el resultado del estado terminal por POST a tu endpoint, en lugar de obligarte a solicitar repetidamente GET /v1/tasks/:id en un bucle.
{
"model": "z-image",
"input": { "prompt": "...", "aspect_ratio": "1:1" },
"callback": { "url": "https://yourapp.com/webhooks/hiapi", "when": "final" }
}
when: "final" significa que recibirás un único POST, disparado en el estado terminal (success o fail): sin el ruido de los estados intermedios handling/archiving. Si has configurado un callback y no recibes nada, comprueba que tu endpoint sea accesible públicamente y que responda rápido con un 2xx; para conocer la causa más habitual de callbacks que se pierden en silencio, consulta este artículo relacionado.
Reintentos idempotentes
Las fluctuaciones de red son inevitables. Si reintentas una llamada POST /v1/tasks tras un tiempo de espera agotado sin protección, puedes crear (y que te facturen) tareas duplicadas. Envía una cabecera Idempotency-Key (cualquier cadena de no más de 255 bytes, normalmente un UUID generado para cada petición lógica): los reintentos con la misma key devuelven la tarea original en lugar de crear una nueva:
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"}}'
Gestionar errores de validación por modelo
Como cada modelo tiene su propio schema de input, el fallo más habitual es un 400 en un campo que es válido para un modelo y no para otro; por ejemplo, enviar aspect_ratio a un modelo que espera 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'"
}
Si estás construyendo un enrutador multimodelo (para que los usuarios o tu propia lógica elijan el modelo en tiempo de ejecución), mantén un pequeño registro de constructores de input por modelo en lugar de una estructura de payload compartida:
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)}
Así concentras las particularidades de cada modelo en un solo lugar en vez de dispersarlas por cada punto de llamada. Un taskId inexistente o de otra cuenta devuelve un 404 normal:
{ "code": 404, "message": "task not found", "data": null }
Y los 503 temporales deben reintentarse con retroceso exponencial en lugar de exponerse directamente al usuario; los límites de velocidad y de capacidad se documentan en Límites de velocidad.
Lecturas relacionadas
- Referencia de la API de tareas asíncronas: el contrato completo de
create/detail - Autenticación: alcance y rotación de las API keys
- Almacenamiento: conservar las salidas más allá de la expiración predeterminada
- Por qué tu callback de tarea puede no dispararse
- Explorar todos los modelos: el schema de
inputde cada modelo está en su página - Precios: el coste se calcula por modelo y salida, no de forma uniforme por petición
Preguntas frecuentes
¿Cada modelo necesita un SDK o una API key distintos?
No. Una sola API key y un único patrón de endpoint (/v1/tasks) sirven para todos los modelos de la plataforma. Solo cambia el objeto input del cuerpo de la petición.
¿Por qué justo después de crear la tarea la respuesta todavía no trae output?
La generación es asíncrona. La llamada de creación solo devuelve el taskId y status: "queued": debes sondear GET /v1/tasks/:id (o usar un callback) hasta que el status llegue a success o fail.
¿Se puede llamar a la API sin autenticación para hacer pruebas rápidas?
No: cada llamada a /v1/tasks requiere una key válida en Authorization: Bearer. Las llamadas de producción no tienen modo sin clave ni modo de demostración; genera primero una key desde la consola.
¿Qué pasa si sondeo después de que caduque la URL de salida?
El registro de la tarea se conserva, pero output[].url de las tareas no persistentes deja de resolverse después de expireAt. Descarga el archivo en cuanto tengas éxito o, si necesitas conservarlo a largo plazo, configura storage: "persistent" al crear la tarea.
¿Cómo sé qué campos acepta un modelo?
Consulta la página de ese modelo en Modelos; cada una documenta su schema exacto de input. No des por hecho que aspect_ratio, size o duration son intercambiables entre modelos; como se ha visto arriba, z-image y qwen-image-2.0 ya difieren en cómo expresan la forma de la imagen.
Artículos relacionados
Ver todos los artículos →-
Tutorial
Cómo llamar a hailuo-2.3-fast/image-to-video a través de la API de HiAPI: práctica con curl y Python
La primera solicitud ejecutable del nivel rápido y de bajo coste de imagen a vídeo: del ID del modelo a la descarga del MP4
-
Tutorial
Cómo llamar a hailuo-2.3/text-to-video mediante la API de HiAPI: curl, Python y ejemplos de solicitud ejecutables
-
Tutorial
Uso de Kandinsky 6.0 Pro texto a video en HiAPI: tutorial con curl y Python
Demostración práctica centrada en la solicitud: construir la llamada de prompt a clip de video, enviar la solicitud, sondear el estado de finalización y descargar el resultado