HiAPI
Blog

Tutorial

Cómo llamar a la API de Imagen 4 a través de HiAPI: ejemplos con curl y Python

Oct 31, 2026
博客封面图:淡灰紫色块与墨黑色块以一条笔直硬边相接,交界处一道细紫缝,构图简洁平静。

Qué te ofrece el endpoint de Imagen 4

Si ya tienes un prompt y quieres obtener una imagen sin registrar una cuenta de facturación, sin montar una cola y sin crear un bucket en un proveedor que nunca has usado, el modelo Google-Imagen4 en HiAPI es un punto de partida razonable. Solo envías un prompt de texto a un único endpoint HTTP, te autenticas con una clave de API y obtienes una referencia a la imagen generada (o una tarea que puedes consultar). Todo lo demás —el alojamiento del modelo, la capacidad, los reintentos del lado del proveedor— queda abstraído.

Las llamadas de texto a imagen a través de este endpoint cubren las tres cosas que realmente te importarán en la práctica:

  • Del prompt a la imagen. El prompt es la entrada principal. Según cómo configures la solicitud, el modelo devuelve una o varias imágenes.
  • Control de la forma de salida. Puedes especificar la relación de aspecto en la solicitud (cuadrada, horizontal, vertical) en lugar de recortar después. Esto es más importante de lo que parece: recortar una imagen ya generada descarta la composición que el modelo ya había planificado.
  • Ejecución asíncrona. Las generaciones largas se gestionan como tareas. Envías, consultas el estado y luego obtienes el resultado. Las llamadas cortas pueden devolver rápido, pero deberías escribir tu cliente asumiendo que cada llamada es asíncrona, porque ese es el flujo que aguanta el tráfico de producción.

El resto de este tutorial construye la misma solicitud de tres formas: una llamada curl en bruto que puedes pegar directamente en la terminal, un cliente Python equivalente y, después, las piezas que necesitas para manejar la relación de aspecto, el sondeo, la persistencia y el manejo de errores. Si quieres generar una imagen con un comando de shell de una línea, esto te sirve. Si necesitas control fino sobre máscaras de inpainting o bucles de entrenamiento, puede que no sea lo adecuado.

Hay dos requisitos previos antes de empezar: tienes una clave de API de HiAPI y tu entorno de ejecución puede hacer solicitudes HTTPS salientes. No hace falta nada más —instalar un SDK no es obligatorio, aunque el ejemplo de Python de abajo usa requests.

Haz tu primera solicitud con curl

La forma más rápida de confirmar que tu clave funciona es una llamada curl. El endpoint acepta un cuerpo JSON, así que necesitas dos cabeceras: Content-Type para el cuerpo y Authorization para la clave. Lee la clave de una variable de entorno en lugar de pegarla directamente en el comando, para que no quede en el historial de tu shell.

export HIAPI_API_KEY="your-key-here"

curl -sS -X POST "https://api.hiapi.ai/v1/images/generations" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -d '{
    "model": "google-imagen-4",
    "prompt": "a weathered lighthouse on a basalt cliff, morning fog, wide shot",
    "n": 1,
    "aspect_ratio": "16:9"
  }'

Hay varios puntos que conviene destacar en este cuerpo:

  • model selecciona Google-Imagen4. Escríbelo explícitamente aunque el servicio tenga un valor por defecto —así tu código seguirá siendo legible dentro de un año.
  • prompt es la única entrada creativa obligatoria. Escríbelo como describirías una toma a un fotógrafo: sujeto, entorno, luz, encuadre.
  • n controla cuántas imágenes quieres. Empieza con una mientras ajustas la estructura de la respuesta; auméntalo cuando confíes en tu canalización.
  • aspect_ratio se explica en detalle más adelante. Si lo omites, se usa el valor por defecto del servicio.

Una respuesta correcta devuelve un objeto JSON. La parte que te interesa es la lista de entradas de imagen; cada entrada incluye una referencia descargable o una URL que puedes pasar al frontend. Si pediste varias imágenes, recibirás varias entradas, en el mismo orden que la solicitud. Si la generación tarda lo suficiente como para encolarse, la respuesta te dará un identificador y un estado consultable —ese flujo se describe en su propia sección más abajo.

Si recibes un 4xx, revisa primero las dos cabeceras: que falte el prefijo Bearer o que la clave tenga un espacio en blanco al final son la causa de la mayoría de los fallos en la primera llamada. Para depurar, usa -i para ver la línea de estado y el cuerpo del error.

Haz la misma solicitud con Python

En un script, envuelve la llamada en una función pequeña para que el resto del código nunca toque HTTP en bruto. La función debe recibir el prompt y los parámetros opcionales, construir el cuerpo, enviar la solicitud y devolver el JSON analizado.

import os
import requests

HIAPI_BASE = "https://api.hiapi.ai/v1"


def generate_image(prompt: str, aspect_ratio: str = "1:1", n: int = 1) -> dict:
    response = requests.post(
        f"{HIAPI_BASE}/images/generations",
        headers={
            "Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}",
            "Content-Type": "application/json",
        },
        json={
            "model": "google-imagen-4",
            "prompt": prompt,
            "n": n,
            "aspect_ratio": aspect_ratio,
        },
        timeout=120,
    )
    response.raise_for_status()
    return response.json()


if __name__ == "__main__":
    result = generate_image(
        "a weathered lighthouse on a basalt cliff, morning fog, wide shot",
        aspect_ratio="16:9",
    )
    print(result)

Hay tres decisiones de diseño que merece la pena conservar cuando adaptes este código:

Timeout explícito. requests no tiene timeout por defecto, lo que significa que una conexión colgada puede dejar a un worker suspendido indefinidamente. Elige un valor superior a tu tiempo de generación más lento esperado y haz que falle de forma explícita.

Llama a raise_for_status() antes de analizar. Las respuestas de error no siempre comparten el mismo esquema que las correctas. Comprueba primero el estado HTTP y luego toca el cuerpo de la respuesta.

Función pequeña y parametrizada. Cuando añadas sondeo y descargas, esta función seguirá siendo el único punto de entrada para enviar, con la lógica del ciclo de vida junto a ella, no enredada dentro.

El resultado analizado contiene la misma información que la salida de curl. Para respuestas síncronas, recorre las entradas de imagen y toma las URL. Para respuestas asíncronas, toma el identificador de la tarea y pásalo a la función de sondeo. Tu código debe ramificar según la forma que reciba, no asumir que solo hay una.

Controla la relación de aspecto y la salida

La relación de aspecto es el parámetro que más ajustarás, y merece la pena configurarlo bien en lugar de recortar después. Si solicitas una imagen cuadrada y luego recortas de ella una franja de 16:9, pierdes la parte superior e inferior que el modelo compuso deliberadamente. Pedir 16:9 de antemano permite al modelo planificar la toma para ese formato.

Pásalo en el cuerpo junto con el prompt:

result = generate_image(
    "a sprawling night market, neon signs, steam from food stalls",
    aspect_ratio="16:9",
    n=2,
)

Las proporciones horizontales sirven para banners hero, miniaturas de tarjetas anchas y fotografía de producto; las verticales, para tarjetas mobile-first, pósteres y retratos de personajes; las cuadradas, para avatares y publicaciones sociales donde el recorte es impredecible. Decide según dónde acabará colocada la imagen, no según lo bien que se vea aislada.

Al cambiar la proporción, lo que cambia en la respuesta es la dimensión en píxeles de la imagen devuelta. El número de entradas, el orden y el flujo de descarga no cambian, así que tu código posterior no necesita ramificar según la proporción. Si tu UI reserva espacio antes de que cargue la imagen, lee las dimensiones de la respuesta y aplícalas al elemento, en lugar de codificarlas a mano.

Hay otros dos ajustes de salida que conviene conocer:

  • Número de imágenes (n). Más imágenes por solicitud significa más opciones, pero también gastar más tokens en un prompt que puede estar mal escrito. Mientras iteras la redacción del prompt, pide solo una imagen; cuando el prompt se estabilice, pide en lote.
  • Redacción del prompt para el encuadre. La proporción decide el formato; el prompt decide la composición dentro de ese formato. Palabras como "wide shot", "close-up" y "centered" hacen su trabajo real junto con el parámetro de proporción.

La facturación de este endpoint sigue a la propia generación de imágenes —consulta el panel de precios del sitio para conocer las tarifas actuales, en lugar de codificar nada fijo en tu modelo de costes.

Maneja tareas asíncronas y sondeo

Cuando una generación tarda lo suficiente como para encolarse, el endpoint responde antes de que exista la imagen. Recibes un identificador de tarea, y tu trabajo es consultarlo hasta que alcance un estado final. Escribe este flujo incluso si las llamadas de hoy responden rápido —la capacidad, la complejidad del prompt y el número de imágenes afectan a la latencia, y un cliente construido para lo síncrono se romperá con la primera solicitud lenta.

El ciclo de vida tiene tres pasos:

  1. Envío. Envía la solicitud de generación como de costumbre. Si la respuesta es una tarea, extrae su identificador.
  2. Sondeo. Llama al endpoint de estado de la tarea con ese identificador a intervalos fijos.
  3. Obtención. Cuando el estado indique éxito, lee la referencia de la imagen del payload del resultado.
import time


def wait_for_job(job_id: str, interval: float = 2.0, max_attempts: int = 60) -> dict:
    for attempt in range(max_attempts):
        response = requests.get(
            f"{HIAPI_BASE}/images/jobs/{job_id}",
            headers={"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"},
            timeout=30,
        )
        response.raise_for_status()
        payload = response.json()

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

        time.sleep(interval)

    raise TimeoutError(f"job {job_id} did not finish in time")

Los detalles clave que hacen seguro este código en producción:

  • Limita los intentos en lugar de hacer un bucle infinito. Una tarea que nunca termina debe manifestarse como un error de timeout que puedas alertar, no como un worker colgado para siempre. max_attempts multiplicado por interval es tu presupuesto real de tiempo —ajústalo según tu generación más lenta en condiciones reales.
  • Backoff bajo carga. Un intervalo fijo de dos segundos está bien para uso interactivo. Para cargas de trabajo en lote, aplica backoff gradual o añade jitter, para que los reintentos de muchos workers no se sincronicen en el mismo segundo.
  • Distingue fallo de desconocido. Solo failed y cancelled son fallos finales. Trata cualquier otro estado no exitoso como "sigue procesando" y vuelve a sondear; añadir una rama que lance excepción de inmediato ante estados no reconocidos dará problemas cuando el servicio añada estados nuevos.
  • Reintenta el envío ante errores de red, no el sondeo. Si la llamada de envío expira, reintentar puede crear tareas duplicadas. Si la API admite claves de idempotencia, da una a cada envío; si no, deduplica por tu lado con un hash del prompt más una marca de tiempo.

Cuando una tarea informe éxito, deja de sondear. Persistir el resultado y conservar el identificador de la tarea para auditoría es más barato que seguir preguntando por un estado que ya conoces.

Descarga y almacena las imágenes

La respuesta de generación te da referencias, no bytes de imagen. En la mayoría de los casos, cada entrada de imagen incluye una URL que puedes obtener con una solicitud GET normal. Trata esa URL como una dirección temporal: descárgala pronto, valídala y guarda los bytes donde tú tengas control real.

import pathlib
import uuid


def download_images(payload: dict, out_dir: str = "images") -> list[pathlib.Path]:
    target = pathlib.Path(out_dir)
    target.mkdir(parents=True, exist_ok=True)

    saved = []
    for entry in payload.get("data", []):
        url = entry["url"]
        resp = requests.get(url, timeout=60)
        resp.raise_for_status()

        content_type = resp.headers.get("Content-Type", "")
        if not content_type.startswith("image/"):
            raise ValueError(f"unexpected content type: {content_type}")

        path = target / f"{uuid.uuid4().hex}.png"
        path.write_bytes(resp.content)
        saved.append(path)

    return saved

Haz varias comprobaciones antes de dar una tarea por completada:

  • Tipo de contenido y tamaño. En algunos entornos, una redirección a una página de error devuelve 200 con HTML. Antes de escribir en almacenamiento permanente, verifica que Content-Type empiece por image/ y que el tamaño del cuerpo no sea insignificante.
  • Nombres. Genera un nombre nuevo para cada descarga, en lugar de derivarlo del prompt. Los prompts se repiten, colisionan y contienen caracteres que el sistema de archivos rechaza.
  • Guarda el prompt junto con el archivo. Guarda el prompt, los parámetros y el identificador de la tarea en tu propia base de datos o en un archivo de metadatos lateral. Seis meses después, la imagen sola no te dirá qué solicitud la produjo.
  • Mueve a almacenamiento permanente, no copies. Escribe primero en una ubicación temporal, valida y luego mueve a tu bucket o a almacenamiento respaldado por CDN. Un archivo a medio escribir en almacenamiento permanente es peor que una tarea fallida.

Si tu canalización produce muchas imágenes, separa el paso de descarga del de generación. La generación es la parte cara y limitada por tasa; los reintentos de descarga son baratos y nunca deberían obligarte a regenerar.

Errores comunes y lista de comprobación para depurar

La mayoría de los fallos del primer día caen en unas pocas categorías. Revisa esta lista en orden —cada comprobación es más rápida que la siguiente.

Fallos de autenticación (401, 403)

Clave incorrecta, ausente o mal formateada. Comprueba que el valor de la cabecera sea exactamente Bearer más la clave, sin saltos de línea introducidos al pegar, y confirma que la variable de entorno está realmente exportada en el shell o proceso que ejecuta el código. Las claves rotadas en el panel invalidan inmediatamente las antiguas —si otro servicio acaba de empezar a fallar, pregunta si alguien rotó la clave.

Parámetros rechazados (400, 422)

El cuerpo no coincide con el esquema. Causas habituales: identificador de model mal escrito, formato de cadena de aspect_ratio no aceptado por el endpoint, o n fuera del rango admitido. Imprime el cuerpo del error —normalmente señala el campo problemático. Compara tu JSON con el ejemplo curl que funciona y haz diff campo por campo.

Timeouts

Es fácil confundir dos timeouts distintos. Un timeout del cliente significa que tu llamada HTTP se rindió; la tarea puede seguir ejecutándose. Que una tarea nunca alcance un estado final significa que el presupuesto de sondeo es demasiado pequeño, o que la solicitud realmente falló. Configura el timeout del cliente por encima de tu generación más lenta esperada, y el presupuesto de sondeo aún más alto. Cuando el timeout del cliente se dispara durante el envío, reintentar a ciegas puede duplicar trabajo —comprueba si ya existe una tarea antes de reenviar.

Descargas que parecen correctas pero no lo son

Un estado 200 con cuerpo HTML, o un archivo de cero bytes, normalmente significa que la URL de la imagen ha caducado, o que se obtuvo sin los permisos de acceso correctos. Descarga inmediatamente después de que la tarea tenga éxito y valida el tipo de contenido y el tamaño como se describió arriba.

Límites de tasa (429)

Haz backoff y reintenta, en lugar de apretar el bucle. El backoff exponencial con jitter es la respuesta estándar; limita el número de reintentos y expón los fallos persistentes a tu monitorización.

Un hábito de depuración que merece la pena adoptar

Registra cuatro cosas por solicitud: el identificador del modelo, el cuerpo completo de la solicitud sin la clave, el identificador de la tarea (o el estado de la respuesta en llamadas síncronas) y la duración. Cuando algo falla en producción, estos cuatro campos suelen bastar para determinar en menos de un minuto si el problema está en tu cliente, en tus parámetros o en la cola.

Empieza con la llamada curl, obtén una imagen y luego mueve la lógica a un cliente Python, añadiendo sondeo y persistencia a su alrededor. Ese camino mantiene cada fallo pequeño en alcance y atribuible.

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