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
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_urlsde 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:
image_urlssolo 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.- Los campos adicionales se rechazan, no se ignoran.
resolution,size,n,strength,image_sizeynum_images—habituales en otros modelos de imagen de la plataforma— aquí devuelven400 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
- Página del modelo flux-2-klein-9b/image-to-image: zona de pruebas y precio por imagen.
- Precios de hiapi: tarifas actuales de todos los modelos de imagen.
- Tutorial de seedream-5.0-lite image-to-image: la misma API de tareas, con un esquema que acepta hasta 14 imágenes de referencia en lugar de 1; una buena ilustración de por qué los payloads no se pueden trasladar entre modelos.
- Documentación de la API de tareas asíncronas de hiapi: referencia completa de creación, polling y callbacks.
- Documentación de autenticación de hiapi: configuración de la API key y formato de las cabeceras.
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 →-
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