Gambar
Membuat dan mengedit gambar: field, gerbang kapabilitas, dan satuan tagih.
Ringkasan
Dua rute gambar: membuat dari prompt, dan mengedit gambar yang sudah ada. Keduanya sinkron — tidak ada streaming, tidak ada job yang harus dipoll.
/v1/images/generationsbutuh API key/v1/images/editsbutuh API keyBody-nya JSON, dengan Content-Type: application/json dan API key di header Authorization. Satu request menunggu satu respons penuh; tidak ada potongan yang datang bertahap seperti pada chat.
Yang kamu terima adalah byte gambarnya di dalam body, bukan tautan. Ini juga berlaku untuk response_format: "url": pada jalur ini nilai "url" tetap berupa data: URI, yaitu gambarnya sendiri yang ditulis sebagai teks base64. Tidak ada tautan yang bisa dibuka belakangan, jadi tidak ada pula tautan yang kedaluwarsa.
Respons membawa cache-control: no-store. content-type yang dinyatakan model diteruskan apa adanya; kalau model tidak menyebutkannya, isinya diberi tipe generik application/octet-stream. Jangan mengandalkan cache di antara kamu dan API untuk gambar.
Body: generations
Selain model dan prompt, semua field opsional.
| Field | Tipe | Wajib | Artinya |
|---|---|---|---|
model | string | Wajib | Id model, persis seperti di katalog. Model itu harus membawa capabilities.image: true — lihat bagian kapabilitas. |
prompt | string | Wajib | Deskripsi gambar yang kamu mau. |
n | integer | Opsional | 1 sampai 10. Ini pengali tagihan: n: 3 berarti tiga unit, bukan tiga kali lebih cepat. |
size | string | Opsional | Misalnya 1024x1024. Kalau modelnya ditagih per megapiksel, bentuk WxH inilah yang dihitung; ukuran yang tidak bisa dibaca ditolak, bukan ditebak. |
quality | string | Opsional | Diteruskan apa adanya. Nilai yang dikenali bergantung modelnya, bukan daftar tetap dari kami. |
style | string | Opsional | Sama: diteruskan apa adanya, dan model yang memutuskan apakah ia memakainya. |
response_format | string | Opsional | b64_json atau url. Keduanya menghasilkan byte di dalam body — lihat ringkasan. |
resolution | string | Opsional | Diteruskan apa adanya. |
aspect_ratio | string | Opsional | Diteruskan apa adanya. |
Field yang tidak ada di tabel ini tidak dibuang. Body diteruskan ke model apa adanya, jadi field khusus sebuah model tetap sampai ke sana. Yang tidak kami janjikan adalah modelnya memakainya: kami tidak memvalidasi arti field yang tidak kami kenal.
Body: edits
Bentuknya sama dengan generation, ditambah gambar sumber. Semua field di bawah ini opsional kecuali model dan prompt.
| Field | Tipe | Wajib | Artinya |
|---|---|---|---|
model | string | Wajib | Id model gambar, seperti di generation. |
prompt | string | Wajib | Perubahan yang kamu minta. |
image | string | Opsional | Gambar sumber, sebagai URL atau data: URI. String-nya diteruskan apa adanya; kami tidak mengunduhnya. |
image_url | string | Opsional | Bentuk lain untuk sumber yang sama. Ketiganya — image, image_url, input_images — opsional di schema, jadi kami tidak menolak body yang tidak membawa sumber. Apakah modelnya sendiri mau mengedit tanpa sumber adalah urusan model itu. |
input_images | array | Opsional | Beberapa gambar sumber sekaligus, maksimal 8 entri. |
n | integer | Opsional | Sama seperti generation: 1 sampai 10, dan pengali tagihan. |
size | string | Opsional | Ukuran keluaran, misalnya 1024x1024. |
response_format | string | Opsional | b64_json atau url. Sama seperti generation, keduanya byte di dalam body. |
Gerbang kapabilitas
Sebelum apa pun berjalan, model ditanya apakah ia memang model gambar. Jawabannya ada di katalog, di capabilities.image.
Pemeriksaan ini berjalan setelah model ditemukan dan sebelum harga dibaca, dan sebelum saldo ditahan. Artinya penolakan di sini tidak memotong saldo, tidak membuat baris permintaan, dan tidak menyisakan tahanan yang harus dilepas. Kamu bisa memperbaiki permintaannya lalu mengirim ulang tanpa biaya.
| Hasil | Kapan | Apa artinya |
|---|---|---|
| Lolos | Model membawa capabilities.image: true. | Permintaan dijalankan. |
422 capability_mismatch | Model membawa capabilities.image: false — operator sudah menyatakan model ini bukan model gambar. | Pakai model lain. Ini keputusan tentang modelnya, bukan tentang body-mu. |
422 capabilities_not_declared | capabilities model null, atau objeknya tidak menyebut image sama sekali. | Modelnya harus mendeklarasikan capabilities.image = true lebih dulu. Itu perubahan di sisi model, bukan sesuatu yang bisa kamu kirim di body. |
Perhatikan bahwa "tidak menyebut" bukan "tidak bisa". Model yang belum mendeklarasikan apa pun diperlakukan sebagai belum diketahui, dan yang belum diketahui tidak diloloskan. Ini disengaja: menebak dari nama model atau dari endpoint yang dipanggil adalah cara sebuah model chat sampai ke rute gambar, lalu gagal jauh di belakang setelah saldo ditahan.
Satuan tagih
Satuannya tertulis di katalog. Di GET /v1/models letaknya price.unit; di GET /public/catalog letaknya pricing.price.unit, karena katalog publik membungkus harga di dalam objek pricing. Baca field itu, jangan menyimpulkan satuannya dari jenis endpointnya.
Dua satuan terhitung yang paling sering muncul untuk model gambar: image (per gambar) dan megapixel (per megapiksel, dihitung dari size). Tapi yang mengikat selalu price.unit pada entri model itu. Sebagian model gambar menerbitkan tarif per token, dan untuk model seperti itu price.unit berisi token dengan tarif per satu juta token — bukan angka per gambar.
n adalah pengali, bukan saran. n: 3 pada model per gambar berarti tiga unit; pada model per megapiksel berarti tiga kali jumlah megapiksel gambar itu. Pada model yang tarifnya per token, n tetap menentukan berapa gambar yang dibuat, jadi biayanya ikut naik — jangan menganggapnya gratis. Karena itu n dibatasi 1 sampai 10: nilai di luar itu ditolak 422 dengan kode invalid_request di pintu masuk, sebelum ada uang yang ditahan.
Model dengan satuan terhitung yang belum punya harga satuan terbit tetap terdaftar, dan harganya tertulis "quoted at submit". Itu bukan nol dan bukan gratis — artinya belum ada angka yang bisa kami sebutkan sebelum request-nya jalan. Di /v1/models flag-nya bernama price.quoted_at_submit; di /public/catalog bernama pricing.price.quotedAtSubmit. Periksa itu sebelum menjadikan sebuah model andalan.
Rincian bentuk price ada di Model dan harga.
Bentuk respons
{
"created": 1758531600,
"data": [
{ "b64_json": "iVBORw0KGgoAAAANSUhEUgAA..." }
]
}Bytes gambarnya ada di data, satu entri per gambar yang diminta. Dengan response_format: "url", isinya tetap data: URI di dalam body — bukan alamat yang bisa kamu buka nanti.
Bentuk respons dan header yang sama berlaku untuk semua rute media; rinciannya ada di Respons dan kegagalan media.
Contoh
Ganti <id-dari-katalog> dengan id model gambar yang kamu temukan di katalog. Contoh tidak menuliskan satu id pun, karena katalog yang berlaku untukmu yang menentukan.
curl https://api.menvora.web.id/v1/images/generations \
-H "Authorization: Bearer sk-nusa-..." \
-H "Content-Type: application/json" \
-d '{
"model": "<id-dari-katalog>",
"prompt": "Kucing oranye duduk di ambang jendela, cahaya pagi",
"n": 1,
"size": "1024x1024",
"response_format": "b64_json"
}'SDK OpenAI memakai bentuk yang sama, dengan base_url di https://api.menvora.web.id/v1. Perhatikan bahwa base_url SDK sudah termasuk /v1, jadi jangan menambahkannya lagi.
Edit gambar, lewat HTTP langsung karena body-nya JSON:
curl https://api.menvora.web.id/v1/images/edits \
-H "Authorization: Bearer sk-nusa-..." \
-H "Content-Type: application/json" \
-d '{
"model": "<id-dari-katalog>",
"prompt": "Ganti latarnya jadi biru muda",
"image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"n": 1
}'Batasan yang perlu kamu tahu
Rute ini hanya dilayani untuk model yang jalur medianya tersedia di platform ini. Model lain dijawab 501 surface_not_served dengan pesan bahwa endpoint itu belum tersedia untuk model tersebut — bukan gangguan, dan bukan kesalahan request-mu. Status 501 dipilih justru supaya kamu bisa membedakan "belum dibangun untuk model ini" dari "sedang rusak".
Satu batas lain ada di ukuran: n maksimal 10, dan body respons dibatasi di sisi kami. Untuk kebutuhan yang lebih besar, kirim beberapa request daripada satu request raksasa — tiap request juga lebih mudah dicoba ulang kalau gagal.
Kode error lain yang mungkin kamu temui semuanya ada di Error dan status.