Podcast
Create single or dual-speaker podcast episodes via API with quick, debate, and deep modes, custom voices, and reference sources.
Create Single-Speaker Podcast
POST /v1/podcast/episodes
Create a single-host podcast episode.
Supported modes: quick, debate (requires two speakers), deep.
Quick mode example:
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 mode example:
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>"}
],
"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>' }],
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>'}],
'language': 'en',
'mode': 'deep',
}
)
data = response.json()
print(data)Supported modes:
quick: fast mode for time-sensitive contentdebate: debate mode, requires exactly 2 speakersdeep: in-depth mode for professional content
Create Dual-Speaker Podcast
POST /v1/podcast/episodes
Create a two-host conversation episode with quick, debate, or deep modes.
Debate mode with two speakers:
# Debate mode requires exactly 2 speakers
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)Deep mode with two speakers:
curl -X POST "https://api.marswave.ai/openapi/v1/podcast/episodes" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "Discuss how AI is changing the job market.",
"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: 'Discuss how AI is changing the job market.',
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': 'Discuss how AI is changing the job market.',
'speakers': [
{'speakerId': '<SPEAKER_ID_1>'},
{'speakerId': '<SPEAKER_ID_2>'},
],
'language': 'en',
'mode': 'deep',
}
)
data = response.json()
print(data)# Debate mode requires exactly 2 speakers
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)Provide references through sources:
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)Query Episode Status
GET /v1/podcast/episodes/{episodeId}
After creating a podcast, poll with the returned episodeId until processStatus is success.
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'])Response when generation is complete (processStatus: "success"):
{
"code": 0,
"message": "",
"data": {
"episodeId": "{episodeId}",
"processStatus": "success",
"credits": 27,
"title": "The Story of AI: How the Intelligent Age Was Born",
"audioUrl": "https://assets.listenhub.ai/listenhub-public-prod/podcast/{episodeId}.mp3",
"audioStreamUrl": "https://assets.listenhub.ai/listenhub-public-prod/podcast/{episodeId}.m3u8",
"scripts": [
{
"speakerId": "<SPEAKER_ID_1>",
"speakerName": "Ethan",
"content": "These days it feels like AI news surrounds us everywhere."
}
]
}
}Podcast generation typically takes 1-4 minutes. Recommended polling strategy: wait 60 seconds before the first poll, then check every 10 seconds. On failure, processStatus is failed and failCode indicates the reason.
Two-Phase Generation
Split podcast generation into two independent stages: generate the script first, then trigger audio rendering after review or editing. Useful when you need to inspect content before recording, or want to control credit consumption separately.
language is required, and the Speaker's language must match the language value. A mismatch returns a Speaker language mismatch error.
Generate Script
Call POST /v1/podcast/episodes/text-content to generate the script text only — no audio is produced:
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)Response:
{
"code": 0,
"message": "",
"data": {
"episodeId": "{episodeId}",
"message": "Text content generation started. Audio generation can be triggered later."
}
}Wait for Script to Be Ready
Poll GET /v1/podcast/episodes/{episodeId} until contentStatus is text-success:
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'])Script-ready response (contentStatus: "text-success"):
{
"code": 0,
"message": "",
"data": {
"episodeId": "{episodeId}",
"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..."
}
]
}
}Always check contentStatus, not processStatus. Only proceed when contentStatus == "text-success".
(Optional) Edit the Script
Take the scripts array from the previous response and modify line content as needed.
Only content fields may be changed. speakerId values must remain exactly as returned, and scripts must include 1-2 distinct speakers — this is a hard API constraint.
Pass the edited scripts as the request body in the next step.
Generate Audio
Call POST /v1/podcast/episodes/{episodeId}/audio. Omit scripts to use the original, or pass your edited version:
# Use original scripts
curl -X POST "https://api.marswave.ai/openapi/v1/podcast/episodes/{episodeId}/audio" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json"
# Use edited scripts
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>"
}
]
}'// Use original scripts
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',
},
});
// Use edited scripts
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
# Use original scripts
requests.post(
f'https://api.marswave.ai/openapi/v1/podcast/episodes/{episode_id}/audio',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'}
)
# Use edited scripts
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>'},
]
}
)Wait for Audio to Be Ready
Continue polling until contentStatus is audio-success:
{
"code": 0,
"message": "",
"data": {
"episodeId": "{episodeId}",
"processStatus": "success",
"contentStatus": "audio-success",
"credits": 42,
"title": "Quantum Computing: Present and Future",
"audioUrl": "https://assets.listenhub.ai/podcast/{episodeId}.mp3",
"audioStreamUrl": "https://assets.listenhub.ai/podcast/{episodeId}.m3u8",
"scripts": [...]
}
}contentStatus Reference
| Value | Meaning | Next step |
|---|---|---|
text-success | Script generation complete | Trigger audio generation |
text-fail | Script generation failed | Recreate the episode |
audio-success | Audio generation complete | Done |
audio-fail | Audio generation failed | Retry audio generation |
Credits: Stage 1 consumes text-generation credits; Stage 2 consumes audio-generation credits. Total = sum of both.