SDK 레퍼런스
OpenAPIClient와 ListenHubClient의 모든 메서드를 제품별로 묶어 시그니처, 엔드포인트, 반환값과 함께 정리했습니다.
@marswave/listenhub-sdk의 전체 메서드 레퍼런스입니다. 메서드는 제품별로 묶여 있습니다. 각 항목에는 시그니처, 동작을 설명하는 한 줄, 그리고 내부에서 호출되는 HTTP 엔드포인트가 적혀 있습니다.
SDK에는 두 개의 클라이언트가 들어 있습니다. 응답 처리는 공통이지만(code 0이면 data를 언래핑하고, 그렇지 않으면 ListenHubError를 던지며, 429는 자동 재시도) 대상 API 면과 인증 방식이 다릅니다.
OpenAPIClient | ListenHubClient | |
|---|---|---|
| 인증 | API 키(Authorization: Bearer) | OAuth 사용자 액세스 토큰 |
| Base URL | https://api.marswave.ai/openapi | https://api.listenhub.ai/api |
| 실행 주체 | 내 계정 / 키 소유자 | 로그인한 사용자 |
| 용도 | 서버, 스크립트, CI | 사용자 대상 앱 |
OpenAPIClient는 공개 OpenAPI 제품이며, 이 레퍼런스의 중심입니다. ListenHubClient는 퍼스트파티 앱이 사용하는 OAuth 클라이언트이고, 해당 메서드는 문서 끝의 ListenHubClient 메서드에 정리되어 있습니다.
생성은 비동기입니다. create* 호출은 즉시 id를 반환합니다. 대응하는 get* 메서드를 폴링해서 processStatus(또는 태스크의 status)가 pending / generating에서 벗어날 때까지 기다리세요. 전체 루프는 퀵스타트를 참고하세요.
아래에서 사용하는 표기 규칙:
- "엔드포인트" 경로는 클라이언트 base URL 기준 상대 경로입니다.
OpenAPIClient의 경우https://api.marswave.ai/openapi/입니다. - 메서드는 언래핑된
data페이로드를 반환합니다.{ code, message, data }엔벨로프는 SDK가 처리합니다. - 일부 메서드는 원시
Response(바이너리 또는 스트림)를 반환하며, 해당 항목에는 명시해 두었습니다. - 크레딧 비용을 하드코딩하지 마세요. 대신
estimate*Credits메서드와getSubscription()을 사용하세요.
OpenAPIClient 메서드
Speakers(보이스)
보이스는 speakerId로 식별됩니다. 에피소드를 만들기 전에 보이스 목록을 조회하고 사용할 id를 전달하세요.
| 메서드 | 엔드포인트 | 반환값 | 설명 |
|---|---|---|---|
listSpeakers(params?) | GET v1/speakers/list | { items: OpenAPISpeaker[] } | 사용 가능한 보이스 |
listSpeakers 파라미터:
| 파라미터 | 타입 | 설명 |
|---|---|---|
language | string | 언어로 필터링. 예: en, zh, ja |
status | number | 이용 가능 여부 필터 |
각 OpenAPISpeaker는 speakerId, name, gender, language, demoAudioUrl, 그리고 선택적인 profile(pitch, speed, traits, styles, scenes, accent, description)을 가집니다.
const { items } = await client.listSpeakers({ language: 'en' });
const speakerId = items[0].speakerId;ListenHub Voice(AI 음성)
엔드투엔드 음성 생성(listenhub-voice-1.0)입니다. 단순 내레이션, 효과음, 단일 보이스 발화, 다중 화자 대화, 참조 클립 기반 보이스 클로닝, 이미지 기반 음성 생성을 지원합니다. 태스크를 만든 뒤 status가 success가 될 때까지 getListenHubVoiceTask를 폴링하세요.
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createListenHubVoice(params) | POST v1/listenhub-voice/generate | { taskId, status } |
getListenHubVoiceTask(taskId) | GET v1/listenhub-voice/tasks/{taskId} | OpenAPIListenHubVoiceTaskDetail |
listListenHubVoiceTasks(params?) | GET v1/listenhub-voice/tasks | { items, page, pageSize, total } |
createListenHubVoice 파라미터(OpenAPICreateListenHubVoiceParams):
| 파라미터 | 타입 | 설명 |
|---|---|---|
model | 'listenhub-voice-1.0' | 선택. 기본값은 listenhub-voice-1.0 |
text | string | 필수. 최대 1400자. 줄 앞에 @音频1 / @音频2를 붙여 여러 보이스에 배분 |
voices | Array<{ type: 'speaker'; id: string } | { type: 'reference'; url: string }> | 1~3개. 일반 텍스트 / 효과음이면 생략. image와 상호 배타 |
image | { url?: string; data?: string } | 이미지 기반 음성 생성용 참조 이미지. url(http/https) 또는 data(Base64) 중 하나를 전달. voices와 상호 배타 |
audioConfig | { speechRate?; loudnessRate?; pitchRate?; format? } | speechRate / loudnessRate는 -50–100, pitchRate는 -12–12, format은 'mp3' | 'wav' | 'pcm' | 'ogg_opus'(기본 mp3) |
durationHint | number | 목표 길이 1–110초. 크레딧 견적에 반영 |
watermark | boolean | 오디오 워터마크 추가 |
speaker 항목은 내장 보이스(ListenHub 보이스 코드 또는 플랫폼 voice_type)를 참조하고, reference 항목은 공개 오디오 URL에서 보이스를 클로닝합니다. 다중 화자 대화는 2~3개 보이스를 나열하고, 배열 순서대로 @音频N 접두사로 각 줄을 배정합니다.
OpenAPIListenHubVoiceTaskDetail에는 id, status(pending → generating → uploading → success | failed), model, params(정제된 에코), audioUrl(success일 때), audioDuration(과금 기준 길이), creditCharged, creditRefunded, errorMessage(failed일 때), createdAt, updatedAt이 포함됩니다. listListenHubVoiceTasks는 { page?, pageSize?, status?, keyword? }를 받습니다.
const { taskId } = await client.createListenHubVoice({
text: 'Welcome to ListenHub. Here is your daily briefing.',
voices: [{ type: 'speaker', id: 'zh_female_warm' }],
durationHint: 20,
});
let task = await client.getListenHubVoiceTask(taskId);
while (task.status !== 'success' && task.status !== 'failed') {
await new Promise((r) => setTimeout(r, 3000));
task = await client.getListenHubVoiceTask(taskId);
}
if (task.status === 'success') console.log(task.audioUrl);Voice Cloning(보이스 클로닝)
참조 오디오를 재사용 가능한 개인 보이스로 만듭니다. 업로드하고, 폴링하고, 확정하면 생성된 speakerId를 speech, tts, audioSpeech에서 사용할 수 있습니다. 업로드 방식만 지원하며, 대화형 녹음 플로우는 웹 전용입니다.
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createVoiceClone(params) | POST v1/voice-clone/clone | { taskId, status } |
getVoiceCloneTask(taskId) | GET v1/voice-clone/clone/{taskId} | OpenAPIVoiceCloneTaskDetail |
confirmVoiceClone(params) | POST v1/voice-clone/confirm | { speakerId } |
listVoiceCloneSpeakers() | GET v1/voice-clone/speakers | { speakers, quota, isLimitReached, maxSpeakers, remainingConfirmations } |
getVoiceCloneSpeaker(speakerId) | GET v1/voice-clone/speakers/{speakerId} | OpenAPIVoiceCloneSpeaker |
updateVoiceCloneSpeaker(speakerId, params) | PUT v1/voice-clone/speakers/{speakerId} | OpenAPIVoiceCloneSpeaker |
deleteVoiceCloneSpeaker(speakerId) | DELETE v1/voice-clone/speakers/{speakerId} | { speakerId } |
createVoiceClone 파라미터(OpenAPICreateVoiceCloneParams) — SDK가 멀티파트로 전송합니다:
| 파라미터 | 타입 | 설명 |
|---|---|---|
audioFiles | Blob[] | 필수. 1~6개 파일, 단일 파일 ≤5MB, 합계 ≤20MB |
audioFilenames | string[] | 선택적 파일명. 위치 순서대로 매칭 |
language | 'zh' | 'en' | 'ja' | 필수 |
consentConfirmed | true | 필수. 클로닝 대상자의 동의를 보유하고 있음을 선언하며, 태스크와 함께 저장됨 |
autoConfirm | boolean | 클로닝 완료를 감지한 폴링 안에서 바로 확정. name과 gender 필요 |
name | string | 보이스 이름, 최대 50자 |
gender | 'male' | 'female' | 'other' | 보이스 성별 |
useCredits | boolean | 무료 쿼터 소진 후 300 크레딧 과금을 승인. 기본값 false |
OpenAPIVoiceCloneTaskDetail의 최종 형태는 세 가지입니다. errorCode / errorMessage를 동반한 status: 'failed', demoAudioUrl은 있고 speakerId는 없는 status: 'completed'(자동 확정이 보이스를 저장하지 못한 경우 confirmError 포함), 또는 speakerId가 존재해 보이스가 저장되었음을 뜻하는 형태입니다. 같은 계정에서 확정이 동시에 발생하면 429, 의존 서비스를 사용할 수 없으면 503이 반환됩니다. 둘 다 재시도 가능하며 과금되지 않습니다.
const { taskId } = await client.createVoiceClone({
audioFiles: [referenceBlob],
audioFilenames: ['reference.mp3'],
language: 'en',
consentConfirmed: true,
});
let task = await client.getVoiceCloneTask(taskId);
while (task.status === 'pending' || task.status === 'processing') {
await new Promise((r) => setTimeout(r, 5000));
task = await client.getVoiceCloneTask(taskId);
}
if (task.status === 'failed') throw new Error(task.errorMessage);
const { speakerId } = await client.confirmVoiceClone({
taskId,
name: 'My API Voice',
gender: 'female',
});
await client.speech({ scripts: [{ content: 'Hello from my own voice.', speakerId }] });Podcast(팟캐스트)
팟캐스트는 query와 sources로부터 생성되는 다중 화자 대화입니다. 생성한 뒤 processStatus가 success가 될 때까지 getPodcast를 폴링하세요.
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createPodcast(params) | POST v1/podcast/episodes | { episodeId } |
getPodcast(episodeId) | GET v1/podcast/episodes/{episodeId} | OpenAPIPodcastDetail |
createPodcastTextContent(params) | POST v1/podcast/episodes/text-content | { episodeId, message } |
generatePodcastAudio(episodeId, params?) | POST v1/podcast/episodes/{episodeId}/audio | { success, message, episodeId, status } |
getPodcastTextStream(episodeId, event) | GET v1/podcast/episodes/{episodeId}/text-stream | 원시 Response(스트림) |
createPodcast 파라미터(OpenAPICreatePodcastParams):
| 파라미터 | 타입 | 설명 |
|---|---|---|
query | string | 에피소드가 다룰 내용 |
sources | Array<{ type: 'text' | 'url'; content: string }> | 근거 자료. 원문 텍스트 또는 페이지 URL |
speakers | Array<{ speakerId: string }> | 필수. 보이스 하나당 항목 하나 |
language | string | 출력 언어 |
mode | string | 생성 깊이(예: quick, deep) |
텍스트 먼저, 오디오 나중의 2단계 플로우를 쓰면 오디오 비용을 내기 전에 스크립트를 검토하거나 수정할 수 있습니다. createPodcastTextContent는 스크립트만 생성하고, 이어서 generatePodcastAudio(episodeId, { scripts })가 (원한다면 직접 고친) 스크립트로 오디오를 렌더링합니다. getPodcastTextStream(episodeId, 'script' | 'outline')은 실시간 스크립트/아웃라인 토큰을 위한 스트리밍 Response를 반환합니다.
OpenAPIPodcastDetail에는 processStatus, title, outline, cover, audioUrl, audioStreamUrl, subtitlesUrl, scripts(화자별 대사), credits, 그리고 실패 시 failCode가 포함됩니다.
Flow Speech / TTS
Flow speech는 텍스트나 URL을 하나 이상의 보이스로 내레이션한 오디오로 만듭니다. TTS 엔드포인트는 더 저수준으로, 명시적으로 준 스크립트를 음성으로 합성합니다.
Flow speech
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createFlowSpeech(params) | POST v1/flow-speech/episodes | { episodeId } |
getFlowSpeech(episodeId) | GET v1/flow-speech/episodes/{episodeId} | OpenAPIFlowSpeechDetail |
createFlowSpeechTTS(params) | POST v1/flow-speech/episodes/tts | { episodeId } |
getFlowSpeechTextStream(episodeId, event) | GET v1/flow-speech/episodes/{episodeId}/text-stream | 원시 Response(스트림) |
createFlowSpeech 파라미터(OpenAPICreateFlowSpeechParams):
| 파라미터 | 타입 | 설명 |
|---|---|---|
sources | Array<{ type: 'text' | 'url'; content?: string; uri?: string }> | 필수. 텍스트는 content, URL은 uri |
speakers | Array<{ speakerId: string }> | 필수 |
language | string | 출력 언어 |
mode | 'smart' | 'direct' | smart는 내레이션에 맞게 다시 쓰고, direct는 원문 그대로 읽음 |
createFlowSpeechTTS는 scripts: Array<{ content: string; speakerId: string }>와 선택적인 title을 받아 각 줄을 그대로 렌더링합니다. 텍스트 스트림의 event는 'script' | 'outline'입니다.
OpenAPIFlowSpeechDetail에는 processStatus, title, outline, cover, audioUrl, audioStreamUrl, subtitlesUrl, scripts, sourceProcessResult가 포함됩니다.
TTS / speech
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
speech(params) | POST v1/speech | OpenAPISpeechResponse |
tts(params) | POST v1/tts | 원시 Response(오디오 바이트) |
audioSpeech(params) | POST v1/audio/speech | 원시 Response(오디오 바이트) |
speech는 scripts: Array<{ content: string; speakerId: string }>를 받아 동기적으로 결과를 반환합니다: { audioUrl, audioDuration, subtitlesUrl?, taskId, credits }.
tts와 audioSpeech는 OpenAI 호환 단일 보이스 합성입니다. { input, voice, response_format? }를 받으며, response_format은 mp3(기본), opus, aac, flac, wav, pcm 중 하나입니다. 오디오는 원시 Response로 반환되므로 .arrayBuffer()로 읽거나 .body를 스트리밍하세요.
const res = await client.tts({ input: 'Hello world', voice: speakerId, response_format: 'mp3' });
const audio = Buffer.from(await res.arrayBuffer());Storybook(설명 영상 / 슬라이드)
Storybook은 페이지 기반 시각 콘텐츠, 즉 설명 영상과 슬라이드 덱을 만듭니다. mode로 형식을 고릅니다. 오디오는 skipAudio로 끌 수 있는 선택 사항입니다.
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createStorybook(params) | POST v1/storybook/episodes | { episodeId } |
getStorybook(episodeId) | GET v1/storybook/episodes/{episodeId} | OpenAPIStorybookDetail |
generateStorybookVideo(episodeId) | POST v1/storybook/episodes/{episodeId}/video | { success } |
createStorybook 파라미터(OpenAPICreateStorybookParams):
| 파라미터 | 타입 | 설명 |
|---|---|---|
sources | Array<{ type: 'text' | 'url'; content: string }> | 필수 근거 자료 |
speakers | Array<{ speakerId: string }> | 선택. 오디오를 생성할 때만 필요 |
mode | 'info' | 'story' | 'slides' | 덱은 slides, 설명 영상은 info / story |
skipAudio | boolean | true면 시각 자료만 출력 |
style | string | 비주얼 스타일 힌트 |
language | string | 출력 언어 |
에피소드가 성공한 뒤 generateStorybookVideo(episodeId)를 호출하면 페이지들로부터 다운로드 가능한 영상을 렌더링합니다. getStorybook을 폴링하며 videoStatus(not_generated → pending → success / fail)를 확인하세요. 성공하면 videoUrl이 채워집니다.
OpenAPIStorybookDetail에는 mode, processStatus, title, cover, audioUrl, audioDuration, videoUrl, videoStatus, 그리고 pages(각각 text, pageNumber, imageUrl, audioTimestamp 포함)가 들어 있습니다.
Image(이미지)
단일 호출 이미지 생성입니다. OpenAPIClient에는 별도의 폴링 메서드가 없으며, 응답에 결과가 담겨 옵니다.
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createImage(params) | POST v1/images/generation | OpenAPICreateImageResponse |
createImage 파라미터(OpenAPICreateImageParams):
| 파라미터 | 타입 | 설명 |
|---|---|---|
provider | string | 필수 이미지 프로바이더 |
model | string | 프로바이더 모델 |
prompt | string | 필수 텍스트 프롬프트 |
referenceImages | Array<{ fileData?; inlineData? }> | 참조 이미지 — fileData: { fileUri, mimeType } 또는 inlineData: { data, mimeType }(base64) |
imageConfig | { imageSize?; aspectRatio? } | imageSize: 1K | 2K | 4K, aspectRatio: 16:9 | 4:3 | 1:1 | 3:4 | 9:16 | 21:9 |
Video(SeeDance / HappyHorse + PixVerse)
두 비디오 계열은 폴링 / 목록 / 견적 메서드는 공유하지만, 생성 메서드와 파라미터가 다릅니다. SeeDance(doubao-seedance-2-*)와 HappyHorse는 content 배열을 쓰고, PixVerse는 capability 기반 형태를 씁니다.
SeeDance / HappyHorse
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createVideoGeneration(params) | POST v1/video-generation/generate | { taskId, status } |
getVideoGenerationTask(taskId) | GET v1/video-generation/tasks/{taskId} | OpenAPIVideoGenerationTaskDetail |
listVideoGenerationTasks(params?) | GET v1/video-generation/tasks | { items, page, pageSize, total } |
estimateVideoCredits(params) | POST v1/video-generation/estimate-credits | { tokens, credits } |
createVideoGeneration 파라미터(OpenAPICreateVideoGenerationParams):
| 파라미터 | 타입 | 설명 |
|---|---|---|
model | 'doubao-seedance-2-pro' | 'doubao-seedance-2-fast' | 'happyhorse' | 기본값은 SeeDance 모델 |
content | VideoContentItem[] | 필수. 텍스트 / 이미지 / 비디오 / 오디오 항목 혼합(아래 참고) |
resolution | '480p' | '720p' | '1080p' | 1080p는 doubao-seedance-2-pro에서만, happyhorse는 480p 없음 |
ratio | '16:9' | '4:3' | '1:1' | '3:4' | '9:16' | '21:9' | '4:5' | '5:4' | 4:5 / 5:4는 happyhorse에서만 |
duration | number | 초 단위. SeeDance 최소 4, HappyHorse 최소 3 |
generateAudio | boolean | 오디오 트랙 생성 |
seed | number | 재현성을 위한 시드 |
inputVideoDuration | number | 비디오 편집 입력용. SeeDance [2,15], HappyHorse [3,60] |
audioSetting | 'auto' | 'origin' | HappyHorse 비디오 편집 전용. content에 video_url이 있을 때 |
각 content 항목은 다음 중 하나입니다:
{ type: 'text', text }{ type: 'image_url', image_url: { url }, role: 'first_frame' \| 'last_frame' \| 'reference_image' }{ type: 'video_url', video_url: { url }, role: 'reference_video' }{ type: 'audio_url', audio_url: { url }, role: 'reference_audio' }
HappyHorse는 last_frame과 audio_url 콘텐츠를 거부합니다. estimateVideoCredits는 { model, resolution, duration, hasVideoInput?, inputVideoDuration?, ratio? }를 받습니다.
const { taskId } = await client.createVideoGeneration({
model: 'doubao-seedance-2-pro',
content: [{ type: 'text', text: 'A timelapse of a city at dusk' }],
resolution: '1080p',
duration: 5,
});PixVerse
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createPixVerseVideoGeneration(params) | POST v1/video-generation/pixverse/generate | { taskId, episodeId?, status } |
estimatePixVerseVideoCredits(params) | POST v1/video-generation/pixverse/estimate-credits | { tokens, credits } |
PixVerse 태스크의 폴링과 목록 조회는 위의 getVideoGenerationTask / listVideoGenerationTasks 메서드를 그대로 사용합니다.
createPixVerseVideoGeneration 파라미터(OpenAPICreatePixVerseVideoParams):
| 파라미터 | 타입 | 설명 |
|---|---|---|
capability | 'text_to_video' | 'image_to_video' | 'transition' | 'multi_transition' | 'fusion' | 'restyle' | 'mimic' | 'lip_sync' | 'agent' | 필수. 생성 모드를 선택 |
model | 'pixverse' | 'v6' | 'v5' | 'v4.5' | PixVerse 모델 버전 |
language | 'zh' | 'en' | 서비스 리전. en은 국제, zh는 중국 본토 |
prompt | string | 텍스트 프롬프트 |
duration | number | 초 단위(1–60, agent는 20/30/60) |
aspectRatio | '9:16' | '16:9' | '1:1' | '4:3' | '3:4' | 출력 종횡비 |
quality | '360p' | '540p' | '720p' | '1080p' | mimic은 720p 고정, agent는 720p/1080p 필요 |
sourceTaskId | string | 이전에 성공한 태스크를 재사용(restyle / lip_sync) |
images / videos / audios | Array<{ url; duration? }> | 입력 애셋 |
pixverse | OpenAPIPixVerseOptions | capability별 중첩 옵션 |
중첩된 pixverse 객체는 agentType, motionMode, cameraMovement, templateId, multiTransition, imageReferences, tts, soundEffect*, lipSyncTts*, brandSticker, introOutroClip을 다룹니다. 어떤 항목이 유효한지는 capability에 따라 달라집니다. estimatePixVerseVideoCredits는 견적용으로 축소된 pixverse 형태와 함께 { capability, model?, language?, duration?, quality?, pixverse? }를 받습니다.
Music(음악)
음악 엔드포인트는 기본적으로 Mureka 프로바이더를 사용합니다. 비동기 엔드포인트는 태스크({ taskId, taskType, status })를 반환하므로 getMusicTask를 폴링하세요. 동기 엔드포인트(recognizeMusic, describeMusic, stemMusic)는 결과를 곧바로 반환합니다.
| 메서드 | 엔드포인트 | 반환값 | 동기 여부 |
|---|---|---|---|
createMusicGenerate(params) | POST v1/music/generate | CreateMusicTaskResponse | 비동기 |
createMusicCover(params) | POST v1/music/cover | CreateMusicTaskResponse | 비동기(deprecated) |
createMusicExtend(params) | POST v1/music/extend | CreateMusicTaskResponse | 비동기 |
createMusicRemix(params) | POST v1/music/remix | CreateMusicTaskResponse | 비동기 |
createMusicInstrumental(params) | POST v1/music/instrumental | CreateMusicTaskResponse | 비동기 |
createMusicSoundtrack(params) | POST v1/music/soundtrack | CreateMusicTaskResponse | 비동기 |
createMusicTrack(params) | POST v1/music/track | CreateMusicTaskResponse | 비동기 |
recognizeMusic(params) | POST v1/music/recognize | RecognizeMusicResponse | 동기 |
describeMusic(params) | POST v1/music/describe | DescribeMusicResponse | 동기 |
stemMusic(params) | POST v1/music/stem | StemMusicResponse | 동기 |
getMusicTask(taskId) | GET v1/music/tasks/{taskId} | MusicTaskDetail | — |
listMusicTasks(params?) | GET v1/music/tasks | { items, page, pageSize, total } | — |
주요 생성 파라미터:
createMusicGenerate:{ prompt?, lyrics?, model?, style?, title?, instrumental?, vocalId? }.model은auto,mureka-7.6,mureka-8,mureka-9,mureka-o2중 하나입니다.createMusicExtend:{ uploadUrl, model, continueAt, prompt?, style?, title?, instrumental?, negativeTags?, vocalGender?, styleWeight?, weirdnessConstraint?, audioWeight? }. 여기서model은 Suno 버전(V4,V4_5,V4_5PLUS,V4_5ALL,V5,V5_5)입니다.createMusicRemix:{ audio?, audioFilename?, audioUrl?, providerSongId?, lyrics, prompt }.audio/audioUrl/providerSongId중 정확히 하나만 전달하세요. deprecated된createMusicCover대신 이 메서드를 쓰세요.createMusicInstrumental:{ prompt?, referenceAudio?, referenceAudioFilename?, model? }.prompt또는referenceAudio중 하나만 전달하세요.createMusicSoundtrack:{ image?, video?, prompt?, model? }.image또는video중 하나만 전달하세요.createMusicTrack:{ audio?, providerSongId?, generateType, prompt, lyrics?, vocalGender?, generateStart?, generateEnd? }.generateType으로 스템을 고르며(Vocals,Instrumental,Drums, …),generateType이Vocals일 때는lyrics가 필수입니다.
멀티파트 엔드포인트(remix, instrumental, soundtrack, track, recognize, describe, stem)는 오디오 / 이미지 / 비디오 필드로 Blob/File을 받습니다. Node 20+에서는 버퍼를 new Blob([buffer])로 감싸세요.
const { taskId } = await client.createMusicGenerate({
prompt: 'lo-fi hip hop, mellow, rainy night',
model: 'auto',
});
let task = await client.getMusicTask(taskId);
while (task.status === 'pending' || task.status === 'generating') {
await sleep(10_000);
task = await client.getMusicTask(taskId);
}
console.log(task.tracks[0]?.audioUrl);동기 결과: recognizeMusic은 타임스탬프가 붙은 lyricsSections를, describeMusic은 { description, tags, genres, instruments }를, stemMusic은 { zipUrl, midiZipUrl, expiresAt }을 반환합니다(링크는 약 24시간 뒤 만료).
Content Extract(콘텐츠 추출)
URL에서 읽을 수 있는 콘텐츠(본문 텍스트, 선택적 요약)를 추출합니다. 비동기이므로 생성한 뒤 폴링하세요.
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createContentExtract(params) | POST v1/content/extract | { taskId } |
getContentExtract(taskId) | GET v1/content/extract/{taskId} | OpenAPIContentExtractDetail |
createContentExtract 파라미터(OpenAPICreateContentExtractParams):
| 파라미터 | 타입 | 설명 |
|---|---|---|
source | { type: 'url'; uri: string } | 필수. 추출할 페이지 |
options | { summarize?; maxLength?; twitter? } | summarize는 요약을 추가하고, twitter: { count? }로 스레드 깊이를 지정 |
status가 completed가 될 때까지 getContentExtract를 폴링하세요. 상세 결과에는 data.content, data.metadata, data.references, credits가 담깁니다.
Subscription(구독)
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
getSubscription() | GET v1/user/subscription | OpenAPISubscriptionInfo |
크레딧 잔액과 플랜 정보를 반환합니다: totalAvailableCredits, 월간/영구/기간 한정 크레딧 내역, resetAt, renewStatus, paidStatus, subscriptionPlan. 비용이 큰 작업을 시작하기 전에 totalAvailableCredits로 잔액을 확인하세요.
Files(파일)
OpenAPIClient에는 전용 파일 업로드 메서드가 없습니다. 로컬 파일을 입력으로 쓰려면 공개 URL로 호스팅한 뒤 그 URL을 전달하세요(예: source의 uri, referenceImages.fileData.fileUri, 비디오의 image_url.url). 사전 서명 업로드 플로우는 ListenHubClient에 있습니다 — 아래 Files를 참고하세요.
ListenHubClient 메서드
ListenHubClient는 OAuth 사용자 액세스 토큰으로 인증하며 https://api.listenhub.ai/api를 대상으로 합니다. 퍼스트파티 앱 면을 노출합니다. 제품 형태는 OpenAPIClient와 다릅니다. 에피소드는 중첩된 template 객체를 사용하고, 생성 메서드는 제품별(팟캐스트, TTS, 설명 영상, 슬라이드)로 나뉩니다. 각 요청이 개별 로그인 사용자 명의로 실행될 때만 이 클라이언트를 쓰세요.
Auth & session(인증 및 세션)
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
connectInit(params) | POST v1/auth/connect/init | { sessionId, authUrl } |
connectToken(params) | POST v1/auth/connect/token | { accessToken, refreshToken, expiresIn } |
refresh(params) | POST v1/auth/token | { accessToken, refreshToken, expiresIn } |
revoke(params) | POST v1/auth/token/revoke | void |
connectInit({ callbackPort })는 디바이스/OAuth 플로우를 시작하고 열어야 할 authUrl을 반환합니다. connectToken({ sessionId, code })는 그 결과를 토큰으로 교환합니다. refresh({ refreshToken })은 만료되어 가는 액세스 토큰을 갱신하고, revoke({ refreshToken })은 토큰을 무효화합니다.
Speakers(보이스)
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
listSpeakers(params?) | GET v1/settings/speakers | { items: Speaker[] } |
파라미터: { language?, status? }. 반환 형태가 OpenAPIClient와 다르다는 점에 유의하세요. 각 Speaker는 speakerId가 아니라 speakerInnerId를 노출하며, personality, accessType, weight도 함께 제공합니다. 에피소드의 template.speakers 배열이 기대하는 값은 speakerInnerId입니다.
Voice Cloning(보이스 클로닝)
로그인한 사용자를 위한 퍼스트파티 클로닝 플로우입니다. 참조 오디오를 업로드하고, 폴링한 뒤, 개인 보이스로 확정합니다. 이 면은 zh와 en만 지원하며, confirmVoiceClone은 페이로드 없이 완료되므로 스피커 ID는 listVoiceCloneSpeakers에서 읽어야 합니다.
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createVoiceClone(params) | POST v1/voice-clone/clone | { taskId, status } |
getVoiceCloneTask(taskId) | GET v1/voice-clone/clone/{taskId} | { status, demoAudioUrl? } |
confirmVoiceClone(params) | POST v1/voice-clone/confirm | void |
listVoiceCloneSpeakers() | GET v1/voice-clone/speakers | { speakers, quota, isLimitReached, maxSpeakers, remainingConfirmations } |
getVoiceCloneSpeaker(speakerId) | GET v1/voice-clone/speakers/{speakerId} | VoiceCloneSpeaker |
updateVoiceCloneSpeaker(speakerId, params) | PUT v1/voice-clone/speakers/{speakerId} | VoiceCloneSpeaker |
deleteVoiceCloneSpeaker(speakerId) | DELETE v1/voice-clone/speakers/{speakerId} | { speakerId } |
createVoiceClone은 { audioFiles: Blob[], audioFilenames?: string[], language: 'zh' | 'en' }를 받고, confirmVoiceClone은 { taskId, name, gender, useCredits? }를 받습니다. API 키 면과 달리, 클로닝이 실패하면 본문에 실패를 담아 반환하는 대신 ListenHubError로 거부되며, 자동 확정 기능은 없습니다.
Podcast(팟캐스트)
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createPodcast(params) | POST v1/episodes/all-in-one | { episodeId } |
listPodcasts(params?) | GET v1/episodes(productId=aiPodcast) | ListEpisodesResponse |
createPodcast 파라미터(CreatePodcastParams):
| 파라미터 | 타입 | 설명 |
|---|---|---|
type | 'podcast-solo' | 'podcast-duo' | 1인 또는 2인 진행 |
query | string | 주제 |
sources | ContentSource[] | { type: 'url' | 'text'; uri?; content? } |
template | { type: 'podcast'; mode; speakers; language } | mode: quick | deep | debate, speakers: speakerInnerId[], language: en | zh | ja |
Flow Speech / TTS
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createTTS(params) | POST v1/episodes/flow-speech | { episodeId } |
listTTS(params?) | GET v1/episodes(productId=textToSpeech) | ListEpisodesResponse |
createTTS 파라미터(CreateTTSParams): { sources, template: { type: 'flowspeech', mode: 'smart' | 'direct', speakers, language } }.
Storybook(설명 영상 / 슬라이드)
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createExplainerVideo(params) | POST v1/episodes/storybook | { episodeId } |
createSlides(params) | POST v1/episodes/storybook(mode: 'slides', skipAudio: true) | { episodeId } |
exportExplainerVideo(episodeId) | POST v1/episodes/{episodeId}/storybook/video | void |
listExplainerVideos(params?) | GET v1/episodes(productId=explainerVideo) | ListEpisodesResponse |
listSlides(params?) | GET v1/episodes(productId=slideDeck) | ListEpisodesResponse |
createExplainerVideo와 createSlides는 같은 형태를 공유합니다: { query?, sources?, style?, styleOverride?, skipAudio?, imageConfig?, template }. template에는 type: 'storybook', mode(설명 영상은 info / story, 덱은 slides), speakers, language, 그리고 선택적인 style, size(2K / 4K), aspectRatio(16:9 / 9:16 / 1:1), pageCount가 들어갑니다. createSlides는 skipAudio 기본값을 true로 두고 mode를 slides로 고정합니다. exportExplainerVideo는 다운로드 가능한 영상 렌더링을 트리거합니다.
Episodes(공통)
ListenHubClient의 네 가지 제품 전반에서 동작합니다.
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
getCreation(episodeId) | GET v5/episodes/{episodeId}/detail | EpisodeDetail |
deleteCreations(params) | DELETE v1/episodes | void |
getCreation은 전체 EpisodeDetail(상태, 스피커, 제목/아웃라인/오디오/비디오/페이지/스크립트가 담긴 topicDetail)을 반환합니다. deleteCreations({ ids })는 최대 100개 에피소드를 id로 일괄 소프트 삭제합니다. 비디오 에피소드 id를 전달하면 해당 비디오 태스크도 함께 소프트 삭제되고, 진행 중인 태스크의 크레딧은 환불됩니다. 제품별 list* 메서드는 모두 productId 필터와 함께 GET v1/episodes를 호출하며 { page?, pageSize? }를 받습니다.
Image(이미지)
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createAIImage(params) | POST v1/images | { imageId } |
getAIImage(imageId) | GET v1/images/{imageId} | AIImageItem |
listAIImages(params?) | GET v1/images | { items, pagination } |
deleteAIImages(params) | DELETE v1/images | void |
createAIImage 파라미터(CreateAIImageParams):
| 파라미터 | 타입 | 설명 |
|---|---|---|
prompt | string | 필수 |
referenceImageUrls | string[] | 참조 이미지 URL |
language | 'auto' | 'en' | 'ja' | 'ko' | 'hi' | 'zh' | 'pt' | 'es' | 프롬프트 언어 |
aspectRatio | '1:1' | '2:3' | '3:2' | '3:4' | '4:3' | '9:16' | '16:9' | '21:9' | … | 출력 비율 |
imageSize | '1K' | '2K' | '4K' | 출력 해상도 |
model | 'gemini-3-pro-image' | 'gemini-3.1-flash-image' | 이미지 모델 |
isLossless | boolean | 무손실 인코딩 |
enableSearch | boolean | 근거 확보를 위한 웹 검색 허용 |
생성은 비동기입니다. status가 최종 상태가 되고 imageUrl이 채워질 때까지 getAIImage(imageId)를 폴링하세요. deleteAIImages({ ids })는 최대 100개 이미지를 일괄 소프트 삭제합니다(소유자 범위로 제한되며, 알 수 없는 id는 무시).
Music(음악)
ListenHubClient는 OpenAPIClient와 동일한 음악 메서드 집합을 노출합니다(엔드포인트와 파라미터도 동일): createMusicGenerate, createMusicCover, createMusicExtend, createMusicRemix, createMusicInstrumental, createMusicSoundtrack, createMusicTrack, recognizeMusic, describeMusic, stemMusic, getMusicTask, listMusicTasks. 파라미터와 폴링 루프는 위의 Music을 참고하세요.
Lyrics(가사)
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createLyrics(params) | POST v1/lyrics/generate | { taskId, status } |
getLyricsTask(taskId) | GET v1/lyrics/tasks/{taskId} | LyricsTaskDetail |
listLyricsTasks(params?) | GET v1/lyrics/tasks | { items, page, pageSize, total } |
createLyrics({ prompt })는 비동기 가사 태스크를 시작합니다. status가 success가 될 때까지 getLyricsTask를 폴링한 뒤 variants(각각 { text, title, status })를 읽으세요.
Video Generation(비디오 생성)
ListenHubClient는 OpenAPIClient와 동일한 비디오 메서드를 노출하지만 이름이 하나 다릅니다. SeeDance/HappyHorse 견적 메서드는 estimateVideoGenerationCredits입니다(OpenAPIClient에서는 estimateVideoCredits).
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createVideoGeneration(params) | POST v1/video-generation/generate | { taskId, status } |
getVideoGenerationTask(taskId) | GET v1/video-generation/tasks/{taskId} | VideoGenerationTaskDetail |
listVideoGenerationTasks(params?) | GET v1/video-generation/tasks | { items, page, pageSize, total } |
estimateVideoGenerationCredits(params) | POST v1/video-generation/estimate-credits | { tokens, credits } |
createPixVerseVideoGeneration(params) | POST v1/video-generation/pixverse/generate | { taskId, episodeId?, status } |
estimatePixVerseVideoCredits(params) | POST v1/video-generation/pixverse/estimate-credits | { tokens, credits } |
파라미터는 위의 Video 섹션과 같습니다.
Subscription & user(구독 및 사용자)
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
getCurrentUser() | GET v1/users/me | UserProfile |
getSubscription() | GET v1/users/subscription | SubscriptionInfo |
getSettings() | GET v2/settings | SettingsResponse |
엔드포인트가 OpenAPIClient와 다르다는 점에 유의하세요(v1/users/subscription vs. v1/user/subscription). getSettings는 사용자가 저장해 둔 제품별 기본값(스피커, 언어, 길이, 모드, 스타일 이미지)을 반환합니다.
Checkin(출석 체크)
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
checkinSubmit() | POST v1/checkin | { checkinDate, rewardCredits } |
checkinStatus() | GET v1/checkin/status | CheckinStatusResponse |
크레딧을 보상으로 받는 일일 출석 체크입니다. checkinStatus는 hasCheckedInToday, lastCheckinTime, monthlyCheckinCount를 알려 줍니다.
Settings / API key
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
getApiKey() | GET v1/settings/api-key | { key } |
regenerateApiKey() | POST v1/settings/api-key/regenerate | { key } |
로그인한 사용자가 자신의 OpenAPI 키를 읽거나 교체할 수 있게 합니다. regenerateApiKey는 이전 키를 무효화합니다.
Files(파일)
| 메서드 | 엔드포인트 | 반환값 |
|---|---|---|
createFileUpload(params) | POST v1/files | { presignedUrl, fileUrl } |
getFileDownloadUrl(fileUrl) | GET v1/files | { downloadUrl } |
createFileUpload({ fileKey, contentType, category })는 바이트를 PUT할 presignedUrl과, 이후 참조에 사용할 fileUrl을 반환합니다. getFileDownloadUrl(fileUrl)은 저장된 파일 URL을 다시 서명해 유효 기간이 있는 downloadUrl로 바꿔 줍니다.
client.api 탈출구
ListenHubClient는 SDK가 감싸지 않은 엔드포인트를 위해 내부 ky 인스턴스를 client.api로 노출합니다. 동일한 인증, base URL, 재시도 동작이 적용되지만 응답 파싱은 직접 해야 합니다. 경로는 base URL 기준 상대 경로이며 앞에 /를 붙이지 않습니다(ky의 요구 사항):
const me = await client.api.get('v1/users/me').json();OpenAPIClient는 client.api를 노출하지 않습니다. SDK가 아직 다루지 않는 OpenAPI 엔드포인트는 동일한 Authorization: Bearer 헤더를 붙여 직접 만든 HTTP 클라이언트로 호출하세요 — OpenAPI 레퍼런스를 참고하세요.
에러
모든 메서드는 code가 0이 아니거나 HTTP 에러가 발생하면 ListenHubError를 던집니다. 이 에러는 status, code, requestId를 담고 있으니, 문제를 보고할 때 requestId를 함께 알려 주세요.
import { ListenHubError } from '@marswave/listenhub-sdk';
try {
await client.getPodcast('nope');
} catch (err) {
if (err instanceof ListenHubError) {
console.error(`[${err.status}] code ${err.code} (request ${err.requestId})`);
} else {
throw err;
}
}