Saltar para o conteúdo principal
Reúne as melhores práticas sobre dúvidas comuns, otimização de desempenho e tratamento de erros. Recomendamos ler tudo antes de integrar.

Submissão de tarefas e polling

Todos os endpoints de submissão são tarefas assíncronas: após a submissão, retornam um task_id, e então você consulta periodicamente GET /v1/midjourney/{task_id} para obter o status, até 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} precisa chamar /modal para completar os parâmetros")
        time.sleep(3)
    raise TimeoutError(task_id)
  • Ritmo do polling: recomendamos a cada 3–5s; frequências maiores não têm sentido e desperdiçam cota.
  • Não bloqueie de forma síncrona dentro de uma requisição web esperando a tarefa terminar —— retorne o task_id imediatamente após a submissão e deixe o frontend fazer polling assíncrono.

Design de prompt

Um bom 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
  • Sujeito primeiro: primeiro o sujeito, depois a descrição da cena e por último os modificadores.
  • Parâmetros estruturados explícitos: usar --ar / --v / --s (ou os campos correspondentes no body) é mais controlável do que depender dos valores padrão.
  • Evite palavras ambíguas: photorealistic é mais claro do que realistic.
Evite: ser abstrato demais (“make it good”), sujeitos dispersos (vários objetos paralelos sem hierarquia), colocar palavras entre aspas (serão tratadas como valor literal). Anime Niji: passe niji: true + version: "7", a plataforma normaliza para --niji 7, e a cobrança vai por midjourney@imagine-niji7.

Melhores práticas para imagem de referência

OrigemPrática recomendadaAtenção
Upload do usuárioSalve primeiro no seu próprio OSS / CDN e envie essa URL na submissãoNão envie base64 diretamente (desperdiça banda)
URL públicaEnvie diretamenteAtenção ao SSRF (deve ser acessível publicamente) e ao limite de 12 MiB
Terceiros / outros artefatosSalve antes no seu próprio OSSURLs de terceiros podem expirar
  • Comprima para < 5 MiB: o limite da plataforma é 12 MiB, mas imagens menores são mais rápidas de transmitir / processar.
  • Os formatos PNG / JPG / WebP são aceitos; recomendamos JPG de alta qualidade.
  • Resolução de 1024–2048 px já é suficiente; mais que isso é desperdício.
  • Peso da imagem de referência iw (0–3, padrão 1): >1 fica mais próximo da imagem original, <1 fica mais livre.

Tratamento de erros e estratégia de retry

codeSignificadoEstratégia de retry
1 / 200Sucesso
4 VALIDATION_ERRORParâmetro errado❌ Não faça retry, corrija os parâmetros
3 NOT_FOUNDNenhuma instância disponível / task_id inexistenteSe a instância estiver indisponível, tente mais tarde; se o task_id não existir, não faça retry
9 FAILURERejeição do serviço / erro interno⏳ Pode fazer retry, com backoff exponencial (1s, 4s, 16s)
21 MODALEstado não terminal✅ Continue chamando /modal
24 BANNED_PROMPTPalavra sensível❌ Não faça retry, altere o prompt; reembolso já automático
429Rate limit⏳ Backoff exponencial + jitter
5xx / erro de redeServidor / rede⏳ Backoff exponencial; em erro de rede, pode fazer retry imediato 1 vez
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"])      # não pode fazer retry
            if data["code"] == 3 and "task" in data["description"]:
                raise ValueError(data["description"])      # task_id inexistente
            # os demais (9 / 429 / 5xx) podem ter retry
        except httpx.RequestError:
            pass
        time.sleep((4 ** attempt) + random.uniform(0, 1))  # 1s / 4s / 16s ...
    raise RuntimeError(f"número máximo de tentativas atingido {max_attempts}")

Fluxo de operações secundárias

# imagine → polling → 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 com composição local, 1–2s
single_image = final["image_urls"][0]
Redesenho local (inpaint → modal, dois passos):
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
# o frontend desenha a mask (branco=área a redesenhar), faz upload no seu próprio OSS e obtém a 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"])
⚠️ Depois que o inpaint entra em MODAL, é obrigatório chamar /modal dentro de 30 minutos, caso contrário o backend faz CANCEL automático + reembolso.

Controle de cobrança de video

  • Trecho único: batch_size: 1 → cobra 1 × midjourney@video
  • Lote de 4 trechos: batch_size: 4 → cobra 4 × midjourney@video
  • Trecho único em HD: video_type: "vid_1.1_i2v_720" + batch_size: 1 → cobra 1 × midjourney@video-720p
Recomendação: se só precisa de 1 trecho final, use batch_size=1; só use 4 para comparar variações em lote. Não deixe 4 como padrão (o custo multiplica por N).

Concorrência e throughput

import asyncio
sem = asyncio.Semaphore(10)  # no máximo 10 submissões concorrentes no cliente

async def submit_one(prompt):
    async with sem:
        return await submit({"prompt": prompt})
  • A plataforma tem um limite de submissões por minuto; ao exceder, retorna 429 e é necessário retry com backoff.
  • A concorrência real de geração é determinada pela capacidade do sistema; ao exceder, entra em fila; uma tarefa parada por muito tempo em SUBMITTED geralmente significa que está na fila.
  • O polling deve sempre incluir sleep; não faça loop infinito sem sleep.

Recomendações de monitoramento

MétricaLimite de referênciaSignificado
Taxa de SUCCESS das tarefas (última 1h)> 95%Se estiver baixa, indica anomalia no serviço / rede
Tempo médio de conclusão< 90sSe estiver alto, indica fila
Número de tarefas paradas em MODALPróximo de 0Se estiver alto, indica que o cliente não chamou /modal
Proporção de code=24< 5%Se estiver alta, indica que o prompt aciona palavras sensíveis com frequência

Checklist de troubleshooting

SintomaDireção de investigação
Tarefa parada em SUBMITTED por muito tempoSistema em fila, consulte mais tarde
Tarefa parada em NOT_START por muito tempoA plataforma fará timeout automático e reembolso mais tarde, não é necessária ação manual
Tarefa em MODAL por mais de 30 minutosO cliente não chamou /modal; já foi feito CANCEL automático + reembolso
Campo prompt vazioO resultado em texto de uma tarefa describe fica no campo description
Falta uma imagem em image_urlsA moderação de conteúdo bloqueou parte das imagens, veja fail_reason
Cobrança acima do esperadoVeja o campo quota; em video, lembre de multiplicar por batch_size