Tutorial
Expiración de las URL de salida de tareas de HiAPI (expireAt): descarga y transfiere los resultados antes de que caduquen
output[].url en estado terminal lleva expireAt; al expirar devuelve 404: cómo detectarlo, cómo descargar a tiempo y cómo recuperarse tras la expiración.
Usas POST /v1/tasks para generar una imagen o un vídeo, y cuando la tarea termina obtienes de data.output[0].url un enlace de temp.hiapi.ai que en ese momento abre bien, pero al acceder días después se convierte en una página HTML de «Not Found»:
HTTP/2 404
content-type: text/html
Lo que devuelve no son bytes de imagen/vídeo, sino una página 404 de Cloudflare. Solo hay una razón: la URL de salida de la tarea es un enlace temporal con validez limitada; en cuanto llega el expireAt (marca de tiempo Unix) del estado terminal, el objeto en temp.hiapi.ai se elimina y el hotlink deja de funcionar.
Primero, entiende los campos del estado terminal
Cuando el sondeo de la tarea alcanza el estado terminal (GET /v1/tasks/{taskId}) con éxito, los campos clave tienen este aspecto:
{
"code": 200,
"data": {
"taskId": "tk-hiapi-xxxxxxxx",
"status": "success",
"storage": "temp",
"created": 1791828181,
"completed": 1791828201,
"output": [
{
"artifactId": "...",
"type": "image",
"url": "https://temp.hiapi.ai/....png",
"expireAt": 1792432981
}
]
},
"message": "success"
}
data.status == "success"es el éxito en estado terminal (el fallo es"fail"; en cola/procesando esqueued/handling/archiving).data.outputes un array, cada elemento lleva su propiourlyexpireAt.expireAtes una marca de tiempo Unix entera (no una cadena ISO). Para saber si ha expirado solo mira eso:now >= expireAtsignifica expirado.- El valor de
data.storagees"temp"; esto ya es una pista: es almacenamiento temporal, no para que lo referencies a largo plazo.
¿Cuánto dura la validez? En una muestra medida,
expireAt - completed = 604800segundos (exactamente 7 días). Pero no codifiques «7 días» a fuego en el código: guíate por elexpireAtde cada salida; distintos modelos/estrategias de almacenamiento pueden variar.
Causas habituales (ordenadas por probabilidad de ocurrencia)
- Guardar el hotlink en la base de datos para referencia a largo plazo: al persistir se guarda la URL temporal
temp.hiapi.ai/..., y tras pasarexpireAttodo devuelve 404 en masa. Es el error más común. - No descargar inmediatamente al obtener el estado terminal: se tarda mucho (más que el TTL) en ir a buscar los bytes tras terminar la tarea.
- Creer que volver a sondear «refresca» el enlace: si haces otra GET con el mismo
taskId, devuelve la misma url y el mismoexpireAt; los metadatos persisten, pero el objeto en sí no se regenera tras expirar; volver a hacer GET no te da un enlace nuevo. - Confundir el 404 con un fallo de la tarea: la tarea en realidad ya estaba en
successhace tiempo, solo que el activo de salida ha expirado; son dos cosas distintas.
Pasos de corrección
- En cuanto llegue el estado terminal, descarga los bytes de inmediato. Tras sondear hasta
data.status == "success", haz una GET a cadaoutput[].urlpara bajar el binario enseguida; no lo encoles ni lo aplaces. - Transfiérelo a tu propio almacenamiento de objetos (R2 / S3 / disco local) y sustitúyelo por tu propia URL persistente.
- En la base de datos guarda solo tu propia URL persistente; nunca guardes el hotlink
temp.hiapi.ai. - Antes de descargar, verifica que no ha expirado: descarga solo si
now < expireAt; si ya expiró, pasa directamente a la lógica de reejecución. - Si ha expirado, solo puedes reejecutar la tarea. No existe eso de «cambiar el taskId por un enlace nuevo»; si vuelves a hacer GET del mismo taskId, obtienes la misma url inválida. Para volver a obtener un resultado descargable, solo puedes enviar la tarea de nuevo (se vuelve a facturar).
- Controla el orden de descarga en tareas por lotes: la primera tarea que termina del lote es también la que tiene el
expireAtmás temprano. Descarga en cuanto termine cada una, no esperes a que termine todo el lote para descargar en bloque; de lo contrario, las que terminaron antes pueden estar al borde de expirar.
Ejemplo mínimo de verificación
Confirma que tu key funciona, que la interfaz de tareas devuelve resultados y observa con tus propios ojos el expireAt. Usa el id desnudo del modelo (z-image, no añadas sufijos como /text-to-image, dará 400):
# 1. Enviar la tarea
curl -s https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "z-image",
"input": { "prompt": "a red apple on a wooden table" }
}'
# → {"code":200,"data":{"taskId":"tk-hiapi-..."},"message":"success"}
# 2. Con el taskId del paso anterior, sondear el estado terminal y leer url y expireAt
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-xxxx \
-H "Authorization: Bearer $HIAPI_API_KEY"
# cuando data.status pase a "success", ya tienes data.output[0].url / .expireAt
Si falla la autenticación, devuelve HTTP 401 + {"error":{"code":"permission_denied","type":"hiapi_error","request_id":"..."}}; eso es un problema de la key, no de expiración. Si no encuentra la tarea, devuelve {"code":404,"data":null,"message":"task not found"}; normalmente es que el taskId está mal copiado o se usó la key de otro entorno.
Enlaces relacionados
- Cómo enviar y sondear la interfaz de tareas: Documentación de inicio rápido de HiAPI
- Consultar tu API key y ver el historial de tareas: Consola
- Qué modelos puedes llamar: Mercado de modelos
- Ejemplo de página de un modelo (el usado en la verificación de este artículo): Z-Image texto a imagen
FAQ
P: ¿Qué formato tiene exactamente expireAt?
R: Marca de tiempo Unix entera (segundos), por ejemplo 1792432981, no una cadena ISO. Para saber si ha expirado, usa now >= expireAt.
P: Una vez que el enlace ha expirado, ¿puedo volver a hacer GET de la tarea para «renovar» el enlace?
R: No. Si vuelves a hacer GET del mismo taskId, obtienes la misma url original y el mismo expireAt original; el objeto ya se ha eliminado. Para volver a obtener un resultado descargable, solo puedes reejecutar la tarea (se vuelve a facturar).
P: Tras la expiración, ¿el acceso devuelve 403 o 404?
R: Devuelve HTTP 404, y el body es una página text/html de Cloudflare «Not Found», no bytes de imagen ni un AccessDenied XML. Si tu lógica de descarga solo acepta 200, recuerda tratar «todo lo que no sea 200 como fallo».
P: ¿La validez es siempre de 7 días?
R: En la muestra medida son 7 días (604800 segundos), pero guíate por el expireAt de cada salida; no codifiques los días a fuego en el código.
P: ¿Cuál es la forma más segura de descargar un lote de tareas?
R: Por orden de finalización: lo que primero llega a success es lo primero cuyos bytes se descargan. Las salidas que terminan antes tienen también el expireAt más temprano; dejarlo todo para una descarga en bloque al final hace fácil caer en la proximidad de la expiración.
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