DokumentasiReferensi API

Audio

Teks ke suara dan suara ke teks: dua rute, dua satuan tagih.

Ringkasan

Suara punya dua arah, dan keduanya rute terpisah. Tidak ada satu endpoint dengan parameter mode.

POST/v1/audio/speechbutuh API key
POST/v1/audio/transcriptionsbutuh API key

POST /v1/audio/speech mengubah teks menjadi audio: body-nya JSON, dan yang kamu kirim adalah teks yang akan diucapkan. POST /v1/audio/transcriptions melakukan arah sebaliknya: body-nya multipart/form-data, dan yang kamu kirim adalah berkas audionya.

Keduanya butuh API key dan keduanya sinkron: response datang sebagai satu body utuh, bukan aliran. Jadi tidak ada stream dan tidak ada frame data: di kedua rute ini.

Body teks ke suara

Bentuknya JSON biasa. Hanya dua field yang wajib, dan salah satunya menentukan biaya.

FieldTipeWajibArtinya
modelstringWajibId model, persis seperti yang ada di katalog.
inputstringWajibTeks yang akan diucapkan. Inilah yang ditagih, dihitung per 1.000 karakter. Lihat bagian tagihan.
voicestringOpsionalNama voice. Nilai yang tersedia ditentukan modelnya.
response_formatstringOpsionalFormat audio yang diminta. Diteruskan apa adanya.
speednumberOpsionalPengali kecepatan. Harus bilangan positif.
application/json
{
  "model": "<id-dari-katalog>",
  "input": "Selamat pagi, ini contoh teks yang akan diucapkan.",
  "voice": "<nama-voice>",
  "response_format": "<format>",
  "speed": 1.0
}

Field di luar daftar di atas tetap diteruskan ke model, tidak dibuang. Jadi opsi spesifik yang didukung sebuah model tetap bisa kamu kirim meskipun tidak tercantum di sini.

Body suara ke teks

Rute ini hanya menerima multipart/form-data. Body JSON ditolak, sekalipun isinya lengkap.

Berkasnya adalah satu part bernama file, dan part itu wajib. Rute ini memeriksa keberadaan part tersebut sebelum meneruskan apa pun, jadi permintaan tanpa berkas dijawab 422 dengan pesan yang menyebut berkas file wajib ada. Tidak ada yang sampai ke tahap transkripsi.

Body JSON ditolak dengan cara yang sama, karena rute ini membaca part multipart dan body JSON tidak punya part apa pun untuk dibaca, termasuk file.

FieldTipeWajibArtinya
fileberkasWajibPart berisi audio. Diteruskan byte-for-byte.
modelstringWajibId model, persis seperti yang ada di katalog.
languagestringOpsionalKode bahasa audio, kalau kamu sudah mengetahuinya.
promptstringOpsionalKonteks tambahan untuk model.
response_formatstringOpsionalFormat hasil transkripsi yang diminta.
temperaturestringOpsionalDiteruskan sebagai teks, apa adanya.

Berkas tidak dibaca, tidak didekode, dan tidak diubah oleh MenvoraRouter. Isinya diteruskan byte-for-byte ke model, dan itu berlaku juga untuk nama berkasnya. Konsekuensinya dua: platform tidak bisa memeriksa apakah format berkasmu didukung, dan platform juga tidak bisa merusaknya di tengah jalan. Format yang diterima adalah urusan modelnya.

Satuan tagihan

Dua rute ini memakai satuan yang berbeda, dan keduanya bukan token.

RuteSatuanDihitung dariArtinya
/v1/audio/speechthousand_charsPanjang inputPer 1.000 karakter teks yang kamu kirim.
/v1/audio/transcriptionsminuteDurasi audioPer menit audio yang ditranskripsi.

Harga satuannya ada di katalog, pada price.unit beserta price.unit_price, dan kalimat siap bacanya di price.unit_price_display. Membaca harga tanpa satuannya tidak ada gunanya: angka yang sama bisa berarti per 1.000 karakter atau per menit, tergantung modelnya.

Model yang belum punya harga satuan terbit tetap terdaftar, dengan quoted_at_submit bernilai true, unit_price bernilai null, dan tampilannya berbunyi quoted at submit. Itu bukan harga nol, dan bukan model gratis: angkanya baru ditentukan saat request dikirim.

Kapabilitas yang dibutuhkan

Kedua rute audio menuntut capabilities.audio = true pada model yang kamu pilih.

Pemeriksaan ini berjalan sebelum saldo ditahan. Request yang ditolak karena kapabilitas tidak pernah memotong saldo, tidak meninggalkan baris permintaan, dan tidak pernah dikirim ke model.

Ada dua penolakan yang berbeda, dan keduanya 422:

KodeKapan munculYang perlu dilakukan
capability_mismatchModel menyatakan audio: false.Pakai model lain yang mendeklarasikan audio.
capabilities_not_declaredModel belum menyatakan apa pun: capabilities bernilai null, atau objeknya ada tapi tidak memuat flag audio.Deklarasikan capabilities.audio = true pada model itu. Tidak ada gunanya mengulang request yang sama.
422 application/json
{
  "error": {
    "message": "Model '…' tidak dideklarasikan untuk permintaan jenis ini (butuh kapabilitas 'audio'). Pakai model lain, atau deklarasikan kapabilitas itu pada modelnya.",
    "type": "invalid_request_error",
    "code": "capability_mismatch"
  }
}

Perhatikan bahwa “tidak menyatakan” bukan “tidak bisa”. Model yang flag-nya absen dianggap belum diketahui, bukan ditolak, jadi penolakannya menyebut field yang harus diisi alih-alih menyuruhmu berganti model.

Contoh

Ganti MODEL dengan id dari katalog yang mendeklarasikan capabilities.audio = true. Model chat tidak akan lolos di sini.

teks ke suara
export MODEL="<id-dari-katalog>"

curl "https://api.menvora.web.id/v1/audio/speech" \
  -H "Authorization: Bearer sk-nusa-..." \
  -H "Content-Type: application/json" \
  -d "{\"model\":\"$MODEL\",\"input\":\"Selamat pagi, ini contoh teks.\"}" \
  --output hasil-audio

Responsnya adalah byte audio, jadi simpan ke berkas dengan --output. Tanpa itu curl mencetak byte-nya ke terminal.

suara ke teks
export MODEL="<id-dari-katalog>"

curl "https://api.menvora.web.id/v1/audio/transcriptions" \
  -H "Authorization: Bearer sk-nusa-..." \
  -F "file=@rekaman.mp3" \
  -F "model=$MODEL"

-F membuat curl mengirim multipart/form-data sendiri, dan @ menandai bahwa nilainya adalah berkas lokal. Itu satu-satunya bentuk body yang diterima rute transkripsi.

Batasan yang perlu kamu tahu

Media bukan kemampuan semua model. Hanya model yang punya jalur media yang dilayani, dan model di luar itu dijawab 501 surface_not_served. Penolakan itu datang sebelum saldo ditahan, jadi tidak ada biaya untuk percobaan yang tidak pernah berjalan.

Ketiga penolakan media, termasuk cara membaca kapabilitas dari katalog sebelum memanggil, dibahas di Respons dan kegagalan media.

Respons audio dikirim apa adanya dengan content-type yang diberikan model. Jangan mengandalkan ekstensi berkas yang kamu minta sebagai penentu tipe; baca headernya.