HiAPI
Blog

Tutorial

Cómo usar kling-4.0/text-to-video a través de la API de HiAPI: curl, Python y un ejemplo de solicitud ejecutable

Guía práctica para desarrolladores: prompts cinematográficos, audio sincronizado, sondeo asíncrono y descarga de resultados

Nov 3, 2026
深炭底色上一条宽大的琥珀色光带斜扫而过,下方配一条细窄的暗棕平行条带,整体用于文章封面。

Qué hace kling-4.0/text-to-video y para qué sirve

kling-4.0/text-to-video es un modelo de video generativo que se ofrece a través de la API unificada de HiAPI. Le proporcionas un prompt de texto y devuelve un clip de video: el movimiento, el movimiento de cámara y la composición de la escena quedan determinados por tu descripción. Es un endpoint de texto a video: no hay imagen de origen que animar, por lo que el modelo tiene total autonomía sobre el encuadre y la apariencia del sujeto.

Casos de uso:

  • Necesitas generar clips cinematográficos cortos a partir de un guion o de una plantilla de prompts: b-roll, tráilers, concept boards, videos para redes sociales.
  • Quieres que se genere audio sincronizado junto con el video, en lugar de obtener un archivo silencioso al que luego haya que añadir música en posproducción.
  • Estás construyendo un pipeline en el que la generación de video es solo uno de los muchos pasos (escritura de guiones, preparación de material, publicación), por lo que necesitas una API HTTP y no una interfaz web.

Sin embargo, este modelo no es adecuado si necesitas control preciso fotograma a fotograma sobre un sujeto real concreto (los modelos de imagen a video o de edición rinden mejor en eso), ni si necesitas obtener una respuesta síncrona en la solicitud del usuario: la generación de video lleva tiempo, por lo que este endpoint es asíncrono por diseño.

Como todas las solicitudes pasan por HiAPI, las credenciales, el formato de solicitud/respuesta y el ciclo de vida de las tareas son los mismos sin importar qué modelo de la plataforma uses. Esa consistencia es la razón práctica para construir sobre ella: añadir o cambiar de modelo más adelante no exige reescribir la capa de autenticación ni la de sondeo.

Requisitos previos y autenticación en HiAPI

Antes de enviar tu primera solicitud, asegúrate de tener:

  • Una cuenta de HiAPI y una clave de API de proyecto. Trátala como confidencial: nunca la confirmes en un repositorio de código ni la incluyas en código de cliente.
  • El cliente HTTP que prefieras. Los ejemplos siguientes usan curl y requests de Python 3.
  • El identificador del modelo, exactamente como aparece en el catálogo: kling-4.0/text-to-video. Un error tipográfico en el nombre del modelo es la causa más común de fallo en la primera llamada.

Las credenciales se envían como cabecera HTTP en cada solicitud (incluidas las de sondeo). El patrón habitual en HiAPI es el bearer token:

export HIAPI_API_KEY="your-api-key-here"

Y luego, en la solicitud:

Authorization: Bearer $HIAPI_API_KEY
Content-Type: application/json

Guarda la URL base y la clave en variables de entorno o en un gestor de secretos, no codificadas directamente. Para desarrollo local, un archivo .env junto con consultas a os.environ en Python es suficiente. Para servicios desplegados, inyecta la clave en tiempo de ejecución.

Un hábito de configuración que conviene adoptar cuanto antes: crea primero una pequeña llamada del tipo «quién soy» —cualquier solicitud de autenticación ligera— para confirmar que la clave y la URL base son correctas antes de depurar tareas de video. Aislar la autenticación de la lógica del modelo ahorra tiempo más adelante.

Una solicitud curl ejecutable

La llamada mínima crea una tarea y devuelve el identificador de la tarea. La estructura es la siguiente:

curl -X POST "$HIAPI_BASE_URL/video/generations" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kling-4.0/text-to-video",
    "prompt": "A slow aerial push over a rain-soaked Tokyo alley at night, neon signs reflecting in puddles, steam rising from vents, shallow depth of field, cinematic color grading",
    "generate_audio": true
  }'

Desglose campo por campo del cuerpo de la solicitud:

  • model — el identificador del modelo. Obligatorio.
  • prompt — la descripción de tu escena. Aquí se concentra la mayor parte de tu presupuesto de calidad; consulta la sección de prompts.
  • generate_audio — solicita que se genere audio sincronizado junto con el video. Si solo necesitas la pista visual, omítelo o ponlo en false.

La respuesta es un objeto de tarea, no el video terminado. Espera un campo de estado y un id que usarás para el sondeo; por ejemplo, un estado queued o processing, más un id de tarea. Guarda ese id de inmediato; si tu proceso se interrumpe, ese id es tu única credencial del trabajo en curso.

Dos consejos prácticos. Primero, cuando el prompt se alargue, guarda el JSON en un archivo y usa -d @payload.json: el manejo de comillas del shell con prompts multilínea es una fuente habitual de errores. Segundo, captura por separado el cuerpo de la respuesta y el código de estado HTTP (curl -s -w "\n%{http_code}") para distinguir una solicitud rechazada de un fallo de red.

Convertir la solicitud a Python

La misma solicitud escrita en Python, con una estructura que puedes incorporar directamente a un script real:

import os
import requests

BASE_URL = os.environ["HIAPI_BASE_URL"].rstrip("/")
API_KEY = os.environ["HIAPI_API_KEY"]

HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

def create_video_job(prompt: str, generate_audio: bool = True) -> dict:
    payload = {
        "model": "kling-4.0/text-to-video",
        "prompt": prompt,
        "generate_audio": generate_audio,
    }
    resp = requests.post(
        f"{BASE_URL}/video/generations",
        headers=HEADERS,
        json=payload,
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()

if __name__ == "__main__":
    job = create_video_job(
        "A slow aerial push over a rain-soaked Tokyo alley at night, "
        "neon signs reflecting in puddles, steam rising from vents, "
        "shallow depth of field, cinematic color grading"
    )
    print(job)

Por qué está escrito así:

  • requests.post(..., json=payload) se encarga a la vez de la serialización y del tipo de contenido, lo que evita usar json.dumps manualmente.
  • raise_for_status() convierte las respuestas 4xx/5xx en excepciones capturables, en lugar de analizar silenciosamente un cuerpo de error como si fuera una tarea.
  • timeout es obligatorio. Sin él, una conexión atascada bloquea al worker de forma indefinida.
  • La configuración está en constantes a nivel de módulo leídas del entorno, de modo que la función es comprobable y reutilizable.

Esta función solo crea la tarea. Devuelve de inmediato el identificador de la tarea, que es justo lo que quieres en un manejador web o en un worker de cola. Las secciones siguientes explican cómo esperar el resultado.

Escribir prompts cinematográficos que resistan la prueba

Los modelos de texto a video premian la estructura. Un prompt que especifica sujeto, cámara, movimiento y emoción siempre supera a una idea de una sola frase, porque cada elemento elimina ambigüedad que el modelo, de otro modo, resolvería al azar.

Una plantilla útil:

[sujeto y escena], [posición y movimiento de cámara],
[movimiento dentro del plano], [iluminación], [atmósfera o etalonaje], [referencia de estilo]

Ejemplo aplicado:

Al amanecer, un ciclista solitario con impermeable amarillo cruza un puente de piedra, cámara baja que lo sigue en paralelo a la altura de las ruedas, niebla flotando sobre el agua, luz suave de hora azul con toques cálidos de faroles, sereno y ligeramente melancólico, textura de lente anamórfica de pantalla ancha.

La función de cada parte:

  • Sujeto y escena — los sustantivos concretos superan a los conceptos abstractos. «Un ciclista solitario con impermeable amarillo» le da al modelo algo que renderizar; «libertad» no.
  • Cámara — la posición (cámara baja, aérea, a la altura de los ojos) y el movimiento (seguimiento, acercamiento lento, estático) son la palanca más potente para el valor de producción percibido.
  • Movimiento dentro del plano — la niebla flotando, la tela ondulando, las ondas en el agua. El video necesita movimiento; dilo explícitamente.
  • Iluminación y atmósfera — hora, temperatura de color, tono emocional. Mantenlos coherentes entre sí.
  • Estilo — el lenguaje de lente o de formato (lente anamórfica, profundidad de campo reducida, etalonaje cinematográfico) moldea la textura de forma más fiable que nombrar a un director.

Mantén el prompt como una descripción densa de unas pocas frases, no como un párrafo de ideas que compiten entre sí. Si un plano necesita dos acciones claramente distintas, genera dos clips. E itera: cambia una sola variable a la vez —primero la cámara, luego la iluminación— para que descubras a qué responde el modelo en lugar de adivinar.

Gestionar tareas asíncronas y el sondeo

La generación de video no es instantánea, así que el patrón es crear primero, sondear después. Ya tienes la parte de creación; este es el bucle:

import time

def wait_for_job(job_id: str, timeout: int = 600) -> dict:
    deadline = time.monotonic() + timeout
    delay = 2.0
    while time.monotonic() < deadline:
        resp = requests.get(
            f"{BASE_URL}/video/generations/{job_id}",
            headers=HEADERS,
            timeout=30,
        )
        resp.raise_for_status()
        job = resp.json()
        status = job.get("status")

        if status == "succeeded":
            return job
        if status in ("failed", "cancelled"):
            raise RuntimeError(f"Job {job_id} ended with status {status}: {job}")

        time.sleep(delay)
        delay = min(delay * 1.5, 15.0)  # back off, cap at 15s

    raise TimeoutError(f"Job {job_id} did not finish in {timeout}s")

Decisiones de diseño que importan en producción:

Sondear con retroceso, no en bucle apretado

Empieza con intervalos cortos y auméntalos de forma gradual: el retroceso exponencial anterior tiene un tope fijo. Sondear de forma continua y rápida consume tu límite de tasa sin ningún beneficio, porque el tiempo de generación no se acorta por preguntar antes.

Fija siempre una fecha límite

Todo bucle de sondeo necesita un tiempo máximo de reloj y un error explícito al superarlo. De lo contrario, una tarea atascada ocupa un worker para siempre y sin avisar.

Trata los estados terminales como definitivos

Distingue los estados en curso (queued, processing) de los terminales (succeeded, failed, cancelled). Devuelve la salida solo en caso de éxito; en los demás casos, lanza una excepción o pasa a una ruta de reintento en lugar de seguir en el bucle.

Desacopla el sondeo del manejador de solicitudes

En un servidor web, no hagas que la respuesta HTTP se bloquee esperando el video. Crea la tarea, devuelve el id al cliente y realiza el sondeo desde un worker en segundo plano o un consumidor de cola. Persiste el id de la tarea y su último estado conocido para que, tras un reinicio, no pierdas el seguimiento del trabajo en curso.

Descargar y verificar la salida

Cuando la tarea alcanza succeeded, la respuesta incluye una referencia al medio generado, normalmente una URL al archivo terminado. Descárgalo cuanto antes y trata esa URL como temporal: obtén, almacena y registra tu propia ubicación persistente, en lugar de pasar la URL del proveedor directamente al usuario final.

import pathlib

def download_output(job: dict, dest: pathlib.Path) -> pathlib.Path:
    url = job["output"]["video_url"]
    with requests.get(url, stream=True, timeout=120) as r:
        r.raise_for_status()
        dest.parent.mkdir(parents=True, exist_ok=True)
        with open(dest, "wb") as f:
            for chunk in r.iter_content(chunk_size=1 << 20):
                f.write(chunk)
    return dest

Pasos de verificación que merece la pena hacer antes de entregar el archivo a nadie:

  • Comprueba que la transferencia se completó. Transmite por bloques, compara el tamaño escrito con Content-Length (si existe) y falla de forma ruidosa si se leyó menos de lo esperado.
  • Confirma la pista de audio. Si solicitaste generate_audio, el archivo debe tener un flujo de audio. Una sonda rápida con ffprobe es la comprobación más barata:
ffprobe -v error -show_entries stream=codec_type -of csv=p=0 output.mp4

Deberías ver tanto la línea video como la línea audio. Si falta el audio, vuelve a comprobar si el flag se estableció en la llamada de creación: no se puede añadir a posteriori.

  • Haz una validación básica de duración y resolución frente a lo que esperabas, para detectar antes de publicar renders truncados o con especificaciones incorrectas.
  • Almacena con claves deterministas (id de tarea más hash), escribe primero en una ruta temporal, renombra tras el éxito y conserva el id de la tarea junto al recurso para poder rastrearlo.

Si la descarga falla a mitad de la transferencia, reinténtala con retroceso; si la URL ha caducado, tendrás que volver a obtener el registro de la tarea para conseguir una referencia nueva.

Encapsularlo como cliente reutilizable

Las piezas anteriores encajan de forma natural en una clase. Al unirlas obtienes algo que puedes importar entre proyectos sin duplicar código:

class KlingTextToVideo:
    def __init__(self, base_url: str, api_key: str):
        self.base_url = base_url.rstrip("/")
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        }

    def generate(self, prompt: str, generate_audio: bool = True,
                 timeout: int = 600) -> dict:
        job = create_video_job(prompt, generate_audio)  # create half
        return wait_for_job(job["id"], timeout=timeout)  # poll half

A medida que el cliente madure, hay varias cosas que añadir:

  • Registro estructurado con el id de tarea como clave: transiciones de estado, número de sondeos, tiempo transcurrido y fallos. Cuando un render se comporte de forma extraña, el id de tarea es la clave de correlación entre tus logs.
  • Reintentos acotados solo para errores transitorios (red, 5xx). Nunca reintentes a ciegas una solicitud rechazada; un cuerpo de solicitud mal formado fallará igual todas las veces y solo desperdiciará cuota.
  • Implementa idempotencia en tu capa. Si tu servicio puede enviar dos veces el mismo prompt, deduplica antes de llamar a la creación, en lugar de pagar por dos tareas.
  • Trata las plantillas de prompts como datos, no como código. Guarda la plantilla cinematográfica en un archivo de configuración para que iterar sobre los prompts no requiera un despliegue.
  • Contabiliza el coste por segundos de salida. La facturación se basa en el uso de salida generado, así que registra la duración generada por tarea para poder atribuir el gasto a una función o a un usuario.

Con el cliente listo, el pipeline es corto: construye el prompt a partir de la plantilla, llama a generate, descarga y sondea la salida y luego entrega el archivo almacenado a tu aplicación. Si más adelante añades otros modelos en HiAPI, las mismas cabeceras de autenticación, el mismo ciclo de vida de tareas y el mismo patrón de descarga siguen aplicándose: esa es la razón principal para construir la capa de abstracción una sola vez.

Artículos relacionados

Ver todos los artículos →

Empieza a crear con artefactos de IA persistentes

Consigue tu API key y llama a modelos de imagen, vídeo y audio a través de una única API basada en tasks.

Leer la documentación de la API