API do Midjourney em Python: gerar e salvar imagens
Crie a tarefa, consulte até concluir e salve uma grade PNG 2x2; a chave e o corpo ficam no disco antes do POST.
Nesta página
- Resposta curta
- Qual API este exemplo de Python usa?
- O que você precisa
- Corpo da requisição e parâmetros
- Chamar a API do Midjourney em Python
- A grade de imagens do Midjourney retornada
- Executar de novo após tempo esgotado ou download com falha
- Erros e correções
- Custo e quando usar outra API de imagens
- Perguntas frequentes
- Preciso de uma assinatura do Midjourney para usar esta API?
- Por que uma tarefa do Midjourney retorna quatro imagens em um único arquivo?
- O que significa 409 idempotency_key_reused?
- O que acontece se meu script atingir o tempo limite durante a consulta?
Este exemplo usa Python para chamar a API do Midjourney da OmniaKey, um serviço de terceiros, em três etapas: criar a tarefa, consultar o status até concluir e baixar o PNG da URL assinada do arquivo. Um teste de integração em 12 de novembro de 2026 retornou uma imagem 2048x2048 em 197,1 segundos, e essa imagem é uma grade 2x2 com quatro candidatos. O script abaixo adiciona um arquivo de estado, então um tempo esgotado ou um download com falha não cria uma segunda tarefa paga.
Resposta curta
- Envie
POST https://api.omniakey.com/v1/media/taskscom umIdempotency-Key, depois consulteGET /v1/media/tasks/{id}e baixeassets[0].url. - Uma tarefa retorna uma grade PNG com quatro imagens, não quatro arquivos separados.
- O Midjourney nesta API faz só texto para imagem: sem edição, imagem de referência, blend, upscale, vary, deslocamento ou seletor de versão.
- A chave vem da OmniaKey e é separada de uma assinatura do Midjourney. Salve o corpo e a chave de idempotência antes do POST, adicione o id da tarefa quando a API aceitar e mantenha o arquivo de estado para retomar depois. Um tempo esgotado local não cancela a tarefa no servidor.
Qual API este exemplo de Python usa?
O exemplo chama a API de terceiros da OmniaKey para o Midjourney. A OmniaKey não é o Midjourney, e uma chave OmniaKey é uma conta separada com saldo próprio. Uma assinatura no site do Midjourney é outro produto e não cria uma chave OmniaKey.
A página do modelo Midjourney mostra o modelo e a tarifa atual do lado da OmniaKey. O restante do passo a passo cobre esta integração e o ciclo de vida das tarefas.
O que você precisa
Você precisa de três coisas:
- Uma chave de API da OmniaKey, exportada como
OMNIAKEY_API_KEY. - Python 3. O script usa só a biblioteca padrão, então não há SDK para instalar.
midjourneydisponível para essa chave/conta. Se o modelo não aparecer na lista da chave, confira o acesso da conta antes de executar a chamada de criação.
export OMNIAKEY_API_KEY="your-key"
python3 midjourney_grid.py
Mantenha a chave fora do script e dos logs. O código a lê uma vez do ambiente.
Corpo da requisição e parâmetros
O Midjourney recebe um prompt e três parâmetros. O tipo de bot é predefinido do lado da OmniaKey, então o corpo pode omitir a versão do modelo e botType.
| Campo | Valor | Observações |
|---|---|---|
input.prompt | Texto | A cena desejada |
parameters.aspectRatio | 1:1 | Também 16:9, 9:16, 3:2, 2:3 |
parameters.quality | "1" | A documentação usa a string "1" |
parameters.n | 1 | Precisa ser 1 |
Defina o formato com aspectRatio, não com um tamanho em pixels. O campo é camelCase: aspectRatio, nunca aspect_ratio. Use um dos cinco valores listados.
O corpo para o prompt de exemplo fica assim:
{
"model": "midjourney",
"type": "image",
"operation": "generate",
"input": {
"prompt": "A small red paper boat floating on a quiet blue lake at sunrise, cinematic lighting, elegant composition, no text"
},
"parameters": {"aspectRatio": "1:1", "quality": "1", "n": 1}
}
Chamar a API do Midjourney em Python
Salve como midjourney_grid.py e execute. A primeira execução grava midjourney-task.json antes do POST e adiciona o id da tarefa quando a OmniaKey aceita a requisição.
import hashlib
import json
import os
import sys
import time
import urllib.error
import urllib.request
import uuid
from pathlib import Path
API = "https://api.omniakey.com/v1"
KEY = os.environ["OMNIAKEY_API_KEY"]
STATE = Path("midjourney-task.json")
OUT = Path("midjourney-grid.png")
PROMPT = ("A small red paper boat floating on a quiet blue lake at sunrise, "
"cinematic lighting, elegant composition, no text")
def save(state):
tmp = STATE.with_suffix(".tmp")
tmp.write_text(json.dumps(state, indent=2))
tmp.replace(STATE)
def read_json(response):
raw = response.read()
try:
return json.loads(raw)
except ValueError:
return {"raw": raw[:300].decode("utf-8", "replace")}
def call(method, path, body=None, headers=None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(
API + path, data=data, method=method,
headers={"Authorization": f"Bearer {KEY}", **(headers or {})})
if body is not None:
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req, timeout=60) as response:
return response.status, read_json(response)
except urllib.error.HTTPError as error:
return error.code, read_json(error)
except (urllib.error.URLError, TimeoutError) as error:
return None, {"error": str(error)}
def main():
state = json.loads(STATE.read_text()) if STATE.exists() else None
if state is None:
body = {
"model": "midjourney", "type": "image", "operation": "generate",
"input": {"prompt": PROMPT},
"parameters": {"aspectRatio": "1:1", "quality": "1", "n": 1},
}
state = {"idempotency_key": str(uuid.uuid4()), "body": body,
"task_id": None, "status": "creating"}
save(state) # no API key here; write before POST
elif state["body"]["input"]["prompt"] != PROMPT:
sys.exit("The state file holds a different prompt. Move it aside to start a new job.")
if OUT.exists() and state.get("status") == "downloaded":
saved_sha = state.get("download_sha")
if not saved_sha or hashlib.sha256(OUT.read_bytes()).hexdigest() == saved_sha:
print("already saved", OUT)
return
if not state["task_id"]:
code, task = call("POST", "/media/tasks", state["body"],
{"Idempotency-Key": state["idempotency_key"]})
if code is None:
sys.exit(f"Create not confirmed: {task['error']}. Rerun to retry the same key and body.")
if code == 409:
sys.exit("409 idempotency_key_reused: same key, different body. Restore the saved body.")
if code == 503:
sys.exit("503 no_available_channel: check model access for this key/account; "
"contact support with the request id if it persists.")
if code not in (200, 202) or "id" not in task:
sys.exit(f"Create failed: HTTP {code} {task}")
state.update(task_id=task["id"], status=task["status"])
save(state)
deadline = time.monotonic() + 900
while True:
code, task = call("GET", f"/media/tasks/{state['task_id']}")
if code is None:
sys.exit(f"Polling stopped: {task['error']}. The job is still saved; rerun this script.")
if code != 200 or "status" not in task:
sys.exit(f"Poll failed: HTTP {code} {task}")
state["status"] = task["status"]
save(state)
if task["status"] in ("failed", "canceled"):
sys.exit(f"Job {task['status']}: {task.get('error')}")
if task["status"] == "succeeded":
break
if time.monotonic() > deadline:
sys.exit(f"Still {task['status']} after 900 s. Rerun with the same state file.")
time.sleep(min(8, 2 + task.get("progress", {}).get("percent", 0) // 20))
asset = task["assets"][0]
if asset.get("mime_type") != "image/png":
sys.exit(f"Expected a PNG grid, got {asset.get('mime_type')}")
try:
with urllib.request.urlopen(asset["url"], timeout=120) as response:
data = response.read() # signed URL: no Authorization header
except (urllib.error.HTTPError, urllib.error.URLError, TimeoutError) as error:
sys.exit(f"Download failed: {error}. Rerun to get a fresh signed URL and retry.")
digest = hashlib.sha256(data).hexdigest()
if asset.get("sha256") and digest != asset["sha256"]:
sys.exit("SHA-256 mismatch. Rerun to download the asset again.")
part = OUT.with_suffix(".part")
part.write_bytes(data)
part.replace(OUT)
state.update(status="downloaded", download_sha=digest)
save(state)
print("saved", OUT, len(data), "bytes")
if __name__ == "__main__":
main()
As formas da requisição e da resposta vêm do teste de integração de 12 de novembro; o estado de reinício é a adição aqui, e seus caminhos de ciclo de vida foram verificados offline com respostas simuladas.
O arquivo de estado guarda o que você precisa para continuar: o corpo JSON e o UUID usado como chave de idempotência. Não guarda nenhuma chave de API. É gravado antes do POST, então uma conexão interrompida deixa os dois valores no disco para recuperação.
A chamada de criação pode retornar dois códigos de sucesso. Um 202 aceita uma tarefa nova. Um 200 significa que a OmniaKey já viu a mesma chave e o mesmo corpo e está devolvendo a tarefa original; continue consultando essa tarefa. Envie o mesmo corpo e a chave de novo só quando o resultado da criação for desconhecido após uma falha de conexão. A documentação lista 409 idempotency_key_reused para um corpo alterado.
Controle o loop de consulta por status. A porcentagem é aproximada e pode ficar em 0 enquanto a tarefa roda. Pare em succeeded, failed ou canceled. A documentação do ciclo de vida de tarefas de mídia da OmniaKey (verificada em 12 de novembro de 2026) descreve esses estados.
O limite de consultas é local. Quando ele vence, a tarefa ainda está rodando ou na fila; executar o mesmo script de novo lê o id salvo e continua a consulta. Um tempo esgotado local não é cancelamento. O cancelamento usa outro endpoint.
A grade de imagens do Midjourney retornada
Uma tarefa concluída tem um único arquivo: uma grade PNG. Esta é a amostra do teste de integração de 12 de novembro de 2026:

A resposta abaixo está resumida: a URL está oculta e expires_at é histórico. Ela adiciona os campos de arquivo necessários:
{
"id": "task_DbCWJPfPL1jyhSD38IihNHiiFQao8hjV",
"status": "succeeded",
"progress": {"percent": 100, "stage": "completed"},
"assets": [
{
"kind": "image",
"mime_type": "image/png",
"width": 2048,
"height": 2048,
"byte_size": 5834301,
"sha256": "f503cef038d15fa150b36b5fe7c41874febad3aa1167698d790781e47a4a10b9",
"expires_at": "2026-11-12T04:16:41Z",
"url": "https://…redacted-signed-url…"
}
]
}
Leia width e height do arquivo em vez de fixar 2048. O quadrado 2048x2048 e os 197,1 segundos são uma amostra; trate-os assim. Os quatro candidatos compartilham um PNG, então divida a grade após o download se o app precisar de imagens separadas.
A url é assinada e dura pouco, cerca de uma hora. O registro da tarefa e a imagem ficam guardados por 24 horas após a criação, segundo a documentação da API de imagens da OmniaKey (verificada em 12 de novembro de 2026). Baixe antes de a URL expirar. Se ela expirou, consulte o id da tarefa de novo para obter uma URL assinada nova.
Baixe a URL do arquivo com um GET comum e sem cabeçalho Authorization. A assinatura está na URL. O script também compara os bytes baixados com o sha256 da tarefa, o que detecta um arquivo truncado antes de sobrescrever sua cópia boa.
Executar de novo após tempo esgotado ou download com falha
Execute o mesmo comando e mantenha a tarefa. O script lê midjourney-task.json e escolhe o caminho seguro mais curto:
- Criação não confirmada: ele faz POST do mesmo corpo com a mesma chave de idempotência.
- O id existe e a tarefa ainda roda: ele consulta o id existente.
- A tarefa concluiu mas o PNG não está lá: ele consulta uma vez para obter uma URL assinada nova e baixa de novo.
- O PNG já está salvo e corresponde ao SHA-256 registrado: ele sai sem outra requisição. Um arquivo corrompido é consultado e baixado de novo.
Isso funciona dentro da janela de retenção de 24 horas. A mesma chave com um prompt ou parâmetros diferentes gera 409. O script recusa um arquivo de estado cujo prompt não corresponde mais; mova o arquivo se mudar os parâmetros.
Erros e correções
As falhas abaixo vêm das regras de tarefa e idempotência da API. Corrija a causa e execute de novo com o mesmo arquivo de estado quando for o caso.
| Resposta | Código | O que fazer |
|---|---|---|
| 400 | missing_idempotency_key | Adicione o cabeçalho; mantenha-o com menos de 255 caracteres |
| 409 | idempotency_key_reused | Envie o corpo original ou comece uma tarefa nova com outra chave |
| 503 | no_available_channel | Confira o acesso desta chave/conta; fale com o suporte se persistir |
failed | error.code, error.message | Leia o erro da tarefa; tarefas com falha são reembolsadas |
canceled | — | A tarefa parou; crie outra se ainda precisar |
Um 503 no_available_channel não prova que uma chave não tem permissão para o modelo. Dois envios com uma chave usada neste lote retornaram 503 e não criaram tarefa; outra chave teve sucesso em um teste de integração anterior no mesmo dia. A causa é desconhecida: pode ser a disponibilidade da chave/conta ou do canal upstream. Confirme que midjourney está disponível para sua chave/conta atual. Se o 503 persistir, envie ao suporte o corpo da resposta e o id da requisição.
Um 200 na criação é a repetição da tarefa original. Só failed e canceled são falhas terminais; parar o script deixa a tarefa rodando.
Custo e quando usar outra API de imagens
A OmniaKey cobra uma vez por tarefa concluída, e tarefas com falha ou canceladas são reembolsadas automaticamente. A tarifa atual está na página do modelo Midjourney; confira antes de estimar um lote.
O Midjourney serve para escolher entre quatro variações em uma grade. Esta integração retorna essa grade de texto para imagem; para uma imagem por chamada ou uma edição que preserve uma cena de origem, veja GPT Image 2.5 API ou Nano Banana Pro API.
Se preferir digitar um prompt a escrever código, o AI Image Generator executa modelos de imagem no navegador.
Perguntas frequentes
Preciso de uma assinatura do Midjourney para usar esta API?
Não. A chave vem da OmniaKey e as chamadas vão para a API de terceiros da OmniaKey. A OmniaKey não é o Midjourney; uma assinatura no site do Midjourney é outro produto e não cria uma chave OmniaKey.
Por que uma tarefa do Midjourney retorna quatro imagens em um único arquivo?
O Midjourney retorna uma grade 2x2 com quatro candidatos como um único PNG. Os quatro compartilham esse arquivo, e upscale e vary não estão disponíveis nesta integração, então divida a grade você mesmo após o download se precisar de arquivos separados.
O que significa 409 idempotency_key_reused?
Você enviou uma chave de idempotência que a OmniaKey já tem, mas o corpo da requisição mudou. Envie o corpo original ou comece uma tarefa nova com outra chave. Reutilizar uma chave com um prompt diferente é a causa comum.
O que acontece se meu script atingir o tempo limite durante a consulta?
A tarefa continua rodando. O id da tarefa está em midjourney-task.json, então executar o mesmo script de novo vai consultar esse id ou baixar o arquivo concluído. Um tempo esgotado local mantém a tarefa viva e a cobrança em vigor.
Modelos deste artigo
Artigos relacionados
Nano Banana Pro API: tutorial em Python, edição de imagem e preço
Um POST, uma consulta, um download: um script de Nano Banana Pro que funciona.
GPT Image 2.5 API: gerar e editar imagens em Python
Um POST cria a tarefa e um GET lê o resultado. Aqui estão o código da requisição, os tamanhos que rodamos em 29 de outubro de 2026 e uma edição mostrada antes e depois.
GPT Image 2.6: análise, GIF, SVG, preço e para quem
O que há de novo no GPT Image 2.6, testado com nosso próprio GIF, três pôsteres e uma edição de foto.
Artigos recentes
- Claude Fable 5.2 vs Opus 5.5: código e tarefas de agente
- Gemini 4 Hydrogen: programação, níveis de pensamento e API
- MiniMax M3.1 Flash no OpenCode: configuração testada e chamadas de ferramenta
- Seedance 2.5 Flash: data de lançamento e acesso à API
- Prompts do Seedream 5 Pro: texto no pôster e edição com referência

