ListenHub Voice
텍스트, 참조 보이스, 이미지를 음성과 효과음으로 엔드투엔드 비동기 생성하고, 작업을 폴링해 완성된 오디오를 가져옵니다.
ListenHub Voice API는 오디오를 엔드투엔드로 생성합니다 — 일반 내레이션, 효과음, 단일 보이스 음성, 다중 화자 대화, 참조 클립을 이용한 보이스 클로닝, 이미지-투-오디오까지 지원합니다. 생성은 비동기입니다. 요청을 제출한 뒤 완료될 때까지 작업을 폴링(polling)합니다. 모든 엔드포인트는 https://api.marswave.ai/openapi/v1/listenhub-voice 아래에 있으며 Authorization: Bearer $LISTENHUB_API_KEY 로 인증합니다.
ListenHub Voice는 현재 기간 한정 무료입니다. 무료 기간이 끝나면 추후 공지에 따라 과금이 재개됩니다.
모든 응답은 { "code": 0, "message": "", "data": { ... } } 로 감싸집니다. code 가 0이 아니면 오류입니다 — 오류 처리를 참고하세요. 아래 예시는 모두 data 에서 필드를 읽습니다.
모델과 제한
| 항목 | 값 |
|---|---|
model | listenhub-voice-1.0 (기본값이자 유일하게 지원되는 값) |
| 레이트 리밋 | 사용자당 분당 5회, /generate 에 적용 |
text | 최대 1400자 |
voices | 1–3개 항목 (일반 텍스트 / 효과음일 때는 생략) |
durationHint | 1–110초 (크레딧 예상치 + 목표 길이 힌트) |
보이스(voices)
voices 는 누가 말할지를 결정합니다. 각 항목은 다음 두 종류 중 하나입니다.
type | 필수 필드 | 설명 |
|---|---|---|
speaker | id | 내장 보이스 — ListenHub 보이스 코드 또는 플랫폼 voice_type. 이 타입에는 url 을 보내지 마세요. |
reference | url | 보이스를 클로닝할 커스텀 참조 오디오 URL(http/https). 최대 30초, ≤10MB, wav/mp3/pcm/ogg_opus. 이 타입에는 id 를 보내지 마세요. |
다중 화자 대화의 경우 2–3개의 보이스를 나열하고, text 의 각 줄 앞에 @音频1, @音频2, … 를 붙여 순서대로 보이스에 대사를 배정합니다. voices 를 완전히 생략하면 일반 내레이션이나 순수 효과음이 생성됩니다.
voices 와 image 는 함께 쓸 수 없습니다 — 최대 하나만 보내세요. 둘 다 포함한
요청은 거부됩니다. speaker 항목은 id 만, reference 항목은 url 만 담아야
합니다. 섞어서 보내면 33004(잘못된 파라미터)가 반환됩니다.
비동기 작업 라이프사이클
POST /v1/listenhub-voice/generate로 생성 요청을 제출합니다. 응답에는taskId와 초기status인pending이 담깁니다.GET /v1/listenhub-voice/tasks/{taskId}를 폴링합니다.status는pending→generating→uploading→success순으로 진행됩니다.success가 되면audioUrl을 읽고,failed가 되면errorMessage를 읽습니다.
| 상태 | 의미 |
|---|---|
pending | 생성됨. 생성 요청 제출을 대기 중입니다. |
generating | 생성 진행 중. audioUrl 은 아직 사용할 수 없습니다. |
uploading | 생성 완료. 오디오를 스토리지로 전송하는 중입니다. |
success | 완료. audioUrl 을 사용할 수 있습니다. |
failed | 어느 단계에서 실패. errorMessage 가 이유를 설명하며, 예약된 크레딧은 환불됩니다. |
ListenHub Voice 작업 생성
POST /v1/listenhub-voice/generate
텍스트(선택적으로 보이스 또는 참조 이미지 포함)를 제출해 엔드투엔드 오디오 생성을 시작합니다. JSON을 전송합니다. taskId 와 함께 202 를 반환합니다.
# Plain text / sound effects (no voices)
curl -X POST "https://api.marswave.ai/openapi/v1/listenhub-voice/generate" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "A gentle rain falls on a quiet street at midnight.",
"durationHint": 20
}'
# Single voice (speaker)
curl -X POST "https://api.marswave.ai/openapi/v1/listenhub-voice/generate" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Welcome to ListenHub. Here is your daily briefing.",
"voices": [{ "type": "speaker", "id": "zh_female_warm" }]
}'
# Voice cloning from a reference clip
curl -X POST "https://api.marswave.ai/openapi/v1/listenhub-voice/generate" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "@音频1 Hi there! @音频2 Hello, how can I help?",
"voices": [
{ "type": "reference", "url": "https://example.com/host.mp3" },
{ "type": "speaker", "id": "zh_male_calm" }
]
}'const response = await fetch(
'https://api.marswave.ai/openapi/v1/listenhub-voice/generate',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: 'Welcome to ListenHub. Here is your daily briefing.',
voices: [{ type: 'speaker', id: 'zh_female_warm' }],
durationHint: 20,
}),
},
)
const { data } = await response.json()
console.log('Task ID:', data.taskId)import os
import requests
response = requests.post(
'https://api.marswave.ai/openapi/v1/listenhub-voice/generate',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'text': 'Welcome to ListenHub. Here is your daily briefing.',
'voices': [{'type': 'speaker', 'id': 'zh_female_warm'}],
'durationHint': 20,
},
)
data = response.json()['data']
print('Task ID:', data['taskId'])이미지-투-오디오의 경우 voices 대신 image 객체를 보냅니다(둘은 함께 쓸 수 없습니다).
{
"text": "Describe this scene as a short narrated clip.",
"image": { "url": "https://example.com/scene.jpg" }
}요청 파라미터:
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
model | string | 아니요 | listenhub-voice-1.0. 기본값은 listenhub-voice-1.0 |
text | string | 예 | 읽어줄 스크립트. 최대 1400자. @音频N 접두사로 대화 대사를 보이스에 배정합니다 |
voices | array | 아니요 | 1–3개의 보이스 항목(보이스 참고). 일반 텍스트 / 효과음일 때는 생략합니다. image 와 함께 쓸 수 없습니다 |
image | object | 아니요 | 이미지-투-오디오용 참조 이미지. url(http/https) 또는 data(Base64, data:image/...;base64, 접두사 포함 가능) 중 정확히 하나만 지정합니다. 이미지 1장, ≤10MB, jpeg/png/webp. voices 와 함께 쓸 수 없습니다 |
audioConfig | object | 아니요 | 출력 튜닝(아래 참고) |
durationHint | number | 아니요 | 목표 길이, 1–110 초. 크레딧 예상치를 결정하고 모델에 힌트를 줍니다 |
watermark | boolean | 아니요 | 오디오 워터마크 추가 |
audioConfig 필드:
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
speechRate | number | 아니요 | 말하기 속도, -50–100 |
loudnessRate | number | 아니요 | 음량, -50–100 |
pitchRate | number | 아니요 | 음높이, -12–12 |
format | string | 아니요 | mp3(기본값), wav, pcm, ogg_opus |
202 를 반환합니다.
{
"code": 0,
"message": "",
"data": {
"taskId": "68e780390fc5c9a54f695a7e",
"status": "pending"
}
}단일 작업 조회
GET /v1/listenhub-voice/tasks/{taskId}
단일 작업을 가져옵니다. 생성 요청을 제출한 뒤 폴링하는 엔드포인트입니다.
curl -X GET "https://api.marswave.ai/openapi/v1/listenhub-voice/tasks/{taskId}" \
-H "Authorization: Bearer $LISTENHUB_API_KEY"const response = await fetch(
`https://api.marswave.ai/openapi/v1/listenhub-voice/tasks/${taskId}`,
{ headers: { Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}` } },
)
const { data } = await response.json()
console.log('Status:', data.status)
if (data.status === 'success') console.log('Audio:', data.audioUrl)import os
import requests
response = requests.get(
f'https://api.marswave.ai/openapi/v1/listenhub-voice/tasks/{task_id}',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
)
data = response.json()['data']
print('Status:', data['status'])
if data['status'] == 'success':
print('Audio:', data['audioUrl'])성공한 작업:
{
"code": 0,
"message": "",
"data": {
"id": "68e780390fc5c9a54f695a7e",
"status": "success",
"model": "listenhub-voice-1.0",
"params": {
"text": "Welcome to ListenHub. Here is your daily briefing.",
"voices": [{ "type": "speaker", "id": "zh_female_warm" }]
},
"audioUrl": "https://assets.listenhub.ai/listenhub-voice/68e780390fc5c9a54f695a7e.mp3",
"audioDuration": 18.4,
"creditCharged": 12,
"creditRefunded": 0,
"createdAt": 1730000000000,
"updatedAt": 1730000040000
}
}작업 응답 필드:
| 필드 | 타입 | 설명 |
|---|---|---|
id | string | 작업 ID |
status | string | pending, generating, uploading, success, failed 중 하나 |
model | string | listenhub-voice-1.0 |
params | object | 제출한 요청의 에코(민감한 이미지/오디오 페이로드는 제거됨. 인라인 이미지는 { "hasData": true } 로 표시되며 thumbnailUrl 이 붙을 수 있음) |
audioUrl | string | 완성된 오디오 URL. status 가 success 일 때만 반환 |
audioDuration | number | 오디오 길이(초 단위, 과금 기준 길이) |
creditCharged | number | 실제로 청구된 크레딧(아직 청구되지 않았으면 0) |
creditRefunded | number | 실패 시 환불된 크레딧(정산 대조용) |
errorMessage | string | 실패 사유. status 가 failed 일 때만 반환 |
createdAt | number | 생성 시각(밀리초 타임스탬프) |
updatedAt | number | 최종 수정 시각(밀리초 타임스탬프) |
작업 목록 조회
GET /v1/listenhub-voice/tasks
내 ListenHub Voice 작업을 최신순으로 나열합니다.
curl -X GET "https://api.marswave.ai/openapi/v1/listenhub-voice/tasks?page=1&pageSize=20&status=success" \
-H "Authorization: Bearer $LISTENHUB_API_KEY"const response = await fetch(
'https://api.marswave.ai/openapi/v1/listenhub-voice/tasks?page=1&pageSize=20',
{ headers: { Authorization: `Bearer ${process.env.LISTENHUB_API_KEY}` } },
)
const { data } = await response.json()
console.log(`${data.total} tasks, showing ${data.items.length}`)import os
import requests
response = requests.get(
'https://api.marswave.ai/openapi/v1/listenhub-voice/tasks',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
params={'page': 1, 'pageSize': 20},
)
data = response.json()['data']
print(data['total'], 'tasks, showing', len(data['items']))쿼리 파라미터:
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
page | integer | 아니요 | 페이지 번호, 최소 1. 기본값은 1 |
pageSize | integer | 아니요 | 페이지당 항목 수, 1–100. 기본값은 20 |
status | string | 아니요 | pending, generating, uploading, success, failed 로 필터링 |
keyword | string | 아니요 | 작업의 text 에 대한 부분 일치 검색. 최대 64자 |
응답:
{
"code": 0,
"message": "",
"data": {
"items": [
{
"id": "68e780390fc5c9a54f695a7e",
"status": "success",
"model": "listenhub-voice-1.0",
"audioUrl": "https://assets.listenhub.ai/listenhub-voice/68e780390fc5c9a54f695a7e.mp3",
"audioDuration": 18.4,
"creditCharged": 12,
"creditRefunded": 0,
"createdAt": 1730000000000,
"updatedAt": 1730000040000
}
],
"page": 1,
"pageSize": 20,
"total": 1
}
}각 item은 단일 작업 조회와 동일한 필드를 가집니다.
오류
비즈니스 오류는 HTTP 400 과 함께 최상위 code 필드에 구체적인 코드를 담아 반환됩니다.
| 코드 | 의미 |
|---|---|
33001 | 작업을 찾을 수 없음(또는 현재 API 사용자 소유가 아님) |
33002 | speaker 보이스 항목에 해당하는 화자를 찾을 수 없음 |
33003 | 생성 서비스를 사용할 수 없음 |
33004 | 잘못된 파라미터(예: voices 와 image 를 함께 전송, 보이스 항목에 id 와 url 혼용) |
33005 | 보이스가 너무 많음(최대 3개) |
33006 | 크레딧 부족 |
33007 | 레이트 리밋 초과 |
33008 | 생성 타임아웃 |
33009 | 사용자별 동시 실행 한도 도달 |
| HTTP 상태 | 의미 |
|---|---|
400 | 잘못된 파라미터 또는 비즈니스 오류 — 위의 33xxx 코드를 참고 |
429 | 레이트 리밋 초과(/generate 에서 사용자당 5 RPM) |
크레딧
크레딧은 제출 시점에 예약되고, success 시 확정되며, 실패 시 자동으로 환불됩니다. 각 작업은 정산 대조를 위해 creditCharged(실제 청구액)와 creditRefunded(실패 시 환불액)를 보고합니다. 과금 기준 길이는 audioDuration 입니다. 실시간 잔액은 GET /v1/user/subscription으로 확인하고, 크레딧과 기능의 대응 관계는 요금을 참고하세요.