HiAPI
Blog

Tutorial

Cómo usar flux-2-klein-9b/image-to-image a través de la API de hiapi: curl, Python y una solicitud ejecutable

Jul 30, 2026
Cómo usar flux-2-klein-9b/image-to-image a través de la API de hiapi: curl, Python y una solicitud ejecutable

Este tutorial muestra cómo invocar el modo image-to-image de flux-2-klein-9b a través de la API de tareas unificada de hiapi: la estructura exacta de la solicitud, un comando curl ejecutable, un script de Python que puedes colocar directamente en tu backend, y una peculiaridad del esquema que encontrarás si migras desde otros modelos de la plataforma.

Al terminar tendrás un script que recibe una única URL de imagen de referencia y un prompt de edición, y devuelve una URL de imagen generada que puedes descargar.

Lo que necesitas preparar

  • Una cuenta de hiapi y una API key. Puedes obtenerla en la consola de hiapi.
  • Exactamente una imagen de referencia alojada en una URL HTTPS pública. A diferencia de algunos modelos image-to-image de la plataforma que aceptan varias imágenes de referencia, el array image_urls de flux-2-klein-9b admite como máximo un elemento; consulta los detalles más abajo.
  • Un prompt de texto que describa la edición o transformación.

Todas las solicitudes se envían al endpoint unificado de tareas, con la clave en la cabecera Authorization:

POST https://api.hiapi.ai/v1/tasks
Authorization: Bearer sk-<your-key>
Content-Type: application/json

Estructura de la solicitud (y un campo que suele causar problemas)

El id del modelo es flux-2-klein-9b/image-to-image; pásalo tal cual, exactamente como aparece en /v1/models. Su esquema de input es estricto:

Campo Tipo Obligatorio Descripción
prompt string Sí Instrucción de edición.
image_urls string[] Sí Exactamente una URL de imagen de referencia: tanto minItems como maxItems valen 1.
aspect_ratio string No Uno de estos valores: 1:1, 4:3, 3:4, 16:9, 9:16.
seed integer No Fíjalo para reproducir resultados.
output_format string No Uno de estos valores: jpeg, png, webp.

Antes de escribir código, hay dos cosas que conviene saber:

  1. image_urls solo acepta una URL, no un rango. Si construiste un código de envío compartido en torno a un modelo que acepta de 1 a 14 imágenes de referencia, enviar dos URL aquí será rechazado: maxItems: got 2, want 1. Este modelo realiza edición de una sola imagen, no fusión de múltiples referencias.
  2. Los campos adicionales se rechazan, no se ignoran. resolution, size, n, strength, image_size y num_images —habituales en otros modelos de imagen de la plataforma— aquí devuelven 400 INVALID_REQUEST: additional properties '<field>' not allowed. No reutilices el payload de otro modelo sin comprobar antes el esquema de este.

Ejemplo mínimo ejecutable: curl

Crear una tarea:

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer sk-<your-key>" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "flux-2-klein-9b/image-to-image",
    "input": {
      "prompt": "Change the jacket to charcoal grey leather, keep the pose and background unchanged",
      "image_urls": ["https://your-cdn.example.com/subject.png"],
      "aspect_ratio": "3:4",
      "output_format": "png"
    }
  }'

Tras un envío exitoso se devuelve un id de tarea en data:

{"code": 200, "data": {"taskId": "tk-hiapi-..."}}

Consultar el resultado mediante polling:

curl https://api.hiapi.ai/v1/tasks/tk-hiapi-... \
  -H "Authorization: Bearer sk-<your-key>"

Durante la ejecución, data.status pasa por queued → handling → archiving. Al terminar cambia a "success" y la imagen aparece en data.output:

{
  "code": 200,
  "data": {
    "status": "success",
    "output": [{"url": "https://.../result.png", "expireAt": "..."}]
  }
}

La URL de salida es de corta duración (fíjate en expireAt). Descarga los bytes de inmediato y guárdalos por tu cuenta; nunca hagas hotlinking ni persistas la URL original.

Python para producción: enviar, consultar y descargar

import time
import requests

API_BASE = "https://api.hiapi.ai/v1/tasks"
API_KEY = "sk-<your-key>"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}


def edit_image(prompt: str, image_url: str,
               aspect_ratio: str = "1:1", output_format: str = "png") -> bytes:
    # 1. Crear la tarea: image_urls de este modelo solo acepta exactamente una URL
    resp = requests.post(API_BASE, headers=HEADERS, json={
        "model": "flux-2-klein-9b/image-to-image",
        "input": {
            "prompt": prompt,
            "image_urls": [image_url],
            "aspect_ratio": aspect_ratio,
            "output_format": output_format,
        },
    }, timeout=60)
    body = resp.json()
    if resp.status_code != 200 or not (body.get("data") or {}).get("taskId"):
        raise RuntimeError(f"create failed [{resp.status_code}]: {body}")
    task_id = body["data"]["taskId"]

    # 2. Consultar hasta el estado final
    deadline = time.time() + 600
    while time.time() < deadline:
        task = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS,
                            timeout=30).json().get("data") or {}
        if task.get("status") == "success":
            outputs = task.get("output") or []
            if not outputs or not outputs[0].get("url"):
                raise RuntimeError(f"task {task_id} succeeded but returned no output URL")
            # 3. Descargar de inmediato: la URL caduca
            img = requests.get(outputs[0]["url"], timeout=120)
            img.raise_for_status()
            return img.content
        if task.get("status") == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
        time.sleep(5)
    raise TimeoutError(f"task {task_id} still running after 600s")


if __name__ == "__main__":
    png = edit_image(
        "Change the jacket to charcoal grey leather, keep the pose and background unchanged",
        "https://your-cdn.example.com/subject.png",
        aspect_ratio="3:4",
    )
    with open("edited.png", "wb") as f:
        f.write(png)
    print(f"saved edited.png ({len(png)} bytes)")

Ediciones reproducibles con seed

Pasa un seed entero y el modelo lo reutilizará en esa generación; esto resulta útil cuando quieres hacer pruebas A/B de la redacción del prompt manteniendo todo lo demás igual:

{
  "model": "flux-2-klein-9b/image-to-image",
  "input": {
    "prompt": "Change the jacket to charcoal grey leather, keep the pose and background unchanged",
    "image_urls": ["https://your-cdn.example.com/subject.png"],
    "seed": 42
  }
}

Si lo omites, el modelo elegirá un seed aleatorio en cada llamada, que es justo lo que se quiere en el tráfico normal de producción; cuando necesites resultados con otro aspecto, cambia el prompt, no el seed.

Usar callbacks en lugar de polling

Para scripts y colas de workers, el polling es suficiente. Para backends web, registra un callback al crear la tarea para que hiapi te envíe el estado final: el objeto callback va en el nivel superior de la solicitud, al mismo nivel que model:

{
  "model": "flux-2-klein-9b/image-to-image",
  "input": { "...": "..." },
  "callback": {
    "url": "https://your-app.example.com/hooks/hiapi",
    "when": "final"
  }
}

Con "when": "final" recibirás exactamente un POST cuando la tarea alcance el estado de éxito o de fallo. El handler tiene dos reglas:

  • Hazlo idempotente. Clave tu lógica de procesamiento por el id de tarea para que un webhook reenviado no se procese dos veces. Guarda el id de tarea al enviarla y trata el callback como «ve a buscar y finaliza», no como la única fuente de verdad.
  • Mantén una vía de reconciliación. Si tu endpoint no está disponible cuando se dispara el callback, escanea periódicamente los ids sin resolver y haz GET a /v1/tasks/<id> para detectar cualquier caso perdido.

Si tu callback parece no llegar nunca, revisa una por una por qué no se dispara el callback de tu tarea de hiapi; el artículo cubre los modos de fallo habituales ordenados por probabilidad.

Manejo de errores: tres escenarios de fallo clave

401 permission_denied al crear: tu clave no puede usar este modelo. El cuerpo de la respuesta es:

{"error": {"code": "permission_denied", "message": "...", "request_id": "...", "type": "hiapi_error"}}

Si te encuentras con esto, revisa API key inválida o comprueba los permisos de modelos de la clave en la consola.

400 INVALID_REQUEST al crear: tu input no coincide con el esquema de este modelo. El mensaje señala directamente el campo infractor (por ejemplo, <root>: additional properties 'resolution' not allowed, o image_urls: maxItems: got 2, want 1). Son errores permanentes; reintentar el mismo payload fallará siempre; corrige el campo y vuelve a enviar.

La tarea devuelve status: "fail": la creación fue correcta pero la generación falló (URL de referencia que el backend no puede obtener, políticas de contenido, capacidad del proveedor). Los campos error.code y error.message de la tarea indicarán cuál es. A diferencia del 400, algunos de estos son transitorios y merece la pena reintentar una vez con retroceso exponencial.

También conviene saber: un GET sobre un id de tarea inexistente o caducado devuelve 404 task not found; trátalo como estado final en tus escaneos de reconciliación, no como «todavía en espera».

Adónde ir a continuación

Preguntas frecuentes

¿Puedo pasar varias imágenes de referencia a flux-2-klein-9b image-to-image? No. minItems y maxItems de image_urls están fijados en 1: exactamente una URL, ni más ni menos. Si necesitas fusión de múltiples referencias, busca un modelo cuyo esquema documente un rango más amplio, como seedream-5.0-lite.

¿Por qué recibo «additional properties 'resolution' not allowed»? El esquema de input de la API de tareas es específico de cada modelo. flux-2-klein-9b/image-to-image no acepta en absoluto los campos resolution ni size; el tamaño de salida sigue a la imagen de entrada y a aspect_ratio. Elimina cualquier campo de tamaño que hayas traído del payload de otro modelo.

¿Puedo enviar la imagen de referencia en base64 en lugar de una URL? El esquema de este modelo acepta image_urls: un array de URL. Aloja la imagen en una URL HTTPS accesible (sirve cualquier CDN, bucket de almacenamiento de objetos o URL prefirmada) y pasa esa URL.

¿Qué controla realmente seed? Usar el mismo seed, el mismo prompt y la misma imagen de referencia reproduce el mismo resultado de generación, lo cual es útil para pruebas A/B controladas de prompts. En el tráfico normal omítelo para que cada llamada tenga su propio seed aleatorio.

¿El mismo código sirve para flux-2-klein-9b text-to-image? El endpoint y el flujo son los mismos, pero text-to-image tiene su propio esquema de input (sin image_urls). Cambia el id del modelo a flux-2-klein-9b/text-to-image y elimina la imagen de referencia; luego valida con una llamada de prueba, porque los campos obligatorios varían según el modo.

¿Cuánto cuesta cada imagen? El precio se factura según el uso de cada generación. Consulta las tarifas en vivo en la página de precios.

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