ListenHubDocs
API ReferenceExplainer & Slides

Explainer & Slides

One endpoint family that turns text or a URL into a narrated page deck — explainer, story, or presentation slides — and renders it to video.

Explainer videos, story videos, and presentation slides are three modes of one endpoint family. You send a source and a voice, ListenHub writes the narration and generates a visual per page, and you either use the pages as raw material or render them into a narrated video.

The three endpoints are shared by every mode:

EndpointPurpose
POST /v1/storybook/episodesCreate an episode. mode selects explainer, story, or slides
GET /v1/storybook/episodes/{episodeId}Poll progress, then read the pages and asset URLs
POST /v1/storybook/episodes/{episodeId}/videoRender the finished pages into a video

All endpoints on this page use the OpenAPI base URL https://api.marswave.ai/openapi and authenticate with your API key via the Authorization: Bearer $LISTENHUB_API_KEY header. Create keys at listenhub.ai/settings/api-keys.

Choose a mode

mode is optional and defaults to info. It is the only field that differs between the three modes — everything else on this page applies to all of them.

modeProducesPage 1Best for
info (default)Infographics, illustrations, data visualizationsMagazine-style coverKnowledge explainers, product intros
storyStory scene illustrationsStory coverStory sharing, case studies
slidesPPT layouts (grid, process flow, big-number hero)Presentation title pageMeeting presentations, business reports, conference talks

Workflow

Create the episode

Call POST /v1/storybook/episodes with your source, a speaker, and the mode you want. 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 the raw materials (optional)

pages[] holds the generated images and narration scripts. If that is all you need, stop here.

Render the video

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

Poll the video

Poll until videoStatus is success, then download videoUrl.

Create an episode

POST /v1/storybook/episodes

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()
const episodeId = data.episodeId
import os
import 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',
    },
)
episode_id = response.json()['data']['episodeId']

Response:

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

Request parameters

ParameterTypeRequiredDescription
sourcesarray(1)YesContent source. Exactly 1 item
sources[].typestringYes"text" or "url"
sources[].contentstringYesText content, or the URL itself when type is "url"
sources[].uristringNoAccepted but ignored — the server derives uri from content for url sources
sources[].metadataobjectNoSource metadata
speakersarray(1)Yes¹Voice config. At most 1 item
speakers[].speakerIdstringYesSpeaker ID (see Speakers)
skipAudiobooleanNoDefaults to false. When true, only images and text are produced — no narration audio
languagestringNoLanguage code, e.g. "en", "zh". Defaults to en — it is not inferred from the source
modestringNo"info" (default), "story", or "slides"
stylestringNoVisual style ID. Omit it to use the mode's default style; the accepted IDs are not part of the public contract

¹ speakers is required unless skipAudio is true, in which case it may be omitted.

language is not detected from your content. Omitting it produces an English episode whatever the source language is, so send it explicitly for any non-English source.

Set skipAudio: true when you only want the visuals and the script — the page images and narration text still come back on the episode, without the audio render.

Poll the episode

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"

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.app/covers/{episodeId}.png",
    "audioUrl": "https://assets.listenhub.app/storybook/{episodeId}.mp3",
    "audioDuration": 180,
    "videoUrl": "",
    "videoStatus": "not_generated",
    "pages": [
      {
        "text": "Artificial intelligence has transformed industries worldwide...",
        "pageNumber": 1,
        "imageUrl": "https://assets.listenhub.app/pages/{episodeId}-1.png",
        "audioTimestamp": 0
      },
      {
        "text": "From healthcare to finance, AI applications continue to expand...",
        "pageNumber": 2,
        "imageUrl": "https://assets.listenhub.app/pages/{episodeId}-2.png",
        "audioTimestamp": 25.3
      }
    ]
  }
}

Raw materials: each item in pages[] carries an imageUrl (the generated visual), text (the narration script), and audioTimestamp (where that page starts in audioUrl). Download them and build or edit your own deck without ever rendering a video.

Response fields

FieldTypeDescription
episodeIdstringThe episode identifier you polled with
modestringThe mode this episode was created in
processStatusstringSee below
videoStatusstringSee below
creditsnumberCredits this episode has consumed so far
failCodenumberFailure code, present when processStatus is fail
messagestringHuman-readable detail about the current status
title / coverstringGenerated title and cover image
audioUrl / audioDurationstring / numberNarration audio and its length in seconds
videoUrlstringRendered video, once videoStatus is success
pages[]arraytext, pageNumber, imageUrl, audioTimestamp per page

Credits are reported per episode rather than quoted up front — see Credits & pricing for how the platform bills generation.

processStatus

ValueMeaning
pendingProcessing
successComplete
failFailed — read failCode and message, and see Error Handling

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.

Render the video

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

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

curl -X POST "https://api.marswave.ai/openapi/v1/storybook/episodes/{episodeId}/video" \
  -H "Authorization: Bearer $LISTENHUB_API_KEY"

Response:

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

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

On this page