Audio, musik & video
kenari menyediakan endpoint audio, musik, dan video yang kompatibel dengan gaya OpenAI. Setiap endpoint hanya melayani model yang ditandai untuk endpoint itu di katalog. Memanggil endpoint audio dengan model teks akan mengembalikan status 400.
Text-to-speech
Section titled “Text-to-speech”POST /v1/audio/speech
Kirim teks dan model, gateway mengembalikan audio mentah (bukan JSON). Biaya dihitung per 1.000 karakter input, lalu dipotong dari saldo Rupiah.
Parameter request
Section titled “Parameter request”| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
model | string | ya | Id model TTS dari katalog. |
input | string | ya | Teks yang akan diucapkan. Ada batas panjangnya, lihat Batas panjang teks. |
voice | string | tidak | Suara yang digunakan. Nama suara berbeda per model, jadi tidak ada daftar tunggal yang berlaku untuk semuanya. Lihat Memilih suara. Kosongkan untuk memakai suara default model. |
response_format | string | tidak | Format audio: mp3, wav, pcm, opus, aac, atau flac. Tidak semua model bisa menghasilkan semuanya, jadi lihat Memilih format. Kosongkan untuk memakai format default model. |
speed | number | tidak | Kecepatan ucapan. |
language | string | tidak | Kode bahasa, mis. id atau en. Default otomatis. |
Memilih suara
Section titled “Memilih suara”Setiap model TTS punya kosakata suaranya sendiri dan nama itu tidak saling
menggantikan: suara yang sah di satu model akan ditolak di model lain. kenari
meneruskan nilai voice apa adanya ke model.
Daftar suaranya ada di /v1/models, pada field voices milik model tersebut:
curl -s https://kenari.id/v1/models \ | jq '.data[] | select(.endpoints | index("audio_speech")) | {id, voices}'Elemen pertama pada voices adalah suara default, yang dipakai kalau voice
dikosongkan. Nama dicocokkan tanpa memperhatikan huruf besar kecil.
Kalau sebuah model belum punya daftar suara tercatat, field voices tidak
muncul. Untuk model seperti itu kenari tidak memeriksa nilai voice, jadi nilai
apa pun yang diterima model akan diteruskan.
Mengirim suara di luar daftar yang diterbitkan akan dijawab 400 yang menyebut
pilihan yang sah, jadi permintaannya tidak pernah sampai ke model dan tidak ada
biaya yang terpotong.
Gaya bicara tidak punya field tersendiri. Tulis arahannya di depan teks, mis.
Bacakan dengan gaya pembawa berita: ....
Memilih format
Section titled “Memilih format”kenari bisa mengirim audio dalam mp3, wav, pcm, opus, aac, atau
flac, tetapi satu model belum tentu bisa menghasilkan semuanya. Format yang
benar-benar dilayani sebuah model ada di /v1/models, pada field formats:
curl -s https://kenari.id/v1/models \ | jq '.data[] | select(.endpoints | index("audio_speech")) | {id, formats}'Elemen pertama pada formats adalah format default, yang dipakai kalau
response_format dikosongkan. Nama dicocokkan tanpa memperhatikan huruf besar
kecil.
Kalau sebuah model belum punya daftar format tercatat, field formats tidak
muncul. Untuk model seperti itu mp3, wav, dan pcm semuanya diterima, dan
nilai lain diperlakukan sebagai mp3. Jadi opus, aac, dan flac hanya
berlaku pada model yang menerbitkan formats. Kalau diminta pada model yang
tidak menerbitkannya, hasilnya mp3 dengan header audio/mpeg yang sesuai.
Mengirim format di luar daftar yang diterbitkan akan dijawab 400 yang menyebut
pilihan yang sah, dan tidak ada biaya yang terpotong. kenari tidak diam-diam
menggantinya dengan format lain, karena Content-Type pada response mengikuti
format yang diminta, jadi mengirim byte mp3 untuk permintaan wav akan memberi
label yang salah pada file yang Anda terima.
Batas panjang teks
Section titled “Batas panjang teks”Sintesis tidak dialirkan sepotong-sepotong, dan model punya batas waktu
sendiri, jadi teks yang terlalu panjang tidak akan pernah selesai. Batas yang
tercatat untuk sebuah model ada di /v1/models, pada field max_input_chars.
Teks yang melewatinya dijawab 400 sebelum ada biaya yang terpotong.
Kalau field itu tidak muncul, kenari tidak membatasi panjangnya. Permintaan yang sangat panjang tetap bisa gagal karena batas waktu model, jadi pecah teks panjang menjadi beberapa permintaan.
Bentuk response
Section titled “Bentuk response”Response berisi byte audio mentah dengan Content-Type sesuai format (audio/mpeg untuk mp3, audio/wav untuk wav, audio/pcm untuk pcm). Tidak ada JSON pembungkus.
Contoh
Section titled “Contoh”curl https://kenari.id/v1/audio/speech \ -H "Authorization: Bearer kn-..." \ -H "Content-Type: application/json" \ -d '{"model":"gemini-3-1-flash-tts","input":"Halo, selamat datang di kenari."}' \ --output speech.mp3Transkripsi audio (Speech-to-text)
Section titled “Transkripsi audio (Speech-to-text)”POST /v1/audio/transcriptions
Kirim file audio dan model, gateway mengembalikan transkripsi teks. Biaya dihitung per detik durasi audio (dibulatkan ke atas), lalu dipotong dari saldo Rupiah. Durasi diperkirakan dari ukuran file saat request masuk dan diperbarui dengan durasi aktual yang dilaporkan provider.
Parameter request
Section titled “Parameter request”Request menggunakan multipart/form-data.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
file | file | ya | File audio yang akan ditranskripsi. |
model | string | ya | Id model STT dari katalog. |
language | string | tidak | Kode bahasa BCP-47, mis. id atau en. Deteksi otomatis jika tidak diisi. |
prompt | string | tidak | Teks petunjuk untuk memandu gaya transkripsi atau ejaan istilah tertentu. |
temperature | number | tidak | Suhu sampling, 0..1. |
response_format | string | tidak | Format respons: json (default), verbose_json, atau text. |
Bentuk response
Section titled “Bentuk response”Format json (default, atau jika response_format tidak diisi):
{ "text": "Halo, ini adalah transkripsi audio." }Format verbose_json menambahkan task, language, duration, segments, dan words di samping text (bergantung model, field yang tidak dikirim model tidak muncul):
{ "text": "Halo, ini adalah transkripsi audio.", "task": "transcribe", "language": "id", "duration": 8.5, "segments": [ { "id": 0, "seek": 0, "start": 0.0, "end": 3.2, "text": "Halo, ini adalah transkripsi audio.", "tokens": [50364, 1234], "temperature": 0.0, "avg_logprob": -0.28, "compression_ratio": 1.23, "no_speech_prob": 0.008 } ]}Response disusun ulang oleh kenari dari daftar field di atas, bukan diteruskan mentah dari provider, jadi field di luar daftar tersebut tidak ikut terkirim.
Format text mengembalikan string teks mentah tanpa JSON pembungkus.
Format srt dan vtt belum didukung di v1 dan akan memakai response json default.
Contoh
Section titled “Contoh”curl https://kenari.id/v1/audio/transcriptions \ -H "Authorization: Bearer kn-..." \ -F file=@rekaman.mp3 \ -F model=whisper-1Pembuatan musik
Section titled “Pembuatan musik”POST /v1/music/generations
Kirim deskripsi atau lirik dan sebuah model musik, gateway mengembalikan satu lagu utuh dalam amplop JSON, dengan audio yang dikodekan sebagai base64. Biaya berupa tarif tetap per lagu, dipotong dari saldo Rupiah setelah lagu selesai dibuat.
Endpoint ini hanya melayani model yang ditandai sebagai model musik di katalog. Memanggilnya dengan model lain mengembalikan 400.
Parameter request
Section titled “Parameter request”| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
model | string | ya | Id model musik dari katalog. |
lyrics | string | bersyarat | Lirik yang akan dinyanyikan. Wajib untuk lagu bernyanyi, yaitu saat instrumental bernilai false atau tidak diisi. Batas panjangnya per model, lihat di bawah. |
prompt | string | bersyarat | Gaya dan suasana trek instrumental. Wajib saat instrumental bernilai true. Batas panjangnya per model, lihat di bawah. |
instrumental | boolean | tidak | Buat trek tanpa vokal. Default false. Saat true, prompt wajib diisi dan lyrics diabaikan. |
response_format | string | tidak | Format audio. Hanya mp3 yang tersedia untuk musik, dan itu sudah menjadi default. Mengirim wav atau pcm dijawab 400, bukan byte mp3 dengan Content-Type yang keliru. |
Panjang lyrics dan prompt dihitung dalam karakter Unicode, bukan byte, jadi teks Indonesian beraksen tidak terpotong lebih cepat dari yang terlihat.
Batas panjang lirik dan prompt
Section titled “Batas panjang lirik dan prompt”Batasnya berbeda per model, jadi bacalah dari /v1/models daripada menuliskannya
tetap di kode Anda. Model musik yang batasnya sudah tercatat mengirim
max_lyrics_chars dan max_prompt_chars pada entri modelnya. Permintaan yang
melewatinya dijawab 400 sebelum ada biaya yang terpotong.
Kalau kedua field itu tidak muncul, kenari memakai batas bawaannya: 3.500
karakter untuk lyrics dan 2.000 karakter untuk prompt. Field yang tidak ada
berarti belum ada angka yang tercatat untuk model itu, bukan berarti tanpa batas.
Panjang lagu
Section titled “Panjang lagu”Panjang lagu ditentukan oleh modelnya dan tidak bisa diminta lewat API. Tidak ada parameter durasi pada request, dan tidak ada cara untuk meminta lagu yang lebih pendek atau lebih panjang.
Model yang panjangnya sudah tercatat mengirim max_duration_secs pada entri
modelnya di /v1/models, dalam DETIK. Angka itu informasi saja: tidak ada
permintaan yang ditolak karenanya. Panjang lagu yang sebenarnya biasanya lebih
pendek dari angka itu.
Menulis lirik
Section titled “Menulis lirik”lyrics adalah kata-kata yang dinyanyikan, bukan deskripsi tentang lagu. Menulis “lagu pop santai tentang Jakarta” akan membuat kalimat itu sendiri yang dinyanyikan. Untuk mendeskripsikan sebuah trek, pakai instrumental dengan prompt.
Penanda bagian membantu model menyusun lagu:
[Verse]Lampu kota menyala satu per satuAku pulang membawa cerita baru
[Chorus]Malam ini milik kitaSampai pagi tibaWaktu pembuatan
Section titled “Waktu pembuatan”Sebagian besar lagu memerlukan dua sampai tiga menit, dan lagu yang lebih panjang memerlukan waktu lebih lama lagi. Gateway menahan koneksi tetap terbuka selama proses berjalan dan memberi satu pembuatan waktu hingga 15 menit sebelum menyerah, jadi tetapkan timeout klien Anda dengan longgar: lima menit adalah batas bawah yang masuk akal, dan klien yang berhenti di tiga menit sesekali akan memutus lagu yang sebentar lagi selesai. Baca seluruh body sebelum memprosesnya.
Bentuk response
Section titled “Bentuk response”Response berupa JSON, dengan lagu ada di data[0].b64_json dan formatnya di data[0].format:
{ "data": [ { "b64_json": "<base64 dari byte mp3>", "format": "mp3" } ]}format selalu mp3, dan array data selalu berisi tepat satu elemen. Audio tidak berupa tautan unduhan, decode base64-nya lalu tulis ke sebuah file.
Body mungkin datang diawali spasi ASCII. Gateway menulis satu spasi setiap 20 detik selama pembuatan lagu berjalan untuk menahan koneksi tetap terbuka, lalu mengirim bingkai JSON. Semua parser JSON melewati spasi di awal, jadi parse body sebagai JSON dan jangan pernah memperlakukannya sebagai blob mentah.
Kegagalan yang terjadi dalam masa tenggang tetap memakai status HTTP aslinya, 400, 402, 429, atau 503. Kegagalan yang terjadi setelahnya datang sebagai HTTP 200 dengan objek error OpenAI standar, misalnya {"error":{"code":"...","message":"...","param":null,"type":"..."}}. Selalu parse JSON-nya dan periksa ada tidaknya field error sebelum mendecode audio.
Batas permintaan bersamaan
Section titled “Batas permintaan bersamaan”Satu akun boleh memiliki maksimal dua pembuatan lagu yang berjalan bersamaan. Permintaan ketiga dijawab 429 dengan header Retry-After, dan tidak ada biaya yang terpotong.
Contoh
Section titled “Contoh”curl https://kenari.id/v1/music/generations \ -H "Authorization: Bearer kn-..." \ -H "Content-Type: application/json" \ -d '{ "model": "music-1.5", "lyrics": "[Verse]\nLampu kota menyala satu per satu\n\n[Chorus]\nMalam ini milik kita" }' \ --max-time 180 \ | jq -r '.data[0].b64_json' | base64 -d > lagu.mp3Trek instrumental:
curl https://kenari.id/v1/music/generations \ -H "Authorization: Bearer kn-..." \ -H "Content-Type: application/json" \ -d '{"model":"music-1.5","instrumental":true,"prompt":"lo-fi santai, piano lembut, hujan malam"}' \ --max-time 180 \ | jq -r '.data[0].b64_json' | base64 -d > instrumen.mp3Penagihan musik
Section titled “Penagihan musik”Musik ditagih dengan tarif tetap per lagu, apa pun durasi hasilnya. Saldo ditahan saat permintaan diterima dan baru dipotong setelah audio berhasil dibuat. Kalau pembuatan gagal, tahanan itu dilepas dan saldo kembali utuh. Harga per lagu setiap model ada di katalog.
Pembuatan video
Section titled “Pembuatan video”kenari menyediakan dua endpoint video. Keduanya asinkron: request diterima langsung, hasilnya diambil lewat polling.
Buat video baru
Section titled “Buat video baru”POST /v1/videos/generations
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
model | string | ya | Id model video dari katalog. |
prompt | string | ya | Deskripsi video yang diminta. |
duration | number | tidak | Durasi dalam detik. Default 6, rentang 1..15, kecuali model punya daftar durasi sendiri. |
resolution | string | tidak | Tingkat resolusi, misalnya 720p atau 1080p. Default memakai tingkat pertama yang didaftarkan model. |
Daftar durasi dan resolusi per model
Section titled “Daftar durasi dan resolusi per model”Sebagian model video hanya menerima durasi tertentu, misalnya 4, 6, 8, dan 10 detik. Model seperti itu mengabaikan rentang 1..15 di atas dan memakai daftarnya sendiri. Nilai default adalah entri pertama pada daftar.
Resolusi bekerja dengan cara yang sama. Model yang menjual beberapa tingkat resolusi punya
harga per detik yang berbeda untuk tiap tingkat, dan tingkat pertama adalah default. Model
yang hanya punya satu harga menerima resolution apa pun dan mengabaikannya, karena tidak
ada tingkat lain yang bisa dipilih.
Daftar durasi dan daftar resolusi untuk tiap model bisa dilihat di katalog. Nilai di luar daftar ditolak sebelum saldo dipotong, jadi request yang salah tidak pernah menghabiskan biaya.
Perpanjang video
Section titled “Perpanjang video”POST /v1/videos/extensions
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
model | string | ya | Id model video dari katalog. |
video.url | string | ya | URL klip sumber yang akan diperpanjang. |
duration | number | tidak | Durasi tambahan dalam detik. Default 6, rentang 1..15. |
Response awal (kedua endpoint)
Section titled “Response awal (kedua endpoint)”Kedua endpoint langsung mengembalikan objek pekerjaan:
{ "id": "vid_abc123", "object": "video.job", "status": "rendering", "model": "kling-v2"}Klip belum tersedia. Gunakan id untuk polling.
Polling hasil video
Section titled “Polling hasil video”GET /v1/videos/{id}
Polling sampai status bukan lagi rendering. Interval yang disarankan: setiap beberapa detik.
| Status | Arti |
|---|---|
rendering | Pekerjaan masih berjalan, lanjutkan polling. |
done | Selesai. Field url berisi tautan klip. |
failed | Render gagal. Biaya dikembalikan otomatis. |
expired | Pekerjaan kedaluwarsa sebelum diambil. Biaya dikembalikan otomatis. |
Bentuk response saat selesai
Section titled “Bentuk response saat selesai”{ "id": "vid_abc123", "status": "done", "url": "https://kenari.id/v1/videos/vid_abc123/content"}Mengunduh klip
Section titled “Mengunduh klip”GET /v1/videos/{id}/content
Tautan pada field url dilayani oleh kenari, bukan oleh mesin render. Ambil dengan API key yang sama, dan hanya akun pemilik pekerjaan yang bisa membacanya.
curl -L https://kenari.id/v1/videos/vid_abc123/content \ -H "Authorization: Bearer kn-..." \ --output klip.mp4Response berisi byte video mentah dengan Content-Type sesuai format klip (video/mp4, video/webm, atau video/quicktime). Selama pekerjaan belum selesai, endpoint ini menjawab 400.
Contoh polling
Section titled “Contoh polling”# Buat videoJOB=$(curl -s https://kenari.id/v1/videos/generations \ -H "Authorization: Bearer kn-..." \ -H "Content-Type: application/json" \ -d '{"model":"kling-v2","prompt":"Seekor elang terbang di atas hutan hujan","duration":6}' \ | jq -r '.id')
# Poll sampai selesaiwhile true; do RESULT=$(curl -s "https://kenari.id/v1/videos/$JOB" \ -H "Authorization: Bearer kn-...") STATUS=$(echo "$RESULT" | jq -r '.status') [ "$STATUS" != "rendering" ] && echo "$RESULT" && break sleep 5donePenagihan video
Section titled “Penagihan video”Video ditagih per detik durasi yang diminta, dipotong saat pekerjaan diterima. Jika render failed atau expired, biaya dikembalikan otomatis ke saldo. Lihat Penagihan untuk detail saldo dan pemotongan.
Kalau model menjual beberapa tingkat resolusi, tarif per detik mengikuti tingkat yang Anda minta, bukan tarif termurah. Biaya satu pekerjaan adalah durasi dikali tarif tingkat itu. Meminta tingkat yang tidak dijual model tersebut ditolak sebelum saldo dipotong, bukan diturunkan diam-diam ke tingkat yang lebih murah.