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 | |
|---|---|---|
| Purpose | Knowledge explainers, product intros | Story sharing, case studies |
| Visual style | Infographics, illustrations, data visualizations | Story scene illustrations |
| Page 1 | Magazine-style cover | Story 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
| Parameter | Type | Required | Description |
|---|---|---|---|
| sources | array(1) | Yes | Content source. Max 1 item. |
| sources[].type | string | Yes | "text" or "url" |
| sources[].content | string | Yes | Text content or URL |
| sources[].uri | string | No | Source URI |
| sources[].metadata | object | No | Source metadata |
| speakers | array(1) | Yes | Voice config. Max 1 item. |
| speakers[].speakerId | string | Yes | Speaker ID (see Speakers) |
| language | string | No | Language code (e.g. "en", "zh") |
| mode | string | No | "info" (default) or "story" |
| style | string | No | Visual 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
| Value | Meaning |
|---|---|
pending | Processing |
success | Complete |
fail | Failed (check failCode) |
videoStatus
| Value | Meaning |
|---|---|
not_generated | Video not yet triggered |
pending | Video generating |
success | Video ready (videoUrl available) |
fail | Video 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.