Gambar
Buat gambar dari prompt teks, atau edit gambar yang sudah ada dengan prompt. Endpoint ini kompatibel dengan OpenAI Images, jadi SDK resmi OpenAI bisa dipakai dengan base URL https://kenari.id/v1. Hanya model gambar yang dilayani di sini. Mengirim model teks mengembalikan 400.
POST /v1/images/generations
POST /v1/images/edits
Daftar model gambar beserta harganya ada di daftar model:
curl -s https://kenari.id/v1/models \ | jq '.data[] | select(.endpoints | index("images")) | {id, pricing_lines}'Request
Section titled “Request”Generations
Section titled “Generations”POST /v1/images/generations menerima body JSON.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
model | string | ya | Id model gambar, misalnya gpt-image-2. |
prompt | string | ya | Deskripsi gambar. |
n | integer | tidak | Jumlah gambar, 1 sampai 10. Default 1. Nilai di luar rentang dipindahkan ke batas terdekat, dan kamu ditagih sesuai jumlah itu. |
size | string | tidak | Ukuran gambar, misalnya "1024x1024". Berupa petunjuk, bukan jaminan. Lihat Ukuran gambar. |
background | string | tidak | "transparent", "opaque" atau "auto". Nilai lain mengembalikan 400. Apakah gambar benar-benar punya latar transparan bergantung pada modelnya. |
response_format | string | tidak | "url" atau "b64_json". Dukungannya berbeda per model, jadi jangan kirim field response_format agar default model digunakan. Lihat Response. |
POST /v1/images/edits mengubah gambar yang sudah ada sesuai prompt. Request-nya multipart/form-data karena gambar dikirim sebagai file. Ulangi field image (atau image[]) untuk setiap file sumber.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
model | string | ya | Id model gambar, misalnya gpt-image-2. |
image | file | ya | Gambar sumber. Kirim PNG, JPEG atau WebP. Ulangi field-nya untuk maksimal 16 file, masing-masing sampai 25 MiB. |
prompt | string | ya | Deskripsi perubahan. Maksimal 8.192 byte. |
mask | file | tidak | Mask PNG, maksimal 25 MiB. Seperti di OpenAI Images, area transparan menandai bagian yang diedit. Tanpa mask, seluruh gambar bisa berubah. |
n | integer | tidak | Jumlah gambar hasil, 1 sampai 10. Default 1. Tidak bergantung pada jumlah file sumber. |
size | string | tidak | Ukuran gambar, misalnya "1024x1024". Berupa petunjuk, bukan jaminan. Lihat Ukuran gambar. |
background | string | tidak | "opaque" atau "auto". "transparent" tidak didukung pada edit dan mengembalikan 400. Pakai generations untuk latar transparan. |
response_format | string | tidak | "url" atau "b64_json". Catatannya sama dengan generations. |
Seluruh body request boleh sampai 32 MiB. Gambar ke-17, file yang melewati batasnya, atau body di atas 32 MiB ditolak sebelum ada biaya yang terpotong. Sebagian model hanya menerima satu gambar sumber atau tidak mendukung mask. Model seperti itu menjawab 400 kalau kamu mengirim lebih.
Response
Section titled “Response”Kedua endpoint mengembalikan bentuk OpenAI Images: created dan array data dengan satu item per gambar.
{ "created": 1718700000, "data": [ { "b64_json": "iVBORw0KGgoAAAANSUhEUg..." } ]}Setiap item berisi b64_json, url, atau keduanya, tergantung model dan response_format. url berupa data: URI yang memuat gambar di dalamnya, bukan tautan ke file yang di-hosting. Baca field mana pun yang ada, lalu decode bagian base64-nya. Response juga bisa memuat usage, size, quality, output_format, dan revised_prompt per gambar kalau modelnya melaporkannya.
Ukuran gambar
Section titled “Ukuran gambar”Model memperlakukan size dengan tiga cara. Ada yang memakainya apa adanya, ada yang mengabaikannya, dan ada yang hanya mengenal rasio aspek. Untuk model berbasis rasio, kenari memetakan ukuran yang kamu kirim ke rasio terdekat yang didukung model, jadi "1792x1024" menghasilkan gambar 16:9 dengan jumlah piksel yang ditentukan model, bukan persis 1792x1024. Bentuk gambar selalu sedekat mungkin dengan permintaan, tetapi jumlah pikselnya bisa berbeda. Kalau ukuran piksel yang tepat itu penting, periksa dimensi gambar yang dikembalikan.
Request yang berjalan lama
Section titled “Request yang berjalan lama”Pembuatan gambar bisa memakan beberapa menit. Kalau hasilnya belum siap dalam 45 detik, kenari langsung mengirim status 200 dan header, lalu menulis satu spasi setiap 20 detik agar proxy tidak memutus koneksi, dan menutup dengan body JSON. Spasi di awal tetap JSON yang valid, jadi parse body lengkap seperti biasa. Atur timeout klien minimal 5 menit.
Contoh
Section titled “Contoh”curl https://kenari.id/v1/images/generations \ -H "Authorization: Bearer $KENARI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-image-2", "prompt": "Seekor panda merah sedang menulis kode di laptop", "n": 1, "size": "1024x1024" }' \ --max-time 300 \ --output response.json
if jq -e '.error' response.json > /dev/null; then jq -r '.error.message' response.json >&2else jq -r '.data[0] | .b64_json // (.url | split(",")[1])' response.json \ | base64 -d > image.pngfiPython
Section titled “Python”import base64import os
from openai import OpenAI
client = OpenAI( base_url="https://kenari.id/v1", api_key=os.environ["KENARI_API_KEY"],)
result = client.images.generate( model="gpt-image-2", prompt="Seekor panda merah sedang menulis kode di laptop", n=1, size="1024x1024",)
error = getattr(result, "error", None)if error or not result.data: raise RuntimeError(f"Image request failed: {error}")
item = result.data[0]b64 = item.b64_json or item.url.split(",", 1)[1]with open("image.png", "wb") as f: f.write(base64.b64decode(b64))JavaScript
Section titled “JavaScript”import fs from "node:fs";import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://kenari.id/v1", apiKey: process.env.KENARI_API_KEY,});
const result = await client.images.generate({ model: "gpt-image-2", prompt: "Seekor panda merah sedang menulis kode di laptop", n: 1, size: "1024x1024",});
if (result.error || !result.data?.length) { throw new Error(`Image request failed: ${JSON.stringify(result.error)}`);}
const item = result.data[0];const b64 = item.b64_json ?? item.url.split(",")[1];fs.writeFileSync("image.png", Buffer.from(b64, "base64"));Edit dengan curl
Section titled “Edit dengan curl”curl https://kenari.id/v1/images/edits \ -H "Authorization: Bearer $KENARI_API_KEY" \ -F "model=gpt-image-2" \ -F "image=@source.png" \ -F "image=@reference.png" \ -F "prompt=Ganti langit menjadi senja, dengan gaya gambar kedua" \ -F "n=1" \ --max-time 300 \ --output response.json
if jq -e '.error' response.json > /dev/null; then jq -r '.error.message' response.json >&2else jq -r '.data[0] | .b64_json // (.url | split(",")[1])' response.json \ | base64 -d > edited.pngfiEdit dengan Python
Section titled “Edit dengan Python”import base64import os
from openai import OpenAI
client = OpenAI( base_url="https://kenari.id/v1", api_key=os.environ["KENARI_API_KEY"],)
result = client.images.edit( model="gpt-image-2", image=[open("source.png", "rb"), open("reference.png", "rb")], prompt="Ganti langit menjadi senja, dengan gaya gambar kedua", n=1,)
error = getattr(result, "error", None)if error or not result.data: raise RuntimeError(f"Image request failed: {error}")
item = result.data[0]b64 = item.b64_json or item.url.split(",", 1)[1]with open("edited.png", "wb") as f: f.write(base64.b64decode(b64))Edit dengan JavaScript
Section titled “Edit dengan JavaScript”import fs from "node:fs";import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://kenari.id/v1", apiKey: process.env.KENARI_API_KEY,});
const result = await client.images.edit({ model: "gpt-image-2", image: [fs.createReadStream("source.png"), fs.createReadStream("reference.png")], prompt: "Ganti langit menjadi senja, dengan gaya gambar kedua", n: 1,});
if (result.error || !result.data?.length) { throw new Error(`Image request failed: ${JSON.stringify(result.error)}`);}
const item = result.data[0];const b64 = item.b64_json ?? item.url.split(",")[1];fs.writeFileSync("edited.png", Buffer.from(b64, "base64"));Penagihan
Section titled “Penagihan”Gambar ditagih per gambar: n dikali harga per gambar model, diambil dari saldo kamu. Empat gambar berharga empat kali satu gambar, dan jumlah file sumber pada edit tidak mengubah harga. Jumlahnya ditahan saat request diterima. Kamu membayar gambar yang benar-benar terkirim, jadi provider yang mengembalikan kurang dari n gambar membuat biayanya lebih kecil, dan response tanpa gambar yang bisa dipakai dianggap gagal dan melepas seluruh tahanan. Selengkapnya ada di Cara penagihan.
| Status | Kode | Kapan |
|---|---|---|
| 400 | bad_request | Model bukan model gambar, prompt, model atau image tidak ada, background tidak valid, atau edit memuat lebih dari 16 gambar, file di atas 25 MiB, atau prompt di atas 8.192 byte. |
| 402 | insufficient_balance | Saldo tidak cukup untuk n gambar. |
| 413 | tidak ada | Body request di atas 32 MiB. Response-nya teks biasa. |
| 503 | all_providers_failed, upstream_error | Tidak ada provider yang bisa menghasilkan gambar. Tidak ada biaya yang terpotong. Coba lagi. |
Kode lainnya ada di Error.