Musik
Buat lagu utuh dari lirik, atau trek instrumental dari deskripsi teks. Response-nya objek JSON yang membawa audio dalam base64. Hanya model musik yang dilayani di sini. Mengirim model lain mengembalikan 400.
POST /v1/music/generations
Model musik ditandai dengan music pada field endpoints di GET /v1/models:
curl -s https://kenari.id/v1/models \ | jq '.data[] | select(.endpoints | index("music")) | {id, max_lyrics_chars, max_prompt_chars, max_duration_secs, pricing_lines}'Kalau panggilan itu tidak mengembalikan apa pun, saat ini belum ada model musik yang tersedia.
Request
Section titled “Request”Body-nya JSON.
| Field | Tipe | Wajib | Keterangan |
|---|---|---|---|
model | string | ya | Id model musik. |
lyrics | string | bersyarat | Kata-kata yang dinyanyikan. Wajib untuk lagu dengan vokal, yaitu saat instrumental bernilai false atau tidak diisi. |
prompt | string | bersyarat | Gaya dan suasana trek. Wajib saat instrumental bernilai true. Pada lagu dengan vokal, prompt opsional dan digunakan untuk menjelaskan gaya musik. |
instrumental | boolean | tidak | true membuat trek tanpa vokal. Default false. Saat true, lyrics diabaikan. |
response_format | string | tidak | Hanya mp3 yang tersedia, dan itu default-nya. wav dan pcm mengembalikan 400. |
stream | boolean | tidak | Dicadangkan. true mengembalikan 400. |
lyrics_optimizer | boolean | tidak | Dicadangkan. true mengembalikan 400. |
Response
Section titled “Response”Lagunya ada di data[0].b64_json, dan formatnya di data[0].format:
{ "data": [ { "b64_json": "<base64 dari byte mp3>", "format": "mp3" } ]}data selalu berisi tepat satu item dan format selalu mp3. Audio bukan tautan unduhan, jadi decode base64-nya lalu tulis ke file.
Karena pembuatan lagu lambat, body bisa datang dalam dua bentuk.
- Spasi di awal. Selama lagu dibuat, gateway menulis satu spasi setiap 20 detik untuk menjaga koneksi tetap terbuka, lalu mengirim JSON-nya. Parse seluruh body sebagai JSON, yang mengabaikan spasi di awal. Jangan perlakukan sebagai audio mentah.
- Error yang terlambat. Kegagalan di awal tetap memakai status aslinya:
400,402,429atau503. Kegagalan setelah gateway terlanjur mengirim200datang sebagai HTTP200dengan objek error standar, misalnya{"error": {"code": "...", "message": "...", "param": null, "type": "..."}}. Selalu periksa fielderrorsebelum mendecode audio.
Lirik atau deskripsi
Section titled “Lirik atau deskripsi”lyrics adalah teks yang dinyanyikan, bukan deskripsi lagu. Mengirim “lagu pop santai tentang Jakarta” sebagai lyrics membuat model menyanyikan kalimat itu. Untuk mendeskripsikan trek, set instrumental ke true dan tulis deskripsinya di prompt.
Penanda bagian membantu model menyusun lagu:
[Verse]Lampu kota menyala satu per satuAku pulang membawa cerita baru
[Chorus]Malam ini milik kitaSampai pagi tibaBatas panjang
Section titled “Batas panjang”Panjang dihitung dalam karakter Unicode, bukan byte, jadi teks beraksen tidak terpotong lebih cepat dari yang terlihat. Batasnya berbeda per model. Kalau sebuah model mencatatnya, GET /v1/models menampilkan max_lyrics_chars dan max_prompt_chars pada entrinya. Tanpa field itu, berlaku batas bawaan: 3.500 karakter untuk lyrics dan 2.000 untuk prompt. Teks yang melewati batas dijawab 400 sebelum ada biaya yang terpotong.
Panjang lagu
Section titled “Panjang lagu”Panjang lagu ditentukan oleh modelnya. Tidak ada field durasi, jadi kamu tidak bisa meminta lagu yang lebih pendek atau lebih panjang. Kalau modelnya mencatatnya, max_duration_secs di GET /v1/models memberi panjang lagu maksimum dalam detik. Angka itu hanya informasi: tidak ada request yang ditolak karenanya, dan lagu sebenarnya biasanya lebih pendek.
Waktu pembuatan
Section titled “Waktu pembuatan”Sebagian besar lagu memerlukan dua sampai tiga menit, dan lagu yang lebih panjang memerlukan waktu lebih lama. Gateway menjaga koneksi tetap terbuka dan menunggu sampai 15 menit sebelum menyerah. Atur timeout klien minimal 5 menit, dan nilai longgar seperti 15 menit paling aman. Klien yang berhenti di 3 menit sesekali akan membatalkan lagu yang sebentar lagi selesai.
Contoh
Section titled “Contoh”Baris pertama memilih satu model musik dari daftar. Isi MODEL sendiri kalau kamu sudah tahu id-nya.
MODEL=$(curl -s https://kenari.id/v1/models \ | jq -r '[.data[] | select(.endpoints | index("music"))][0].id')
curl https://kenari.id/v1/music/generations \ -H "Authorization: Bearer $KENARI_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"model\": \"$MODEL\", \"lyrics\": \"[Verse]\nLampu kota menyala satu per satu\n\n[Chorus]\nMalam ini milik kita\" }" \ --max-time 900 \ -o response.json
# Berhenti jika ada objek error, lalu decode hanya string audio yang tidak kosongjq -e 'has("error") | not' response.json > /dev/null || { jq .error response.json; exit 1; }jq -er '.data[0].b64_json | select(type == "string" and length > 0)' response.json | base64 -d > song.mp3Trek instrumental:
curl https://kenari.id/v1/music/generations \ -H "Authorization: Bearer $KENARI_API_KEY" \ -H "Content-Type: application/json" \ -d "{ \"model\": \"$MODEL\", \"instrumental\": true, \"prompt\": \"lo-fi santai, piano lembut, hujan malam\" }" \ --max-time 900 \ -o response.json
jq -e 'has("error") | not' response.json > /dev/null || { jq .error response.json; exit 1; }jq -er '.data[0].b64_json | select(type == "string" and length > 0)' response.json | base64 -d > instrumental.mp3Python
Section titled “Python”Tidak ada method SDK OpenAI untuk musik, jadi pakai requests.
import base64import os
import requests
headers = {"Authorization": f"Bearer {os.environ['KENARI_API_KEY']}"}
models = requests.get("https://kenari.id/v1/models", timeout=30).json()["data"]model = next(m["id"] for m in models if "music" in m["endpoints"])
response = requests.post( "https://kenari.id/v1/music/generations", headers=headers, json={ "model": model, "lyrics": "[Verse]\nLampu kota menyala satu per satu\n\n[Chorus]\nMalam ini milik kita", }, timeout=900,)body = response.json() # spasi di awal diabaikan
if "error" in body: raise RuntimeError(body["error"]["message"])
with open("song.mp3", "wb") as f: f.write(base64.b64decode(body["data"][0]["b64_json"]))JavaScript
Section titled “JavaScript”import fs from "node:fs";
const headers = { Authorization: `Bearer ${process.env.KENARI_API_KEY}`, "Content-Type": "application/json",};
const { data: models } = await (await fetch("https://kenari.id/v1/models")).json();const model = models.find((m) => m.endpoints.includes("music")).id;
const response = await fetch("https://kenari.id/v1/music/generations", { method: "POST", headers, body: JSON.stringify({ model, lyrics: "[Verse]\nLampu kota menyala satu per satu\n\n[Chorus]\nMalam ini milik kita", }), signal: AbortSignal.timeout(900_000),});const body = JSON.parse(await response.text()); // spasi di awal diabaikan
if (body.error) throw new Error(body.error.message);
fs.writeFileSync("song.mp3", Buffer.from(body.data[0].b64_json, "base64"));Penagihan
Section titled “Penagihan”Musik ditagih dengan harga tetap per lagu, berapa pun panjangnya. Pembuatan yang gagal tidak ditagih. Harga per lagu setiap model ada di Model & harga. Selengkapnya ada di Cara penagihan.
| Status | Kode | Kapan |
|---|---|---|
| 400 | bad_request | Model bukan model musik, field yang diwajibkan untuk jenis trekmu tidak ada, lyrics atau prompt melewati batas, atau response_format bukan mp3. |
| 402 | insufficient_balance | Saldo tidak cukup untuk harga satu lagu. |
| 429 | rate_limit_exceeded | Batas pembuatan musik bersamaan untuk akunmu tercapai. Lihat Batas laju. Tunggu sesuai header Retry-After, lalu coba lagi. Tidak ada biaya yang terpotong. |
| 503 | all_providers_failed, upstream_error | Tidak ada provider yang bisa membuat lagunya. Tidak ada biaya yang terpotong. Coba lagi. |
Error setelah gateway mengirim 200 datang di dalam body response. Lihat Response. Kode lainnya ada di Error.