ListenHubOpenAPI
API 参考

播客

生成单人或双人播客单集——一次成稿或拆分为「先脚本后音频」流程——支持 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;生成过程异步进行,因此需轮询单集直到完成。

请求参数

字段类型必填说明
querystring用于生成的 prompt 或主题。当 sources 已携带素材时可为空。
sourcesarray参考素材。每一项为 { "type": "text" | "url", "content": "..." }urlcontent 是链接;textcontent 是原始文本。
speakersarray1 到 2 个音色,每项为 { "speakerId": "..." }。一个音色生成独白,两个生成对话。debate 模式要求正好 2 个。
languagestring输出语言,例如 enzhja。省略时根据输入自动推断。
modestring生成模式,取 quickdeepdebate 之一。默认 quick

querysources 至少提供其一。speakerId 取值可通过 Speakers API 查询。

模式

模式音色数适用场景
quick1 或 2时效性内容的快速产出。默认值。
deep1 或 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'])

响应字段

字段类型说明
episodeIdstring单集标识。
createdAtnumber创建时间戳(毫秒)。
processStatusstring整体任务状态:pendingsuccessfail
contentStatusstring阶段级状态,出现在两阶段流程中:text-successtext-failaudio-successaudio-fail。一次成稿时不返回。
failCodenumber失败原因码;无失败时为 0
messagestring可读的状态详情。
completedTimenumber完成时间戳(毫秒)。
creditsnumber已消耗积分。
titlestring生成的单集标题。
outlinestring生成的大纲。
coverstring封面图 URL。
audioUrlstring最终音频文件 URL(MP3)。
audioStreamUrlstring流式音频 URL(HLS .m3u8)。
subtitlesUrlstring字幕文件 URL(SRT)。
sourceProcessResultobject处理后的来源素材:content 及一个引用数组 references
scriptsarray逐句脚本:每项为 { "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 秒轮询一次。失败时 processStatusfailfailCode 指示原因。

流式获取脚本与大纲(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 解析。它适合用于实时进度界面。要读取最终资产(音频、字幕),在生成完成后使用状态接口


两阶段生成

把生成拆成两次独立调用:先产出脚本,审阅或编辑后,再渲染音频。当你需要在投入音频前加入人工或自动审阅环节,或想分别核算文本生成与音频生成的积分时,这是合适的模式。

该流程有两个接口:

  1. POST /v1/podcast/episodes/text-content —— 仅生成脚本。
  2. POST /v1/podcast/episodes/{episodeId}/audio —— 基于(可选编辑过的)脚本渲染音频。

text-contentlanguage必填,且每个音色的语言必须与 language 值匹配。不匹配会返回 Speaker language mismatch 错误。

生成脚本

POST /v1/podcast/episodes/text-content 接受与一次成稿相同的 querysourcesspeakersmode 字段,但此处 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} 直到 contentStatustext-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} 直到 contentStatusaudio-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 查询实时余额;积分与功能的对应关系参见定价


相关

On this page