팟캐스트
1인 또는 2인 화자 팟캐스트 에피소드를 생성합니다. 한 번에 생성하거나 스크립트 생성과 오디오 렌더링의 2단계 워크플로로 나눌 수 있으며, quick·deep·debate 세 가지 모드를 지원합니다.
팟캐스트 API는 프롬프트와 선택적인 참고 소스를 완성된 한 편의 에피소드로 바꿉니다. 결과물은 화자가 배정된 텍스트 스크립트, 렌더링된 오디오, 그리고 자막입니다. 한 번의 호출로 전부 생성할 수도 있고, 두 단계로 나눠서 스크립트를 먼저 생성하고 검토·편집한 뒤에 오디오를 렌더링할 수도 있습니다.
모든 요청은 base URL https://api.marswave.ai/openapi/v1을 사용하며 API key가 필요합니다:
Authorization: Bearer $LISTENHUB_API_KEY키는 listenhub.ai/settings/api-keys에서 만듭니다. 모든 응답은 { "code": 0, "message": "", "data": { ... } } 형태로 감싸여 있으며, code가 0이 아니면 오류입니다.
팟캐스트 생성
POST /v1/podcast/episodes
한 번의 호출로 완전한 에피소드(스크립트 + 오디오)를 생성합니다. 요청은 즉시 episodeId를 반환하고 생성은 비동기로 진행되므로, 완료될 때까지 에피소드를 폴링하세요.
요청 파라미터
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
query | string | 아니요 | 생성의 기반이 되는 프롬프트 또는 주제. sources가 소재를 담고 있다면 비워 둘 수 있습니다. |
sources | array | 아니요 | 참고 소재. 각 항목은 { "type": "text" | "url", "content": "..." } 형태입니다. url이면 content는 링크이고, text면 content는 원본 텍스트입니다. |
speakers | array | 예 | 1~2명의 화자, 각 항목은 { "speakerId": "..." }. 화자가 한 명이면 독백, 두 명이면 대화가 됩니다. debate 모드는 정확히 2명이 필요합니다. |
language | string | 아니요 | 출력 언어. 예: en, zh, ja. 생략하면 입력에서 언어를 추론합니다. |
mode | string | 아니요 | 생성 모드. quick, deep, debate 중 하나. 기본값은 quick. |
query와 sources 중 최소 하나는 제공해야 합니다. speakerId 값은 Speakers API에서 조회하세요.
모드
| 모드 | 화자 수 | 적합한 용도 |
|---|---|---|
quick | 1 또는 2 | 시의성이 중요한 콘텐츠를 빠르게 뽑아낼 때. 기본값입니다. |
deep | 1 또는 2 | 전문적인 주제를 다루는 심층·리서치형 에피소드. |
debate | 정확히 2 | 두 화자가 서로 다른 입장을 주장하는 양측 토론. |
크레딧 비용은 모드와 길이에 따라 달라집니다. 고정 가격을 가정하지 마세요. 에피소드의 credits 필드가 실제 청구액을 반영하며, 실시간 잔액은 GET /v1/user/subscription으로 확인할 수 있습니다. 크레딧과 기능의 대응 관계는 요금을 참고하세요.
단일 화자 예제
quick 모드의 독백:
curl -X POST "https://api.marswave.ai/openapi/v1/podcast/episodes" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "Give a short technology news briefing for today.",
"speakers": [
{"speakerId": "<SPEAKER_ID_1>"}
],
"language": "en",
"mode": "quick"
}'const response = await fetch('https://api.marswave.ai/openapi/v1/podcast/episodes', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'Give a short technology news briefing for today.',
speakers: [{ speakerId: '<SPEAKER_ID_1>' }],
language: 'en',
mode: 'quick',
}),
});
const data = await response.json();
console.log(data);import os
import requests
response = requests.post(
'https://api.marswave.ai/openapi/v1/podcast/episodes',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'query': 'Give a short technology news briefing for today.',
'speakers': [{'speakerId': '<SPEAKER_ID_1>'}],
'language': 'en',
'mode': 'quick',
}
)
data = response.json()
print(data)2인 화자 예제
두 명의 진행자가 등장하는 deep 에피소드:
curl -X POST "https://api.marswave.ai/openapi/v1/podcast/episodes" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "Analyze the technical foundations and future outlook of large language models.",
"speakers": [
{"speakerId": "<SPEAKER_ID_1>"},
{"speakerId": "<SPEAKER_ID_2>"}
],
"language": "en",
"mode": "deep"
}'const response = await fetch('https://api.marswave.ai/openapi/v1/podcast/episodes', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'Analyze the technical foundations and future outlook of large language models.',
speakers: [
{ speakerId: '<SPEAKER_ID_1>' },
{ speakerId: '<SPEAKER_ID_2>' },
],
language: 'en',
mode: 'deep',
}),
});
const data = await response.json();
console.log(data);import os
import requests
response = requests.post(
'https://api.marswave.ai/openapi/v1/podcast/episodes',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'query': 'Analyze the technical foundations and future outlook of large language models.',
'speakers': [
{'speakerId': '<SPEAKER_ID_1>'},
{'speakerId': '<SPEAKER_ID_2>'},
],
'language': 'en',
'mode': 'deep',
}
)
data = response.json()
print(data)debate 모드
debate는 정확히 2명의 화자가 필요합니다:
curl -X POST "https://api.marswave.ai/openapi/v1/podcast/episodes" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "Should remote work become the default model?",
"speakers": [
{"speakerId": "<SPEAKER_ID_1>"},
{"speakerId": "<SPEAKER_ID_2>"}
],
"language": "en",
"mode": "debate"
}'const response = await fetch('https://api.marswave.ai/openapi/v1/podcast/episodes', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'Should remote work become the default model?',
speakers: [
{ speakerId: '<SPEAKER_ID_1>' },
{ speakerId: '<SPEAKER_ID_2>' },
],
language: 'en',
mode: 'debate',
}),
});
const data = await response.json();
console.log(data);import os
import requests
response = requests.post(
'https://api.marswave.ai/openapi/v1/podcast/episodes',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'query': 'Should remote work become the default model?',
'speakers': [
{'speakerId': '<SPEAKER_ID_1>'},
{'speakerId': '<SPEAKER_ID_2>'},
],
'language': 'en',
'mode': 'debate',
}
)
data = response.json()
print(data)참고 소스 사용하기
특정 소재에 기반한 에피소드를 만들려면 sources를 전달하세요. 각 항목은 가져올 URL이거나 원본 텍스트입니다:
curl -X POST "https://api.marswave.ai/openapi/v1/podcast/episodes" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "Summarize and discuss the core ideas in this article.",
"sources": [
{
"type": "url",
"content": "https://blog.samaltman.com/reflections"
}
],
"speakers": [
{"speakerId": "<SPEAKER_ID_1>"},
{"speakerId": "<SPEAKER_ID_2>"}
],
"language": "en",
"mode": "deep"
}'const response = await fetch('https://api.marswave.ai/openapi/v1/podcast/episodes', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'Summarize and discuss the core ideas in this article.',
sources: [
{ type: 'url', content: 'https://blog.samaltman.com/reflections' },
],
speakers: [
{ speakerId: '<SPEAKER_ID_1>' },
{ speakerId: '<SPEAKER_ID_2>' },
],
language: 'en',
mode: 'deep',
}),
});
const data = await response.json();
console.log(data);import os
import requests
response = requests.post(
'https://api.marswave.ai/openapi/v1/podcast/episodes',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'query': 'Summarize and discuss the core ideas in this article.',
'sources': [
{'type': 'url', 'content': 'https://blog.samaltman.com/reflections'}
],
'speakers': [
{'speakerId': '<SPEAKER_ID_1>'},
{'speakerId': '<SPEAKER_ID_2>'},
],
'language': 'en',
'mode': 'deep',
}
)
data = response.json()
print(data)응답
{
"code": 0,
"message": "",
"data": {
"episodeId": "665f1c2a9b3e4d0012a8c7e1"
}
}episodeId는 이후 모든 호출에서 사용하는 핸들입니다. 저장해 두고 상태를 폴링하세요.
에피소드 상태 조회
GET /v1/podcast/episodes/{episodeId}
현재 상태를 가져오고, 완료된 뒤에는 생성된 결과물을 가져옵니다. 에피소드를 생성한 다음 생성이 끝날 때까지 이 엔드포인트를 폴링하세요.
curl -X GET "https://api.marswave.ai/openapi/v1/podcast/episodes/{episodeId}" \
-H "Authorization: Bearer $LISTENHUB_API_KEY"const response = await fetch(
`https://api.marswave.ai/openapi/v1/podcast/episodes/${episodeId}`,
{ headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` } }
);
const data = await response.json();
console.log('Status:', data.data.processStatus);
console.log('Audio URL:', data.data.audioUrl);import os
import requests
response = requests.get(
f'https://api.marswave.ai/openapi/v1/podcast/episodes/{episode_id}',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'}
)
data = response.json()
print('Status:', data['data']['processStatus'])
print('Audio URL:', data['data']['audioUrl'])응답 필드
| 필드 | 타입 | 설명 |
|---|---|---|
episodeId | string | 에피소드 식별자. |
createdAt | number | 생성 타임스탬프(epoch 밀리초). |
processStatus | string | 전체 작업 상태: pending, success, fail. |
contentStatus | string | 단계별 상태로, 2단계 워크플로에서 제공됩니다: text-success, text-fail, audio-success, audio-fail. 한 번에 생성할 때는 없습니다. |
failCode | number | 실패 사유 코드. 실패가 없으면 0. |
message | string | 사람이 읽을 수 있는 상태 상세. |
completedTime | number | 완료 타임스탬프(epoch 밀리초). |
credits | number | 현재까지 소모된 크레딧. |
title | string | 생성된 에피소드 제목. |
outline | string | 생성된 아웃라인. |
cover | string | 커버 이미지 URL. |
audioUrl | string | 최종 오디오 파일 URL(MP3). |
audioStreamUrl | string | 스트리밍 오디오 URL(HLS .m3u8). |
subtitlesUrl | string | 자막 파일 URL(SRT). |
sourceProcessResult | object | 처리된 소스 소재: content와 인용 목록인 references 배열. |
scripts | array | 대사 단위 스크립트. 각 항목은 { "speakerId", "speakerName", "content" }. |
생성이 완료된 상태(processStatus: "success")의 응답:
{
"code": 0,
"message": "",
"data": {
"episodeId": "665f1c2a9b3e4d0012a8c7e1",
"processStatus": "success",
"failCode": 0,
"credits": 27,
"title": "The Story of AI: How the Intelligent Age Was Born",
"audioUrl": "https://assets.listenhub.ai/listenhub-public-prod/podcast/665f1c2a9b3e4d0012a8c7e1.mp3",
"audioStreamUrl": "https://assets.listenhub.ai/listenhub-public-prod/podcast/665f1c2a9b3e4d0012a8c7e1.m3u8",
"scripts": [
{
"speakerId": "<SPEAKER_ID_1>",
"speakerName": "Ethan",
"content": "These days it feels like AI news surrounds us everywhere."
}
]
}
}팟캐스트 생성은 보통 1~4분 걸립니다. 권장 폴링 방식은 첫 요청 전에 60초 기다린 뒤 10초 간격으로 폴링하는 것입니다. 실패하면 processStatus가 fail이 되고 failCode가 사유를 나타냅니다.
스크립트와 아웃라인 스트리밍(SSE)
GET /v1/podcast/episodes/{episodeId}/text-stream?event={script|outline}
스크립트나 아웃라인이 생성되는 동안, 폴링 대신 Server-Sent Events 스트림을 구독해 텍스트를 점진적으로 받을 수 있습니다. event 쿼리 파라미터로 어떤 스트림을 받을지 선택합니다:
outline— 작성되는 중의 아웃라인.script— 작성되는 중의 대사 단위 스크립트.
curl -N "https://api.marswave.ai/openapi/v1/podcast/episodes/{episodeId}/text-stream?event=script" \
-H "Authorization: Bearer $LISTENHUB_API_KEY"응답은 text/event-stream입니다. 하나의 JSON body로 파싱하지 말고 스트림으로 읽으세요. 실시간 진행 상황 UI에 적합합니다. 최종 결과물(오디오, 자막)을 읽으려면 생성이 완료된 뒤 상태 엔드포인트를 사용하세요.
2단계 생성
생성을 두 번의 독립적인 호출로 나눕니다. 스크립트를 먼저 만들고, 확인하거나 편집한 뒤 오디오를 렌더링합니다. 오디오로 확정하기 전에 사람이나 자동화된 검토 단계가 필요할 때, 또는 텍스트 생성 크레딧과 오디오 생성 크레딧을 따로 관리하고 싶을 때 적합한 패턴입니다.
이 흐름에는 두 개의 엔드포인트가 있습니다:
POST /v1/podcast/episodes/text-content— 스크립트만 생성합니다.POST /v1/podcast/episodes/{episodeId}/audio— (필요하면 편집한) 스크립트로 오디오를 렌더링합니다.
text-content에서는 language가 필수이며, 각 화자의 언어가 language 값과 일치해야 합니다. 일치하지 않으면 Speaker language mismatch 오류가 반환됩니다.
스크립트 생성
POST /v1/podcast/episodes/text-content는 한 번에 생성할 때와 동일한 query, sources, speakers, mode 필드를 받지만, 여기서는 language가 필수입니다. 오디오 없이 스크립트만 생성합니다:
curl -X POST "https://api.marswave.ai/openapi/v1/podcast/episodes/text-content" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "Discuss the current state of quantum computing.",
"speakers": [
{"speakerId": "<SPEAKER_ID_1>"},
{"speakerId": "<SPEAKER_ID_2>"}
],
"language": "en",
"mode": "deep"
}'const response = await fetch('https://api.marswave.ai/openapi/v1/podcast/episodes/text-content', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: 'Discuss the current state of quantum computing.',
speakers: [
{ speakerId: '<SPEAKER_ID_1>' },
{ speakerId: '<SPEAKER_ID_2>' },
],
language: 'en',
mode: 'deep',
}),
});
const data = await response.json();
const episodeId = data.data.episodeId;
console.log('Episode ID:', episodeId);import os
import requests
response = requests.post(
'https://api.marswave.ai/openapi/v1/podcast/episodes/text-content',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'query': 'Discuss the current state of quantum computing.',
'speakers': [
{'speakerId': '<SPEAKER_ID_1>'},
{'speakerId': '<SPEAKER_ID_2>'},
],
'language': 'en',
'mode': 'deep',
}
)
data = response.json()
episode_id = data['data']['episodeId']
print('Episode ID:', episode_id)응답:
{
"code": 0,
"message": "",
"data": {
"episodeId": "665f1c2a9b3e4d0012a8c7e1",
"message": "Text content generation started. Audio generation can be triggered later."
}
}스크립트 완성 대기
contentStatus가 text-success가 될 때까지 GET /v1/podcast/episodes/{episodeId}를 폴링합니다. 2단계 흐름에서는 어떤 단계가 끝났는지 알려주는 필드가 contentStatus이므로, processStatus 대신 이 값을 확인하세요.
curl -X GET "https://api.marswave.ai/openapi/v1/podcast/episodes/{episodeId}" \
-H "Authorization: Bearer $LISTENHUB_API_KEY"const result = await fetch(`https://api.marswave.ai/openapi/v1/podcast/episodes/${episodeId}`, {
headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` },
});
const status = await result.json();
console.log('Content status:', status.data.contentStatus);import os
import requests
result = requests.get(
f'https://api.marswave.ai/openapi/v1/podcast/episodes/{episode_id}',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'}
)
status = result.json()
print('Content status:', status['data']['contentStatus'])스크립트가 준비된 응답(contentStatus: "text-success"):
{
"code": 0,
"message": "",
"data": {
"episodeId": "665f1c2a9b3e4d0012a8c7e1",
"processStatus": "success",
"contentStatus": "text-success",
"credits": 15,
"title": "Quantum Computing: Present and Future",
"outline": "...",
"scripts": [
{
"speakerId": "<SPEAKER_ID_1>",
"speakerName": "Ethan",
"content": "Welcome to this discussion on quantum computing..."
},
{
"speakerId": "<SPEAKER_ID_2>",
"speakerName": "Sophia",
"content": "Quantum computing is exciting and rapidly evolving..."
}
]
}
}실시간 진행 상황 UI가 필요하다면 폴링 대신 /v1/podcast/episodes/{episodeId}/text-stream?event=script SSE 스트림을 구독하세요.
(선택) 스크립트 편집
이전 응답의 scripts 배열을 가져와 필요한 만큼 각 대사의 내용을 다시 씁니다.
변경할 수 있는 것은 content뿐입니다. 각 대사의 speakerId는 반환된 값 그대로 유지하고, 스크립트에 등장하는 서로 다른 화자는 1~2명으로 유지하세요. 이는 API의 강제 제약입니다.
편집한 배열을 다음 단계의 요청 본문으로 전달합니다.
오디오 렌더링
POST /v1/podcast/episodes/{episodeId}/audio를 호출합니다. 원본 스크립트를 그대로 렌더링하려면 빈 본문을 보내고, 편집한 버전을 렌더링하려면 scripts 배열(각 항목은 { "content", "speakerId" })을 전달하세요:
# Render the original script
curl -X POST "https://api.marswave.ai/openapi/v1/podcast/episodes/{episodeId}/audio" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json"
# Render an edited script
curl -X POST "https://api.marswave.ai/openapi/v1/podcast/episodes/{episodeId}/audio" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"scripts": [
{
"content": "Welcome to this episode. Today we go deeper into quantum computing...",
"speakerId": "<SPEAKER_ID_1>"
},
{
"content": "This field has moved quickly from theory to practical experiments...",
"speakerId": "<SPEAKER_ID_2>"
}
]
}'// Render the original script
await fetch(`https://api.marswave.ai/openapi/v1/podcast/episodes/${episodeId}/audio`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
});
// Render an edited script
await fetch(`https://api.marswave.ai/openapi/v1/podcast/episodes/${episodeId}/audio`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
scripts: [
{ content: 'Welcome to this episode. Today we go deeper into quantum computing...', speakerId: '<SPEAKER_ID_1>' },
{ content: 'This field has moved quickly from theory to practical experiments...', speakerId: '<SPEAKER_ID_2>' },
],
}),
});import os
import requests
# Render the original script
requests.post(
f'https://api.marswave.ai/openapi/v1/podcast/episodes/{episode_id}/audio',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'}
)
# Render an edited script
requests.post(
f'https://api.marswave.ai/openapi/v1/podcast/episodes/{episode_id}/audio',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'scripts': [
{'content': 'Welcome to this episode. Today we go deeper into quantum computing...', 'speakerId': '<SPEAKER_ID_1>'},
{'content': 'This field has moved quickly from theory to practical experiments...', 'speakerId': '<SPEAKER_ID_2>'},
]
}
)응답:
{
"code": 0,
"message": "",
"data": {
"success": true,
"message": "Audio generation started",
"episodeId": "665f1c2a9b3e4d0012a8c7e1",
"status": "submit"
}
}오디오 완성 대기
contentStatus가 audio-success가 될 때까지 GET /v1/podcast/episodes/{episodeId} 폴링을 계속합니다. 그러면 오디오, 스트리밍, 자막 URL이 채워집니다:
{
"code": 0,
"message": "",
"data": {
"episodeId": "665f1c2a9b3e4d0012a8c7e1",
"processStatus": "success",
"contentStatus": "audio-success",
"credits": 42,
"title": "Quantum Computing: Present and Future",
"audioUrl": "https://assets.listenhub.ai/podcast/665f1c2a9b3e4d0012a8c7e1.mp3",
"audioStreamUrl": "https://assets.listenhub.ai/podcast/665f1c2a9b3e4d0012a8c7e1.m3u8",
"subtitlesUrl": "https://assets.listenhub.ai/podcast/665f1c2a9b3e4d0012a8c7e1.srt",
"scripts": [ ]
}
}contentStatus 레퍼런스
| 값 | 의미 | 다음 단계 |
|---|---|---|
text-success | 스크립트 생성 완료 | 오디오 렌더링 |
text-fail | 스크립트 생성 실패 | 에피소드 다시 생성 |
audio-success | 오디오 생성 완료 | 완료 |
audio-fail | 오디오 생성 실패 | 오디오 렌더링 재시도 |
크레딧: 1단계는 텍스트 생성 크레딧을, 2단계는 오디오 생성 크레딧을 소모합니다. credits 필드는 두 단계에 걸쳐 누적되며 실제 청구액을 반영합니다. 실시간 잔액은 GET /v1/user/subscription으로 확인하고, 크레딧과 기능의 대응 관계는 요금을 참고하세요.