Crea una imagen, apruébala y luego anímala en un clip corto. Estos pasos siguen nuestra prueba de la taza azul de octubre de 2026. Esta guía utiliza una imagen fija de Z-Image por 0,010 $ y cinco segundos de MiniMax H3 a 768p por 0,185 $: 0,195 $ por un intento en cada etapa, sin expansión del prompt en la imagen fija. Prueba primero la misma escena en Playground; cuando funcionen las entradas, usa el ejemplo de Python o deja las llamadas en manos de un agente.
El resultado deseado es concreto: una taza bien formada sobre un banco de trabajo, vapor desplazándose a la derecha y un acercamiento lento de cámara que termina detenido. No necesitas desplegar un modelo ni configurar un servidor GPU.
Prueba el caso completo en Playground
Crea tu cuenta y añade saldo, y verifica tu correo. Playground gestiona la solicitud; solo necesitas una clave API para el código y las herramientas de agente que aparecen más abajo.
1. Genera la imagen inicial
Abre Z-Image de texto a imagen y utiliza su Playground. Pega este prompt:
Photograph of a handmade blue ceramic mug of tea on a worn wooden workbench in a pottery studio, a thin curl of steam rising from the mug. Soft window light from the left, shallow depth of field, shelves of pots softly out of focus behind. The mug sits left of centre with empty bench to its right. Natural colours, no text, no logos.
Elige 16:9, PNG, seed 42, y desactiva la expansión del prompt. Comprueba el precio mostrado y pulsa Run model. La imagen básica cuesta 0,010 $; con expansión subiría a 0,016 $. Mantén seed 42 fijo mientras comparas cambios en el prompt.
Abre el resultado antes de continuar. Comprueba:
- Objeto: una taza, un asa y una geometría cerámica limpia.
- Posibilidad de movimiento: vapor visible y espacio a la derecha para que se desplace.
- Composición: la taza queda a la izquierda del centro; las estanterías siguen como fondo discreto.
- Detalles no deseados: sin letras sueltas, logotipos ni tazas adicionales.
Descarga el PNG aprobado como image.png. Si la taza está mal, cambia una frase y genera otra imagen antes de gastar en vídeo. Cada imagen básica nueva añade 0,010 $.
2. Anima la imagen aprobada
Abre MiniMax H3 de imagen a vídeo, añade image.png como Input image y pega en Prompt:
Steam rises from the mug and drifts to the right. The camera pushes in slowly toward the mug, then holds. The mug, bench and shelves stay where they are. Quiet room, a faint ceramic clink.
Ajusta Duration a 5 segundos y Resolution a 768p; comprueba el precio y pulsa Run model. Este clip cuesta 0,185 $; elegir 2k costaría 0,290 $.
El prompt separa movimiento del vapor, movimiento de cámara y sonido. Pide que la taza permanezca en su sitio en vez de volver a describirla. Si la taza recorre el banco, simplifica la indicación de cámara en lugar de añadir acciones al objeto. Un acercamiento lento desplaza la cámara; no pide que se mueva la taza.
3. Revisa el clip y elige la siguiente toma
Reproduce todo el resultado con sonido. Comprueba que la taza no se deslice ni cambie de forma, que la cámara se acerque y luego se detenga, que no haya cortes no deseados y que el audio encaje con un estudio tranquilo. La entrega esperada es un MP4 de unos cinco segundos con pista de audio; que el archivo se reproduzca no basta para que la toma sea útil.
Si la imagen estaba bien pero el movimiento no, conserva la imagen inicial aprobada y modifica el prompt de movimiento. Si la imagen inicial estaba mal, sustitúyela primero. H3 no tiene seed: generar a 2k produce una toma nueva, que puede moverse de otra manera que la versión de 768p que te gustó.
Qué produjo la prueba de la taza azul
La imagen fija costó 0,010 $ y llegó como PNG de 1280 × 720. La solicitud de vídeo de cinco segundos costó 0,125 $ (a 480p, que ya no se ofrece; el mismo clip a 768p cuesta 0,185 $) y devolvió un MP4 de 832 × 480 con pista de audio. El total fue 0,135 $; a 768p sería 0,195 $.
La taza permaneció en el banco mientras el vapor subía y se desplazaba a la derecha; el acercamiento de cámara fue sutil. La imagen situó la taza solo ligeramente a la izquierda del centro y el vapor se difuminó cerca de la estantería al final. Para una toma de producto más cerrada, especifica mejor la posición de la taza en el prompt de imagen antes de animarla.
Por qué estos dos modelos encajan en el primer intento
Z-Image ofrece una imagen económica para definir el sujeto y la composición. H3 acepta esa imagen y genera movimiento y banda sonora. Sus entradas son distintas:
| Z-Image de texto a imagen | H3 de imagen a vídeo | |
|---|---|---|
| ID del modelo | alibaba/z-image-turbo/text-to-image | minimax/h3/image-to-video |
| Entrada obligatoria | prompt (de 1 a 1.000 caracteres) | prompt (hasta 7.000) e image_url |
| Ajustes usados aquí | aspect_ratio (1:1, 4:3, 3:4, 16:9 o 9:16), output_format (solo png), seed, enable_prompt_expansion | duration_seconds (de 4 a 15), resolution (768p o 2k) |
| No acepta | ningún otro formato de salida | seed y aspect_ratio: la imagen inicial determina la forma |
| Devuelve | un PNG | un MP4 con banda sonora generada |
| Precio | 0,010 $ por imagen | 0,037 $ por segundo a 768p |
Usa una imagen inicial PNG, JPEG, WebP o GIF, con cada lado entre 256 y 5.760 píxeles, una relación de aspecto entre 2:5 y 5:2 y un archivo de menos de 30 MB. Subir un PNG es la entrada más sencilla. Si usas un enlace público de imagen, debe devolver la imagen real; una página para compartir de un servicio en la nube no sirve como entrada de imagen.
Ejecuta el mismo caso desde Python
Usa Python 3.11 o posterior, instala el paquete oficial con python -m pip install spicyapi y ten disponible la utilidad file. Configura SPICY_API_KEY en tu terminal, guarda esto como blue-mug.py y ejecuta python blue-mug.py. Pide aprobación en ambas etapas de pago y se detiene para que revises la imagen. Conserva la carpeta de ejecución generada al reanudar; elige un nuevo RUN_NAME solo para un intento nuevo deliberado. Consulta el contrato API y las opciones SDK en la documentación del SDK de Python.
import hashlib
import json
import os
from pathlib import Path
import subprocess
import tempfile
import time
import urllib.request
import uuid
from spicyapi import SpicyClient, output_assets
# Change RUN_NAME for a deliberately new pair of paid generations.
folder = Path(os.getenv("RUN_NAME", "blue-mug-1"))
folder.mkdir(exist_ok=True, mode=0o700)
state_file = folder / "state.json"
state = json.loads(state_file.read_text()) if state_file.exists() else {}
identity = hashlib.sha256(os.environ["SPICY_API_KEY"].encode()).hexdigest()
if state.get("identity", identity) != identity:
raise RuntimeError("This saved run belongs to a different API key.")
state["identity"] = identity
client = SpicyClient()
def save() -> None:
temp = state_file.with_suffix(".tmp")
temp.write_text(json.dumps(state, indent=2))
temp.chmod(0o600)
temp.replace(state_file)
def run(stage: str, model: str, inputs: dict) -> dict:
request = {"model": model, "input": inputs}
if stage not in state:
state[stage] = {"request": request, "key": str(uuid.uuid4())}
saved = state[stage]
if saved["request"] != request:
raise RuntimeError("Input changed: choose a new RUN_NAME, not a new retry key.")
save() # The same request and key survive a lost create response.
if not saved.get("taskId"):
if "first_sent" in saved and time.time() - saved["first_sent"] >= 23 * 3600:
raise RuntimeError("Unknown acceptance: reconcile task history before retrying.")
if "quote" not in saved:
quote = client.quote_task(model=model, input_data=inputs)
if input(f"{stage}: quoted ${quote['estimatedCost']}. Create? [y/N] ") != "y":
raise SystemExit("No task created for this stage.")
saved["quote"] = quote
saved.setdefault("first_sent", time.time())
save()
quote = saved["quote"]
task = client.create_task(model=model, input_data=inputs,
idempotency_key=saved["key"],
quote_id=quote["quoteId"],
expected_cost=quote["estimatedCost"])
saved["taskId"] = task["taskId"]
save()
task_id = saved["taskId"]
print(stage, "task:", task_id)
deadline = time.monotonic() + 1200
while time.monotonic() < deadline:
task = client.get_task(task_id)
if task["state"] in {"failed", "canceled", "expired"}:
raise RuntimeError(f"{task_id}: {task.get('errorCode')} {task.get('errorMessage')}")
if task["state"] == "succeeded":
assets = output_assets(task)
if not assets or assets[0].get("unavailable"):
raise RuntimeError(f"{task_id}: no retrievable output; keep the task ID.")
asset = assets[0]
if not asset.get("pending") and asset.get("url"):
print("Charge:", task.get("cost"), "settled:", task.get("settled"))
return asset
time.sleep(5)
raise TimeoutError(f"Resume {task_id} by rerunning this file; do not start another task.")
def download(asset: dict, destination: Path, mime: str) -> None:
temporary = None
try:
request = urllib.request.Request(asset["url"], headers={"User-Agent": "SpicyAPI-Blog-Example/1.0"})
with urllib.request.urlopen(request, timeout=300) as response:
if response.status != 200 or response.headers.get_content_type() != mime:
raise RuntimeError("Unexpected download status or content type.")
with tempfile.NamedTemporaryFile(dir=folder, delete=False) as handle:
temporary = Path(handle.name)
while chunk := response.read(1024 * 1024):
handle.write(chunk)
size = temporary.stat().st_size
expected = int(asset.get("bytes") or 0)
detected = subprocess.check_output(["file", "-b", "--mime-type", str(temporary)], text=True).strip()
if not size or (expected and size != expected) or detected != mime:
raise RuntimeError("Incomplete or incorrectly formatted download.")
temporary.replace(destination) # An interrupted transfer leaves the old file alone.
finally:
if temporary is not None:
temporary.unlink(missing_ok=True)
image_input = {
"prompt": (
'Photograph of a handmade blue ceramic mug of tea on a worn wooden workbench '
'in a pottery studio, a thin curl of steam rising from the mug. Soft window '
'light from the left, shallow depth of field, shelves of pots softly out of '
'focus behind. The mug sits left of centre with empty bench to its right. '
'Natural colours, no text, no logos.'
),
"aspect_ratio": "16:9",
"output_format": "png",
"seed": 42,
"enable_prompt_expansion": False,
}
image_asset = run("image", "alibaba/z-image-turbo/text-to-image", image_input)
download(image_asset, folder / "image.png", "image/png")
if input(f"Open {folder / 'image.png'}. Is the mug/frame approved? [y/N] ") != "y":
raise SystemExit("Stopped before video generation.")
if not state.get("image_uri"):
state["image_uri"] = client.upload_file(str(folder / "image.png"))["uri"]
save()
video_input = {"image_url": state["image_uri"], "prompt": 'Steam rises from the mug and drifts to the right. The camera pushes in slowly toward the mug, then holds. The mug, bench and shelves stay where they are. Quiet room, a faint ceramic clink.',
"duration_seconds": 5, "resolution": "768p"}
video_asset = run("video", "minimax/h3/image-to-video", video_input)
download(video_asset, folder / "video.mp4", "video/mp4")
print("Play", folder / "video.mp4", "with sound and review it before using it.")Guarda la clave API en tu servidor o en un equipo que controles, nunca en una página pública ni en una aplicación móvil. Las páginas de modelo anteriores siguen siendo el lugar donde comprobar disponibilidad y entradas admitidas antes de integrar otro modelo.
Deja que un agente haga las llamadas cuando sea más fácil
Usa un Agent si quieres que prepare las entradas y devuelva el resultado mientras tú apruebas el precio y las decisiones creativas. Revisa personalmente la imagen inicial antes de autorizar el vídeo.
- CLI: guarda el mismo objeto de entrada como
input.jsony usanpx @spicyapi/cli tasks create --model alibaba/z-image-turbo/text-to-image --input-file input.json --wait. La CLI confirma el precio; conserva el ID de tarea devuelto para recuperarla. Para el clip, sube la imagen aprobada connpx @spicyapi/cli files upload image.pngy creaminimax/h3/image-to-videodel mismo modo, usando el URIspicy://devuelto comoimage_url. - MCP: pide al asistente que use
spicyapi_task_createparaalibaba/z-image-turbo/text-to-imagey luegospicyapi_task_wait; para el clip, añadespicyapi_upload_filepara tuimage.pngaprobado y luegospicyapi_task_createconminimax/h3/image-to-video, 5 segundos, 768p y el prompt de movimiento anterior. El servidor MCP solicita confirmación del precio antes de crear la tarea. - Skill: instala con
npx skills add spicyapi-ai/spicy-skilly pide a$spicyapique ejecute el mismo flujo y se detenga para tu aprobación. El Skill usa una conexión CLI o MCP configurada.
Presupuesta los intentos, no solo el clip final
Estas estimaciones están en dólares estadounidenses. Confirma el precio vigente antes de generar.
| Solicitud | Precio estimado |
|---|---|
| Una imagen fija de Z-Image, PNG 16:9 | 0,010 $ |
| La misma imagen con expansión del prompt | 0,016 $ |
| H3, 4 s a 768p | 0,148 $ |
H3, 5 s a 768p (valor predeterminado si se omite resolution) | 0,185 $ |
| H3, 5 s a 2k | 0,290 $ |
| H3, 10 s a 768p | 0,370 $ |
| H3, 15 s a 2k | 0,870 $ |
- Duración y resolución: H3 cuesta 0,037 $ o 0,058 $ por segundo a 768p o 2k.
- Combinaciones no admitidas: un clip de 4 segundos a 2k se rechaza con el error
40004. - Dos borradores: una imagen fija y dos vídeos de cinco segundos a 768p cuestan 0,380 $.
- Borrador y toma de entrega: una imagen fija, una comprobación de cinco segundos a 768p y una toma de cinco segundos a 2k cuestan 0,010 $ + 0,185 $ + 0,290 $ = 0,485 $, reutilizando la imagen aprobada.
El dinero se retiene al aceptar una tarea y se liquida cuando termina. Las tareas fallidas y caducadas normalmente liberan la retención; ciertos rechazos de contenido se cobran según la política indicada del modelo. Comprueba el cost real y el estado settled de la tarea al investigar un cobro.
Corrige el problema en la etapa adecuada
| Síntoma | Qué cambiar |
|---|---|
| La taza tiene dos asas o texto suelto | Modifica el prompt de imagen y aprueba una nueva imagen inicial antes de generar vídeo |
| La taza se mueve cuando querías mover la cámara | Conserva la imagen; pide un movimiento de cámara y que la taza permanezca expresamente inmóvil |
| El vídeo queda bien a 768p pero la nueva toma de 2k difiere | Trátala como una toma nueva y revisa el movimiento antes de aceptarla |
| El enlace de imagen falla | Sube el archivo o usa una URL de imagen directa y accesible en vez de una página para compartir |
La cuenta devuelve 401 o 40310 | Comprueba la clave o completa la verificación del correo |
40301 / 40302 / 40303 | Comprueba permisos del modelo, lista de IP permitidas de la clave o disponibilidad regional |
40201 / 40202 | Comprueba saldo disponible o límite de gasto de cuenta/clave; conserva la solicitud guardada al conciliar un envío fallido |
Una solicitud rechaza seed, aspect_ratio u output_format | Comprueba las entradas admitidas por la tarea seleccionada; no copies campos de Z-Image en H3 |
40004 | Cambia la combinación incompatible: 4 segundos a 2k |
40901 o conflicto de idempotencia | Solicita otra cotización: si no se guardó un ID de tarea, elimina la entrada de esa etapa en state.json y vuelve a ejecutar. Si el intento anterior pudo haberse aceptado, revisa tu lista de tareas antes de crear nada más; consulta la referencia de errores |
429 o error de conexión | Conserva la carpeta de ejecución y el ID de tarea; no inicies otro intento de pago solo para recuperar el resultado |
Una tarea indica content_rejected o timeout | Lee el error y el cobro, y elige deliberadamente un prompt cambiado, otra imagen fuente o una toma más corta |
| La generación terminó, pero el resultado no está listo | Reanuda la misma tarea guardada; no generes otro clip para conseguir una descarga |
Mantén pequeño el primer flujo de trabajo
Cuando la toma funcione, decide su duración y resolución de entrega; después genera y revisa esa toma. Una imagen final opcional puede orientar el cierre mediante last_image_url de H3; no garantiza un último fotograma exacto ni el movimiento entre fotogramas.
Para una aplicación, conserva ambos ID de tarea y guarda los archivos aprobados en tu propio almacenamiento. Usa sondeo frente a Webhooks para elegir cómo recoger resultados, almacenamiento de medios para conservar archivos y generación por lotes cuando necesites muchas tomas. El primer hito sigue siendo el mismo: una imagen aprobada y un clip que realmente siga la toma prevista.







