ListenHubOpenAPI
API Reference

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 content
  • debate: debate mode, requires exactly 2 speakers
  • deep: 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

ValueMeaningNext step
text-successScript generation completeTrigger audio generation
text-failScript generation failedRecreate the episode
audio-successAudio generation completeDone
audio-failAudio generation failedRetry audio generation

Credits: Stage 1 consumes text-generation credits; Stage 2 consumes audio-generation credits. Total = sum of both.

On this page