# Panduan Midjourney API Python: buat dan simpan gambar

> Buat tugas gambar Midjourney di Python, periksa statusnya, lalu unduh grid gambar. Ketahui operasi yang tersedia sebelum membangun integrasi.

- Canonical HTML: https://omniakey.com/id/blog/midjourney-api-python
- Markdown: https://omniakey.com/id/blog/midjourney-api-python.md
- Published: 2026-11-12 (updated 2026-11-12)
- Author: Kiki, COO at OmniaKey
- Language: id
- Category: API setup
- Reading time: 8 min
- All posts: https://omniakey.com/blog/llms.txt

---

Contoh ini memanggil API [Midjourney](https://omniakey.com/id/midjourney) pihak ketiga dari OmniaKey melalui Python dalam tiga langkah: buat tugas, periksa statusnya sampai selesai, lalu unduh PNG dari URL aset bertanda tangan. Uji integrasi 12 November 2026 mengembalikan satu gambar 2048x2048 dalam 197,1 detik, dan gambar itu adalah grid 2x2 berisi empat kandidat. Skrip di bawah menambahkan file status, sehingga timeout atau unduhan gagal tidak membuat tugas berbayar kedua.

## Jawaban singkat

- Kirim `POST https://api.omniakey.com/v1/media/tasks` dengan `Idempotency-Key`, lalu periksa `GET /v1/media/tasks/{id}` dan unduh `assets[0].url`.
- Satu tugas mengembalikan satu grid PNG berisi empat gambar, bukan empat file terpisah.
- Midjourney di API ini hanya teks ke gambar: tanpa edit, gambar referensi, blend, upscale, vary, geser, atau pemilih versi.
- Kunci berasal dari OmniaKey dan terpisah dari langganan Midjourney. Simpan body dan kunci idempotensi sebelum POST, tambahkan id tugas setelah API menerima, dan pertahankan file status untuk melanjutkan nanti. Timeout lokal tidak membatalkan tugas di server.

## API mana yang dipakai contoh Python ini?

Contoh memanggil API pihak ketiga OmniaKey untuk Midjourney. OmniaKey bukan Midjourney, dan kunci OmniaKey adalah akun terpisah dengan saldo sendiri. Langganan di situs Midjourney adalah produk lain dan tidak membuat kunci OmniaKey.

[Halaman model Midjourney](https://omniakey.com/id/midjourney) menampilkan model dan tarif saat ini di sisi OmniaKey. Bagian berikutnya membahas integrasi ini dan siklus hidup tugasnya.

## Yang Anda butuhkan

Anda membutuhkan tiga hal:

1. Kunci API OmniaKey, diekspor sebagai `OMNIAKEY_API_KEY`.
2. Python 3. Skrip hanya memakai pustaka standar, jadi tidak ada SDK yang perlu dipasang.
3. `midjourney` tersedia untuk kunci/akun itu. Jika model tidak ada di daftar kunci, periksa akses akun sebelum menjalankan panggilan pembuatan.

```bash
export OMNIAKEY_API_KEY="your-key"
python3 midjourney_grid.py
```

Jauhkan kunci dari skrip dan log. Kode membacanya sekali dari environment.

## Body permintaan dan parameter

Midjourney menerima prompt dan tiga parameter. Tipe bot sudah diatur di sisi OmniaKey, jadi body bisa melewati versi model dan `botType`.

|Kolom|Nilai|Catatan|
|-|-|-|
|`input.prompt`|Teks|Adegan yang diinginkan|
|`parameters.aspectRatio`|`1:1`|Juga `16:9`, `9:16`, `3:2`, `2:3`|
|`parameters.quality`|`"1"`|Dokumentasi memakai string `"1"`|
|`parameters.n`|`1`|Harus 1|

Tentukan bentuk dengan `aspectRatio`, bukan ukuran piksel. Kolomnya camelCase: `aspectRatio`, bukan `aspect_ratio`. Pakai salah satu dari lima nilai yang terdaftar.

Body untuk prompt contoh terlihat seperti ini:

```json
{
  "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}
}
```

## Memanggil Midjourney API dari Python

Simpan sebagai `midjourney_grid.py` dan jalankan. Eksekusi pertama menulis `midjourney-task.json` sebelum POST, lalu menambahkan id tugas setelah OmniaKey menerima permintaan.

```python
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()
```

Bentuk permintaan dan respons berasal dari uji integrasi 12 November; status mulai ulang adalah tambahan di sini, dan cabang siklus hidupnya diperiksa offline dengan respons tiruan.

File status menyimpan yang Anda butuhkan untuk melanjutkan: body JSON dan UUID yang dipakai sebagai kunci idempotensi. File ini tidak menyimpan kunci API. File ditulis sebelum POST, jadi koneksi yang terputus meninggalkan kedua nilai di disk untuk pemulihan.

Panggilan pembuatan bisa mengembalikan dua kode sukses. `202` menerima tugas baru. `200` berarti OmniaKey sudah melihat kunci dan body yang sama dan mengembalikan tugas asli; lanjutkan memeriksa tugas itu. Kirim lagi body dan kunci yang sama hanya jika hasil pembuatan tidak diketahui setelah koneksi gagal. Dokumentasi mencantumkan `409 idempotency_key_reused` untuk body yang berubah.

Kendalikan loop pemeriksaan dengan `status`. Persentase bersifat perkiraan dan bisa tetap 0 selama tugas berjalan. Berhenti pada `succeeded`, `failed`, atau `canceled`. [Dokumentasi siklus hidup tugas media](https://docs.omniakey.com/en/media/overview) OmniaKey (diperiksa 12 November 2026) menjelaskan status tersebut.

Batas pemeriksaan bersifat lokal. Saat batas habis, tugas masih berjalan atau antre; menjalankan skrip yang sama lagi akan membaca id tersimpan dan melanjutkan pemeriksaan. Timeout lokal bukan pembatalan. Pembatalan memakai endpoint terpisah.

## Grid gambar Midjourney yang dikembalikan

Tugas yang berhasil punya satu aset: grid PNG. Ini sampel dari uji integrasi 12 November 2026:

![Empat perahu kertas merah terlipat di atas air biru saat matahari terbit, tersusun dalam grid 2x2 dengan pantulan cahaya hangat](https://omniakey.com/_img/blog/midjourney-api-python/paper-boat-grid.03735622.1600.webp)

_Sampel uji integrasi 12 November 2026: satu tugas midjourney, satu PNG 2048x2048, empat kandidat, 197,1 detik dari pembuatan sampai unduhan. Byte yang diunduh cocok dengan SHA-256 tugas._

Respons di bawah dipersingkat: URL disamarkan dan `expires_at` adalah nilai historis. Respons ini menambahkan kolom aset yang Anda butuhkan:

```json
{
  "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…"
    }
  ]
}
```

Baca `width` dan `height` dari aset, jangan menetapkan 2048 secara hard-code. Persegi 2048x2048 dan 197,1 detik adalah satu sampel; perlakukan seperti itu. Keempat kandidat berbagi satu PNG, jadi potong grid setelah diunduh jika aplikasi Anda butuh gambar terpisah.

`url` bertanda tangan dan berumur pendek, sekitar satu jam. Catatan tugas dan gambar disimpan 24 jam setelah pembuatan, menurut [dokumentasi API gambar](https://docs.omniakey.com/en/media/image) OmniaKey (diperiksa 12 November 2026). Unduh sebelum URL kedaluwarsa. Jika sudah kedaluwarsa, periksa id tugas lagi untuk mendapatkan URL bertanda tangan baru.

Unduh URL aset dengan GET biasa dan tanpa header `Authorization`. Tanda tangannya ada di URL. Skrip juga membandingkan byte yang diunduh dengan `sha256` tugas, sehingga file terpotong terdeteksi sebelum menimpa salinan bagus Anda.

## Menjalankan ulang setelah timeout atau unduhan gagal

Jalankan perintah yang sama dan pertahankan tugas. Skrip membaca `midjourney-task.json` dan memilih jalur aman terpendek:

- Pembuatan belum terkonfirmasi: skrip mengirim POST body yang sama dengan kunci idempotensi yang sama.
- Id tugas ada dan tugas masih berjalan: skrip memeriksa id yang ada.
- Tugas berhasil tetapi PNG hilang: skrip memeriksa sekali untuk URL bertanda tangan baru dan mengunduh lagi.
- PNG sudah tersimpan dan cocok dengan SHA-256 yang dicatat: skrip keluar tanpa permintaan lain. File rusak diperiksa dan diunduh lagi.

Ini berfungsi dalam jendela penyimpanan 24 jam. Kunci yang sama dengan prompt atau parameter berbeda menghasilkan `409`. Skrip menolak file status yang prompt-nya tidak lagi cocok; pindahkan file jika Anda mengubah parameter.

## Error dan perbaikan

Kegagalan berikut berasal dari aturan tugas dan idempotensi API. Perbaiki penyebabnya, lalu jalankan ulang dengan file status yang sama bila berlaku.

|Respons|Kode|Yang harus dilakukan|
|-|-|-|
|400|`missing_idempotency_key`|Tambahkan header; jaga di bawah 255 karakter|
|409|`idempotency_key_reused`|Kirim body asli atau mulai tugas baru dengan kunci baru|
|503|`no_available_channel`|Periksa akses untuk kunci/akun ini; hubungi dukungan jika berlanjut|
|`failed`|`error.code`, `error.message`|Baca error tugas; tugas gagal dikembalikan dananya|
|`canceled`|—|Tugas berhenti; buat yang baru jika masih perlu|

`503 no_available_channel` tidak berarti kunci tidak punya izin model. Dua pengiriman dengan satu kunci yang dipakai di kelompok ini mengembalikan `503` dan tidak membuat tugas; kunci lain berhasil dalam uji integrasi lebih awal pada hari yang sama. Penyebabnya tidak diketahui: bisa jadi ketersediaan kunci/akun atau ketersediaan kanal upstream. Pastikan `midjourney` tersedia untuk kunci/akun Anda saat ini. Jika `503` berlanjut, kirim body respons dan id permintaan ke dukungan.

`200` saat pembuatan adalah tugas asli yang dikembalikan lagi. Hanya `failed` dan `canceled` yang merupakan kegagalan terminal; menghentikan skrip membiarkan tugas tetap berjalan.

## Biaya dan kapan memakai API gambar lain

OmniaKey menagih sekali per tugas yang berhasil, dan tugas gagal atau dibatalkan dikembalikan dananya secara otomatis. Tarif saat ini ada di [halaman model Midjourney](https://omniakey.com/id/midjourney); periksa sebelum memperkirakan satu kelompok.

Midjourney cocok untuk memilih di antara empat variasi dalam satu grid. Integrasi ini saat ini mengembalikan grid teks ke gambar tersebut; untuk satu gambar per panggilan atau edit yang mempertahankan adegan sumber, lihat [GPT Image 2.5 API](https://omniakey.com/id/blog/gpt-image-2-5-api) atau [Nano Banana Pro API](https://omniakey.com/id/blog/nano-banana-pro-api).

Jika Anda lebih suka mengetik prompt daripada menulis kode, [AI Image Generator](https://omniakey.com/id/ai-image-generator) menjalankan model gambar di browser.

## Pertanyaan umum

### Apakah saya perlu langganan Midjourney untuk memakai API ini?

Tidak. Kunci berasal dari OmniaKey dan panggilan ditujukan ke API pihak ketiga OmniaKey. OmniaKey bukan Midjourney; langganan di situs Midjourney adalah produk lain dan tidak membuat kunci OmniaKey.

### Mengapa satu tugas Midjourney mengembalikan empat gambar dalam satu file?

Midjourney mengembalikan grid 2x2 berisi empat kandidat sebagai satu PNG. Keempatnya berbagi file itu, dan upscale serta vary tidak tersedia di integrasi ini, jadi potong grid sendiri setelah mengunduh jika butuh file terpisah.

### Apa arti 409 idempotency\_key\_reused?

Anda mengirim kunci idempotensi yang sudah dimiliki OmniaKey, tetapi body permintaan berubah. Kirim body asli atau mulai tugas baru dengan kunci baru. Memakai ulang kunci dengan prompt berbeda adalah penyebab umum.

### Apa yang terjadi jika skrip saya timeout saat memeriksa status?

Tugas tetap berjalan. Id tugas ada di `midjourney-task.json`, jadi menjalankan skrip yang sama lagi akan memeriksa id itu atau mengunduh aset yang sudah selesai. Timeout lokal membiarkan tugas hidup dan biaya tetap berlaku.
