ListenHubOpenAPI
API Reference

Explainer Video

Turn any content into narrated explainer videos with AI-generated visuals and voiceover.

Explainer Video lets you create narrated visual content from text or URLs. Two modes are available:

info (default)story
PurposeKnowledge explainers, product introsStory sharing, case studies
Visual styleInfographics, illustrations, data visualizationsStory scene illustrations
Page 1Magazine-style coverStory cover

The mode parameter is optional and defaults to info.


Create Episode

POST /v1/storybook/episodes

Create an explainer video episode with AI-generated visuals and narration.

sources accepts at most 1 item. speakers accepts at most 1 item.

Info mode (default):

curl -X POST "https://api.marswave.ai/openapi/v1/storybook/episodes" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sources": [
      {"type": "url", "content": "https://example.com/article"}
    ],
    "speakers": [
      {"speakerId": "<SPEAKER_ID>"}
    ],
    "language": "en",
    "mode": "info"
  }'
const response = await fetch('https://api.marswave.ai/openapi/v1/storybook/episodes', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    sources: [{ type: 'url', content: 'https://example.com/article' }],
    speakers: [{ speakerId: '<SPEAKER_ID>' }],
    language: 'en',
    mode: 'info',
  }),
});
const data = await response.json();
console.log(data);
import os, requests

response = requests.post(
    'https://api.marswave.ai/openapi/v1/storybook/episodes',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'sources': [{'type': 'url', 'content': 'https://example.com/article'}],
        'speakers': [{'speakerId': '<SPEAKER_ID>'}],
        'language': 'en',
        'mode': 'info',
    }
)
print(response.json())

Story mode:

curl -X POST "https://api.marswave.ai/openapi/v1/storybook/episodes" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sources": [
      {"type": "text", "content": "The founding story of a small startup that grew into a global platform..."}
    ],
    "speakers": [
      {"speakerId": "<SPEAKER_ID>"}
    ],
    "language": "en",
    "mode": "story"
  }'
const response = await fetch('https://api.marswave.ai/openapi/v1/storybook/episodes', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    sources: [{ type: 'text', content: 'The founding story of a small startup that grew into a global platform...' }],
    speakers: [{ speakerId: '<SPEAKER_ID>' }],
    language: 'en',
    mode: 'story',
  }),
});
const data = await response.json();
console.log(data);
import os, requests

response = requests.post(
    'https://api.marswave.ai/openapi/v1/storybook/episodes',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
    json={
        'sources': [{'type': 'text', 'content': 'The founding story of a small startup that grew into a global platform...'}],
        'speakers': [{'speakerId': '<SPEAKER_ID>'}],
        'language': 'en',
        'mode': 'story',
    }
)
print(response.json())

Response:

{
  "code": 0,
  "message": "",
  "data": {
    "episodeId": "{episodeId}"
  }
}

Request Parameters

ParameterTypeRequiredDescription
sourcesarray(1)YesContent source. Max 1 item.
sources[].typestringYes"text" or "url"
sources[].contentstringYesText content or URL
sources[].uristringNoSource URI
sources[].metadataobjectNoSource metadata
speakersarray(1)YesVoice config. Max 1 item.
speakers[].speakerIdstringYesSpeaker ID (see Speakers)
languagestringNoLanguage code (e.g. "en", "zh")
modestringNo"info" (default) or "story"
stylestringNoVisual style ID

Query Episode Status

GET /v1/storybook/episodes/{episodeId}

Poll with the returned episodeId until processStatus is success.

curl "https://api.marswave.ai/openapi/v1/storybook/episodes/{episodeId}" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY"
const response = await fetch(
  `https://api.marswave.ai/openapi/v1/storybook/episodes/${episodeId}`,
  { headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` } }
);
const data = await response.json();
console.log('Status:', data.data.processStatus);
import os, requests

response = requests.get(
    f'https://api.marswave.ai/openapi/v1/storybook/episodes/{episode_id}',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'}
)
data = response.json()
print('Status:', data['data']['processStatus'])

Response (when processStatus is success):

{
  "code": 0,
  "message": "",
  "data": {
    "episodeId": "{episodeId}",
    "createdAt": 1700000000,
    "mode": "info",
    "processStatus": "success",
    "credits": 30,
    "title": "How AI Is Changing the World",
    "cover": "https://assets.listenhub.ai/covers/{episodeId}.png",
    "audioUrl": "https://assets.listenhub.ai/storybook/{episodeId}.mp3",
    "audioDuration": 180,
    "videoUrl": "",
    "videoStatus": "not_generated",
    "pages": [
      {
        "text": "Artificial intelligence has transformed industries worldwide...",
        "pageNumber": 1,
        "imageUrl": "https://assets.listenhub.ai/pages/{episodeId}-1.png",
        "audioTimestamp": 0
      },
      {
        "text": "From healthcare to finance, AI applications continue to expand...",
        "pageNumber": 2,
        "imageUrl": "https://assets.listenhub.ai/pages/{episodeId}-2.png",
        "audioTimestamp": 25.3
      }
    ]
  }
}

Raw materials: Each item in pages[] contains an imageUrl (AI-generated visual) and text (voiceover script). You can download these independently for your own content.

processStatus

ValueMeaning
pendingProcessing
successComplete
failFailed (check failCode)

videoStatus

ValueMeaning
not_generatedVideo not yet triggered
pendingVideo generating
successVideo ready (videoUrl available)
failVideo generation failed

Generation typically takes 2–5 minutes. Recommended polling: wait 60 seconds, then poll every 10 seconds.


Generate Video

POST /v1/storybook/episodes/{episodeId}/video

Trigger video generation for a completed episode. processStatus must be success.

Wait until processStatus is success before calling this endpoint.

curl -X POST "https://api.marswave.ai/openapi/v1/storybook/episodes/{episodeId}/video" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY"
const response = await fetch(
  `https://api.marswave.ai/openapi/v1/storybook/episodes/${episodeId}/video`,
  {
    method: 'POST',
    headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` },
  }
);
console.log(await response.json());
import os, requests

response = requests.post(
    f'https://api.marswave.ai/openapi/v1/storybook/episodes/{episode_id}/video',
    headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'}
)
print(response.json())

Response:

{
  "code": 0,
  "message": "",
  "data": {
    "success": true
  }
}

After triggering, poll GET /v1/storybook/episodes/{episodeId} until videoStatus is success.


Complete Workflow

Create Episode

Call POST /v1/storybook/episodes with your source, speaker, and mode (info or story). Save the returned episodeId.

Poll for Completion

Poll GET /v1/storybook/episodes/{episodeId} every 10 seconds (after an initial 60-second wait) until processStatus is success.

Use Raw Materials (Optional)

The pages[] array contains AI-generated images (imageUrl) and narration scripts (text) for each page. Use these directly without generating a video.

Generate Video

Call POST /v1/storybook/episodes/{episodeId}/video to combine pages into a narrated video.

Poll Video Status

Poll until videoStatus is success. The videoUrl field contains the download link.

On this page