Saltar al contenido principal
Reúne las buenas prácticas sobre preguntas frecuentes, optimización del rendimiento y manejo de errores. Se recomienda leerlo por completo antes de integrar.

Envío de tareas y sondeo

Todos los endpoints de envío son tareas asíncronas: tras el envío devuelven un task_id, luego se consulta periódicamente GET /v1/midjourney/{task_id} para obtener el estado, hasta SUCCESS / FAILURE.
import time, httpx

def wait_task(task_id, timeout=300):
    deadline = time.time() + timeout
    while time.time() < deadline:
        resp = httpx.get(f"{HOST}/v1/midjourney/{task_id}",
                         headers={"Authorization": f"Bearer {API_KEY}"}).json()
        if resp["status"] in ("SUCCESS", "FAILURE"):
            return resp
        if resp["status"] == "MODAL":
            raise RuntimeError(f"task {task_id} necesita llamar a /modal para completar parámetros")
        time.sleep(3)
    raise TimeoutError(task_id)
  • Ritmo de sondeo: se recomienda una vez cada 3–5 s; una frecuencia mayor no aporta nada y desperdicia cuota.
  • No bloquee de forma síncrona dentro de una petición web esperando a que la tarea termine: tras el envío devuelva de inmediato el task_id y deje que el frontend sondee de forma asíncrona.

Diseño de prompts

Un buen prompt:
a serene mountain lake at sunrise, photorealistic, soft golden light,
mist rising from water, snow-capped peaks in distance --ar 16:9 --v 8.1 --s 100
  • El sujeto primero: primero el sujeto, luego la descripción de la escena y al final los modificadores.
  • Parámetros estructurados explícitos: usar --ar / --v / --s (o los campos del body correspondientes) es más controlable que depender de los valores por defecto.
  • Evite palabras ambiguas: photorealistic es más preciso que realistic.
Evite: ser demasiado abstracto (“make it good”), sujetos dispersos (varios objetos en paralelo sin jerarquía) y poner comillas a las palabras (se interpretan como valores literales). Anime Niji: pase niji: true + version: "7"; la plataforma lo normaliza a --niji 7 y la facturación va por midjourney@imagine-niji7.

Buenas prácticas de generación guiada por imagen

OrigenPráctica recomendadaNota
Subida de usuarioGuarde primero en su propio OSS / CDN y envíe esa URLNo envíe base64 directamente (desperdicia ancho de banda)
URL públicaEnvíela directamenteAtención al SSRF (debe ser accesible públicamente) y al límite de 12 MiB
Terceros / otros artefactosGuárdelo primero en su propio OSSLas URLs de terceros pueden caducar
  • Comprima a < 5 MiB: el límite de la plataforma es 12 MiB, pero las imágenes pequeñas se transmiten y procesan más rápido.
  • Se aceptan los formatos PNG / JPG / WebP; se recomienda JPG de alta calidad.
  • Una resolución de 1024–2048 px es suficiente; más es desperdicio.
  • Peso de imagen iw (0–3, por defecto 1): >1 se ajusta más a la imagen original, <1 da más libertad.

Manejo de errores y estrategia de reintentos

codeSignificadoEstrategia de reintento
1 / 200Éxito
4 VALIDATION_ERRORParámetro incorrecto❌ No reintentar, corrija el parámetro
3 NOT_FOUNDSin instancia disponible / task_id inexistenteSi la instancia no está disponible, reintente más tarde; si el task_id no existe, no reintente
9 FAILURERechazo del servicio / error interno⏳ Reintentable, con retroceso exponencial (1s, 4s, 16s)
21 MODALEstado no terminal✅ Continúe llamando a /modal
24 BANNED_PROMPTPalabra sensible❌ No reintentar, cambie el prompt; el reembolso es automático
429Límite de tasa⏳ Retroceso exponencial + jitter
5xx / error de redServidor / red⏳ Retroceso exponencial; ante error de red puede reintentar 1 vez de inmediato
import time, random, httpx

def submit_with_retry(payload, max_attempts=5):
    for attempt in range(max_attempts):
        try:
            r = httpx.post(f"{HOST}/v1/midjourney/generations/imagine",
                           json=payload,
                           headers={"Authorization": f"Bearer {API_KEY}"},
                           timeout=30)
            data = r.json()
            if r.status_code == 200 and data["code"] in (1, 200):
                return data
            if data["code"] in (4, 24):
                raise ValueError(data["description"])      # no reintentable
            if data["code"] == 3 and "task" in data["description"]:
                raise ValueError(data["description"])      # task_id inexistente
            # el resto (9 / 429 / 5xx) es reintentable
        except httpx.RequestError:
            pass
        time.sleep((4 ** attempt) + random.uniform(0, 1))  # 1s / 4s / 16s ...
    raise RuntimeError(f"se alcanzó el número máximo de reintentos {max_attempts}")

Flujo de operaciones secundarias

# imagine → sondeo → upscale
imagine_id = submit({"prompt": "a cat"})["data"][0]["task_id"]
result = wait_task(imagine_id)           # grid_image_url + 4 image_urls + buttons
upscale_id = submit_to("/upscale", {"task_id": imagine_id, "index": 2})["data"][0]["task_id"]
final = wait_task(upscale_id)            # upscale se compone localmente, 1–2s
single_image = final["image_urls"][0]
Redibujado local (inpaint → modal en dos pasos):
imagine_id = submit({"prompt": "a portrait"})["data"][0]["task_id"]; wait_task(imagine_id)
upscale_id = submit_to("/upscale", {"task_id": imagine_id, "index": 1})["data"][0]["task_id"]; wait_task(upscale_id)

inpaint_id = submit_to("/inpaint", {"task_id": upscale_id})["data"][0]["task_id"]  # status=modal
# el frontend dibuja la mask (blanco=zona a redibujar), la sube a su propio OSS y obtiene mask_url
final = submit_to("/modal", {
    "task_id": inpaint_id,
    "prompt": "replace the eyes with cybernetic blue eyes",
    "mask_url": "https://your-oss.com/mask.png"
})
wait_task(final["data"][0]["task_id"])
⚠️ Tras entrar en MODAL, inpaint debe llamar a /modal en un plazo de 30 minutos, de lo contrario el backend hace CANCEL automático + reembolso.

Control de facturación de video

  • Un solo segmento: batch_size: 1 → descuenta 1 × midjourney@video
  • Lote de 4 segmentos: batch_size: 4 → descuenta 4 × midjourney@video
  • Un segmento en alta definición: video_type: "vid_1.1_i2v_720" + batch_size: 1 → descuenta 1 × midjourney@video-720p
Recomendación: si solo necesita 1 segmento para el resultado final, use batch_size=1; reserve 4 únicamente para comparar borradores en lote, no lo deje en 4 por defecto (multiplica el coste por N).

Concurrencia y rendimiento

import asyncio
sem = asyncio.Semaphore(10)  # como máximo 10 envíos concurrentes en el cliente

async def submit_one(prompt):
    async with sem:
        return await submit({"prompt": prompt})
  • La plataforma impone un límite de envíos por minuto; al superarlo devuelve 429 y hay que reintentar con retroceso.
  • La concurrencia real de generación la determina la capacidad del sistema; lo que la supere se pone en cola. Si una tarea permanece mucho tiempo en SUBMITTED, normalmente está en cola.
  • El sondeo debe incluir siempre un sleep; no haga un bucle infinito sin sleep.

Recomendaciones de monitorización

MétricaUmbral de referenciaSignificado
Tasa de SUCCESS (última 1h)> 95%Si es baja indica anomalía del servicio / red
Tiempo medio de finalización< 90sSi es alto indica cola
Número de tareas detenidas en MODALCercano a 0Si es alto indica que el cliente no llamó a /modal
Proporción de code=24< 5%Si es alta indica que el prompt activa palabras sensibles con frecuencia

Lista de resolución de problemas

SíntomaDirección de diagnóstico
Tarea mucho tiempo en SUBMITTEDEn cola en el sistema, consulte más tarde
Tarea mucho tiempo en NOT_STARTLa plataforma la agotará y reembolsará automáticamente más tarde; no requiere intervención manual
Tarea MODAL más de 30 minutosEl cliente no llamó a /modal; ya fue CANCELada automáticamente + reembolsada
Campo prompt vacíoEl resultado de texto de una tarea describe está en el campo description
Falta una imagen en image_urlsLa revisión del sistema bloqueó parte de las imágenes; revise fail_reason
Facturación por encima de lo esperadoRevise el campo quota; en video recuerde × batch_size