> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apimart.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Praktik Terbaik

> Pola polling, desain prompt, gambar referensi, strategi retry error, konkurensi, dan saran troubleshooting untuk integrasi Midjourney

Kumpulan praktik terbaik untuk pertanyaan umum, optimasi performa, dan penanganan error, **disarankan dibaca menyeluruh sebelum integrasi**.

## Pengiriman tugas dan polling

Semua endpoint pengiriman bersifat asinkron: setelah dikirim akan mengembalikan `task_id`, lalu query status secara berkala via `GET /v1/midjourney/{task_id}` hingga `SUCCESS` / `FAILURE`.

```python theme={null}
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} perlu memanggil /modal untuk melengkapi parameter")
        time.sleep(3)
    raise TimeoutError(task_id)
```

* **Ritme polling**: disarankan setiap 3–5 detik, frekuensi lebih tinggi tidak berguna dan memboroskan kuota.
* **Jangan memblokir secara sinkron di dalam request web** menunggu tugas selesai — setelah dikirim langsung kembalikan `task_id`, biarkan frontend melakukan polling secara asinkron.

## Desain prompt

**Prompt yang baik:**

```text theme={null}
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
```

* **Subjek di depan**: subjek dulu, lalu deskripsi scene, terakhir kata pemodifikasi.
* **Parameter terstruktur eksplisit**: gunakan `--ar` / `--v` / `--s` (atau field body yang sesuai) lebih terkendali daripada mengandalkan nilai default.
* **Hindari kata ambigu**: `photorealistic` lebih jelas daripada `realistic`.

**Hindari:** terlalu abstrak ("make it good"), subjek berserakan (beberapa objek sejajar tanpa prioritas), memberi tanda kutip pada kata (akan dianggap nilai literal).

**Niji anime:** kirim `niji: true` + `version: "7"`, platform menormalisasinya menjadi `--niji 7`, penagihan melalui `midjourney@imagine-niji7`.

## Praktik terbaik gambar referensi

| Sumber                     | Praktik yang direkomendasikan                                         | Catatan                                                       |
| -------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------- |
| Upload pengguna            | Simpan dulu ke OSS / CDN Anda sendiri, kirim URL tersebut saat submit | Jangan kirim base64 langsung (memboroskan bandwidth)          |
| URL publik                 | Kirim langsung                                                        | Perhatikan SSRF (harus dapat diakses publik) dan batas 12 MiB |
| Pihak ketiga / produk lain | Simpan dulu ke OSS Anda sendiri                                       | URL pihak ketiga bisa kedaluwarsa                             |

* **Kompres hingga \< 5 MiB**: batas platform 12 MiB, tetapi gambar kecil lebih cepat ditransfer / diproses.
* Format PNG / JPG / WebP semuanya bisa, direkomendasikan JPG berkualitas tinggi.
* Resolusi 1024–2048 px sudah cukup, lebih tinggi hanya pemborosan.
* Bobot gambar referensi `iw` (0–3, default 1): >1 lebih dekat ke gambar asli, \<1 lebih bebas.

## Penanganan error dan strategi retry

| code                   | Arti                                             | Strategi retry                                                                      |
| ---------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------- |
| `1` / `200`            | Sukses                                           | ✅                                                                                   |
| `4` VALIDATION\_ERROR  | Parameter salah                                  | ❌ Jangan retry, perbaiki parameter                                                  |
| `3` NOT\_FOUND         | Tidak ada instance tersedia / task\_id tidak ada | Jika instance tidak tersedia bisa retry nanti; jika task\_id tidak ada jangan retry |
| `9` FAILURE            | Ditolak layanan / error internal                 | ⏳ Bisa retry, exponential backoff (1s, 4s, 16s)                                     |
| `21` MODAL             | Bukan status final                               | ✅ Lanjutkan memanggil `/modal`                                                      |
| `24` BANNED\_PROMPT    | Kata sensitif                                    | ❌ Jangan retry, ubah prompt; **sudah otomatis di-refund**                           |
| `429`                  | Rate limit                                       | ⏳ Exponential backoff + jitter                                                      |
| `5xx` / error jaringan | Server / jaringan                                | ⏳ Exponential backoff, error jaringan bisa retry langsung 1 kali                    |

```python theme={null}
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"])      # tidak bisa di-retry
            if data["code"] == 3 and "task" in data["description"]:
                raise ValueError(data["description"])      # task_id tidak ada
            # sisanya (9 / 429 / 5xx) bisa di-retry
        except httpx.RequestError:
            pass
        time.sleep((4 ** attempt) + random.uniform(0, 1))  # 1s / 4s / 16s ...
    raise RuntimeError(f"mencapai jumlah retry maksimum {max_attempts}")
```

## Alur operasi sekunder

```python theme={null}
# 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 digabung secara lokal, 1–2s
single_image = final["image_urls"][0]
```

Inpaint (inpaint → modal, dua langkah):

```python theme={null}
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
# frontend menggambar mask (transparan=area inpaint), upload ke OSS sendiri untuk dapat 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"])
```

> ⚠️ Setelah inpaint masuk MODAL, `/modal` harus dipanggil **dalam 30 menit**, jika tidak backend otomatis CANCEL + refund.

## Kontrol penagihan video

* Segmen tunggal: `batch_size: 1` → menagih 1 × `midjourney@video`
* Batch 4 segmen: `batch_size: 4` → menagih 4 × `midjourney@video`
* HD segmen tunggal: `video_type: "vid_1.1_i2v_720"` + `batch_size: 1` → menagih 1 × `midjourney@video-720p`

**Saran**: jika hanya butuh 1 segmen gunakan `batch_size=1`, gunakan 4 hanya untuk perbandingan batch, jangan default membuka 4 (biaya berlipat N kali).

## Konkurensi dan throughput

```python theme={null}
import asyncio
sem = asyncio.Semaphore(10)  # klien maksimal 10 submit konkuren

async def submit_one(prompt):
    async with sem:
        return await submit({"prompt": prompt})
```

* Platform memiliki batas jumlah submit per menit, jika terlampaui mengembalikan `429`, perlu backoff retry.
* Konkurensi generasi aktual ditentukan oleh kapasitas sistem, jika terlampaui akan antre; tugas yang lama berhenti di `SUBMITTED` biasanya sedang antre.
* Polling harus disertai `sleep`, jangan loop tanpa henti tanpa sleep.

## Saran monitoring

| Metrik                               | Ambang referensi | Arti                                                         |
| ------------------------------------ | ---------------- | ------------------------------------------------------------ |
| Rasio SUCCESS tugas (1 jam terakhir) | > 95%            | Terlalu rendah menandakan anomali layanan / jaringan         |
| Rata-rata waktu penyelesaian         | \< 90s           | Terlalu tinggi menandakan antrean                            |
| Jumlah tugas tertahan di MODAL       | mendekati 0      | Terlalu banyak menandakan klien tidak memanggil `/modal`     |
| Rasio `code=24`                      | \< 5%            | Terlalu tinggi menandakan prompt sering memicu kata sensitif |

## Daftar troubleshooting

| Gejala                            | Arah pemeriksaan                                                             |
| --------------------------------- | ---------------------------------------------------------------------------- |
| Tugas lama `SUBMITTED`            | Sistem sedang antre, cek lagi nanti                                          |
| Tugas lama `NOT_START`            | Platform akan otomatis timeout + refund nanti, tidak perlu penanganan manual |
| Tugas `MODAL` lebih dari 30 menit | Klien tidak memanggil `/modal`, sudah otomatis CANCEL + refund               |
| Field `prompt` kosong             | Hasil teks tugas describe ada di field `description`                         |
| `image_urls` kurang satu gambar   | Sebagian gambar diblokir review konten, lihat `fail_reason`                  |
| Penagihan melebihi ekspektasi     | Lihat field `quota`; video ingat × `batch_size`                              |
