播客
生成单人或双人播客单集——一次成稿或拆分为「先脚本后音频」流程——支持 quick、deep、debate 三种模式。
播客 API 把一段 prompt 和可选的参考来源转化为一集完整的节目:带音色分配的文字脚本、渲染好的音频以及字幕。你可以在一次调用里生成全部内容,也可以拆成两个阶段——先生成脚本,审阅或编辑后,再渲染音频。
所有请求使用基础 URL https://api.marswave.ai/openapi/v1,并需携带 API key:
Authorization: Bearer $LISTENHUB_API_KEY在 listenhub.ai/settings/api-keys 创建 key。每个响应都包裹在 { "code": 0, "message": "", "data": { ... } } 中;code 非零表示出错。
创建播客
POST /v1/podcast/episodes
一次调用生成完整单集(脚本 + 音频)。请求会立即返回一个 episodeId;生成过程异步进行,因此需轮询单集直到完成。
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
query | string | 否 | 用于生成的 prompt 或主题。当 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)双音色示例
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 | 创建时间戳(毫秒)。 |
processStatus | string | 整体任务状态:pending、success 或 fail。 |
contentStatus | string | 阶段级状态,出现在两阶段流程中:text-success、text-fail、audio-success、audio-fail。一次成稿时不返回。 |
failCode | number | 失败原因码;无失败时为 0。 |
message | string | 可读的状态详情。 |
completedTime | number | 完成时间戳(毫秒)。 |
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 解析。它适合用于实时进度界面。要读取最终资产(音频、字幕),在生成完成后使用状态接口。
两阶段生成
把生成拆成两次独立调用:先产出脚本,审阅或编辑后,再渲染音频。当你需要在投入音频前加入人工或自动审阅环节,或想分别核算文本生成与音频生成的积分时,这是合适的模式。
该流程有两个接口:
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."
}
}等待脚本就绪
轮询 GET /v1/podcast/episodes/{episodeId} 直到 contentStatus 为 text-success。在两阶段流程中,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..."
}
]
}
}若需实时进度界面,可订阅 SSE 流 /v1/podcast/episodes/{episodeId}/text-stream?event=script,而非轮询。
(可选)编辑脚本
取上一步响应中的 scripts 数组,按需改写各句的文本内容。
只能修改 content。每句的 speakerId 必须与返回值完全一致,且脚本须保持相同的 1 到 2 个不同音色——这是 API 的硬性约束。
把编辑后的数组作为下一步的请求 body 传入。
渲染音频
POST /v1/podcast/episodes/{episodeId}/audio。发送空 body 渲染原始脚本,或传入 scripts 数组(每项 { "content", "speakerId" })渲染你编辑后的版本:
# 渲染原始脚本
curl -X POST "https://api.marswave.ai/openapi/v1/podcast/episodes/{episodeId}/audio" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json"
# 渲染编辑后的脚本
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>"
}
]
}'// 渲染原始脚本
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',
},
});
// 渲染编辑后的脚本
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
# 渲染原始脚本
requests.post(
f'https://api.marswave.ai/openapi/v1/podcast/episodes/{episode_id}/audio',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'}
)
# 渲染编辑后的脚本
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"
}
}等待音频就绪
继续轮询 GET /v1/podcast/episodes/{episodeId} 直到 contentStatus 为 audio-success。此时音频、流式与字幕 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 | 音频生成失败 | 重试音频渲染 |
积分: 阶段一消耗文本生成积分;阶段二消耗音频生成积分。credits 字段在两个阶段间累加,反映实际扣费。可用 GET /v1/user/subscription 查询实时余额;积分与功能的对应关系参见定价。