API 사용법음성 합성
음성 합성
POST /v1/audio/speech — 문장을 음성 파일로 바꿉니다.
호출하기
문장을 음성으로 합성합니다. 다른 엔드포인트와 달리 응답이 JSON 이 아니라 오디오 바이트입니다 — res.json() 으로 읽으면 실패합니다. 파일로 저장하거나 그대로 재생하세요.
아래는 Gemini 계열 예제입니다. voice 에 프리셋 목소리 이름을 넣습니다.
curl -X POST https://ai.cubion.kr/v1/audio/speech \
-H "Authorization: Bearer $GATEWAY_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3.1-flash-tts-preview",
"voice": "Kore",
"input": "안녕하세요, 반갑습니다.",
"instructions": "밝고 따뜻한 톤으로 읽어 주세요.",
"response_format": "wav",
"language": "ko-KR"
}' \
--output out.wavimport os
from openai import OpenAI
ai = OpenAI(base_url="https://ai.cubion.kr/v1", api_key=os.environ["GATEWAY_KEY"])
# 응답은 오디오 바이트다 — 스트리밍 응답으로 받아 그대로 파일에 쓴다
with ai.audio.speech.with_streaming_response.create(
model="gemini-3.1-flash-tts-preview",
voice="Kore", # 프리셋 목소리 이름
input="안녕하세요, 반갑습니다.",
instructions="밝고 따뜻한 톤으로 읽어 주세요.",
response_format="wav", # Gemini 는 wav 만
) as res:
res.stream_to_file("out.wav")import OpenAI from "openai";
import { writeFile } from "node:fs/promises";
const ai = new OpenAI({ baseURL: "https://ai.cubion.kr/v1", apiKey: process.env.GATEWAY_KEY });
const audio = await ai.audio.speech.create({
model: "gemini-3.1-flash-tts-preview",
voice: "Kore", // 프리셋 목소리 이름
input: "안녕하세요, 반갑습니다.",
instructions: "밝고 따뜻한 톤으로 읽어 주세요.",
response_format: "wav", // Gemini 는 wav 만
});
// 응답은 오디오 바이트다
await writeFile("out.wav", Buffer.from(await audio.arrayBuffer()));language(예: ko-KR)는 선택입니다. SDK 예제에서는 빼 두었지만, cURL 예제처럼 본문에 함께 보내도 됩니다.
Gemini 와 Fish 의 차이
같은 POST /v1/audio/speech 지만, 어느 계열 모델을 고르느냐에 따라 voice 에 넣는 값과 쓸 수 있는 포맷·길이가 다릅니다. 여기를 헷갈리면 400 이 납니다.
| Gemini | Fish (fish/…) | |
|---|---|---|
| voice | 프리셋 이름 (Kore, Achernar …) | reference_id — 등록된 음성 모델의 id |
| response_format | wav 만 | wav mp3 opus pcm |
| input 길이 | 200자 | 2,000자 |
| instructions | 억양 지시로 반영됨 | 무시됨 |
Fish 계열 호출
Fish 계열(fish/s1 등)은 voice 에 목소리 이름이 아니라 내 음성 모델의 reference_id 를 넣습니다. instructions 를 보내도 반영되지 않고 조용히 버려집니다.
curl -X POST https://ai.cubion.kr/v1/audio/speech \
-H "Authorization: Bearer $GATEWAY_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"fish/s1","voice":"<reference_id>","input":"안녕하세요","response_format":"mp3"}' \
--output out.mp3with ai.audio.speech.with_streaming_response.create(
model="fish/s1",
voice="<reference_id>", # 프리셋 이름이 아니라 등록된 음성 모델의 id
input="안녕하세요",
response_format="mp3", # wav | mp3 | opus | pcm
) as res:
res.stream_to_file("out.mp3")const audio = await ai.audio.speech.create({
model: "fish/s1",
voice: "<reference_id>", // 프리셋 이름이 아니라 등록된 음성 모델의 id
input: "안녕하세요",
response_format: "mp3", // wav | mp3 | opus | pcm
});
await writeFile("out.mp3", Buffer.from(await audio.arrayBuffer()));목소리·포맷 고르기
쓸 수 있는 목소리와 포맷은 모델 목록 에 함께 실려 옵니다 — mode 가 audio_speech 인 항목에 audio 가 붙습니다. 선택 버튼을 손으로 채우지 말고 이 값으로 만드세요.
GET /v1/models 의 audio_speech 항목
{
"id": "fish/s1",
"object": "model",
"mode": "audio_speech",
"audio": {
"voices": ["<reference_id>", "..."],
"formats": ["wav", "mp3", "opus", "pcm"],
"languages": ["ko-KR", "..."]
}
}길이·포맷 제한
규칙을 넘기면 오디오 대신 JSON 오류가 돌아옵니다. 이 두 가지가 실제로 자주 납니다.
| 상태 | error | 언제 |
|---|---|---|
| 400 | input_too_long | input 이 상한을 넘었습니다 — Gemini 200자, Fish 2,000자. 응답의 max 에 상한이 함께 옵니다. 긴 대본은 문장 단위로 나눠 여러 번 부르고 오디오를 이어 붙이세요. |
| 400 | unsupported_format | 그 모델이 못 내는 response_format 입니다. Gemini 에 mp3 를 요청하면 여기서 걸립니다. 응답의 supported 에 쓸 수 있는 포맷이 함께 옵니다. |
보낸 문장은 로그에 남지 않습니다
input 은 요청 로그·미리보기·오류 메시지 어디에도 남기지 않습니다. 기록되는 건 상태 코드·지연과 길이(수)뿐입니다.
model·voice·input중 하나라도 빠지면400입니다.- 목록에 없는 모델을 부르면
403 model_not_allowed입니다. - 그 밖의 상태 코드와 재시도 규칙은 오류 처리 를 보세요.