CLI Examples
Copy-pasteable shell recipes for one-shot generation, batch loops, audio export, and CI pipelines with the listenhub CLI.
A cookbook of real shell recipes. Each one runs as-is once you have the CLI installed and authenticated. Most examples use the API-key (listenhub openapi) namespace because it's the right fit for scripts and CI — but recipes that depend on local-file upload (cover audio, image references) use the OAuth namespace where that behavior lives, and each one says which auth mode it needs.
Before you start:
- Install:
npm install -g @marswave/listenhub-cli(Node.js >= 20). - For the
openapirecipes, setLISTENHUB_API_KEY(the CLI reads it first) or runlistenhub openapi config set-key. See Authentication. - For the OAuth recipes, run
listenhub auth loginonce.
--json / -j prints machine-readable output to stdout; errors and the progress spinner go to stderr. That separation is what makes piping into jq safe. Exit codes: 0 ok, 1 error, 2 auth, 3 timeout — branch on $? in scripts.
1. One-shot podcast
Submit a topic and block until the episode is ready. By default the command polls every 10 seconds up to the --timeout (default 300), then prints the finished episode.
export LISTENHUB_API_KEY="lh_sk_..."
# Pick a voice once, copy its ID
listenhub openapi speakers list --language en
listenhub openapi podcast create \
--query "AI agent trends in 2026" \
--speaker-id <speaker-id> \
--mode quick--speaker-id is required and repeatable — pass it twice for a two-host episode. Ground the content with --source-url or --source-text (both repeatable) instead of, or in addition to, --query.
2. Batch generation in a bash loop
Read topics from a file, fire each one with --no-wait (return the ID immediately), capture the episodeId with jq, then poll the IDs in a second pass. This keeps submission fast and decouples it from the slow generation step.
#!/usr/bin/env bash
set -euo pipefail
SPEAKER_ID="<speaker-id>"
ids=()
# Submit every topic, collect episode IDs
while IFS= read -r topic; do
[ -z "$topic" ] && continue
id=$(listenhub openapi podcast create \
--query "$topic" \
--speaker-id "$SPEAKER_ID" \
--mode quick \
--no-wait --json | jq -r '.episodeId')
echo "submitted: $topic -> $id"
ids+=("$id")
done < topics.txt
# Poll each episode until it leaves the running state
for id in "${ids[@]}"; do
while :; do
status=$(listenhub openapi podcast get "$id" --json | jq -r '.processStatus')
case "$status" in
success) echo "$id done"; break ;;
failed) echo "$id failed" >&2; break ;;
*) sleep 10 ;;
esac
done
doneprocessStatus is success or failed at a terminal state; anything else means still running. topics.txt is one topic per line.
3. Text-to-speech to an audio file
openapi tts streams binary audio straight to disk — no polling, no --json. Use openapi speakers list to find a voice ID, then write an MP3:
VOICE=$(listenhub openapi speakers list --language en --json | jq -r '.[0].speakerId')
listenhub openapi tts \
--text "Hello from ListenHub" \
--voice "$VOICE" \
--output hello.mp3
# ✓ Audio saved: /abs/path/hello.mp3 (42.3KB)--format accepts mp3 (default), opus, aac, flac, wav, or pcm. For an audio URL instead of a local file (returns JSON with audioUrl), use openapi speech --script "..." --speaker-id "$VOICE" -j.
4. Music from a reference track
Generating a cover from an existing track needs local-file upload, which lives in the OAuth namespace. listenhub music cover auto-detects a local path, validates it, uploads it to cloud storage, then submits the job and polls.
# OAuth: run `listenhub auth login` first
listenhub music cover \
--audio ./original.mp3 \
--style "lo-fi" \
--title "My Cover" \
--json--audio also accepts a URL, which is passed through without uploading. For a fully API-key-driven flow that takes a reference audio, use listenhub openapi music instrumental --reference-audio ./clip.mp3 (model default applies) or listenhub openapi music soundtrack --image ./cover.png --prompt "..." to score an image.
5. AI image with a local reference
openapi image create reads a local reference file, base64-encodes it, and sends it inline. --reference is repeatable and also accepts URLs (passed through by URI). --provider is required.
listenhub openapi image create \
--prompt "a dragon in watercolor style, inspired by this sketch" \
--provider google \
--reference ./sketch.png \
--ratio 16:9 \
--json | jq -r '.imageUrl // .'Supported reference formats: .png, .jpg/.jpeg, .gif, .webp, .bmp. Optional --size is 1K, 2K, or 4K; --ratio is one of 16:9, 4:3, 1:1, 3:4, 9:16, 21:9.
6. PixVerse text-to-video
PixVerse runs through openapi video pixverse generate. --capability is required; text_to_video is the simplest case. --language en (default) hits the international service, --language zh the China service.
# Estimate credits first (never assume a fixed cost)
listenhub openapi video pixverse estimate \
--capability text_to_video --quality 720p --duration 5
# Submit and return the task ID without waiting
listenhub openapi video pixverse generate \
--capability text_to_video \
--prompt "A cat playing piano on a neon stage" \
--quality 720p \
--aspect-ratio 16:9 \
--duration 5 \
--no-wait --json--quality is 360p, 540p, 720p (default), or 1080p; --duration is an integer from 1 to 60 (default 5). Image, video, and audio assets accept an optional trailing :<seconds> duration suffix (for example --image https://example.com/p.jpg:3).
7. CI pipeline: submit, then poll separately
In CI you usually don't want a single command holding a connection open for minutes. Submit with --no-wait, persist the ID as a job output, and poll in a later step (or a later run). This recipe also shows branching on the exit code.
#!/usr/bin/env bash
set -euo pipefail
# LISTENHUB_API_KEY is injected from CI secrets
# --- step: submit ---
EPISODE_ID=$(listenhub openapi podcast create \
--source-url "https://example.com/release-notes" \
--speaker-id "$LH_SPEAKER_ID" \
--no-wait --json | jq -r '.episodeId')
echo "episode_id=$EPISODE_ID" >> "$GITHUB_OUTPUT"
# --- step: poll with a hard cap ---
deadline=$(( $(date +%s) + 900 )) # 15 minutes
while :; do
status=$(listenhub openapi podcast get "$EPISODE_ID" --json | jq -r '.processStatus')
[ "$status" = "success" ] && break
if [ "$status" = "failed" ]; then
echo "generation failed" >&2
exit 1
fi
if [ "$(date +%s)" -ge "$deadline" ]; then
echo "timed out waiting for $EPISODE_ID" >&2
exit 3
fi
sleep 10
done
# --- step: fetch the audio URL ---
listenhub openapi podcast get "$EPISODE_ID" --json | jq -r '.audioUrl'If you let a single creation command poll in CI (no --no-wait), it exits 3 when --timeout is reached — but the job keeps running server-side. Always capture the ID so a later step can recover it. Treat exit 2 as a credentials problem: rotate or re-set LISTENHUB_API_KEY.
8. Check credits before a batch
Generation spends credits. Read your balance, and estimate the cost of the work, before kicking off a large run.
# Remaining credits and plan
listenhub openapi subscription --json | jq '{credits: .totalAvailableCredits, plan: .subscriptionPlan.name}'
# Estimate one video task before multiplying it across a batch
listenhub openapi video estimate \
--model doubao-seedance-2-pro \
--resolution 1080p \
--duration 10 \
--json | jq '{credits, tokens}'Every product with a metered cost exposes an estimate command (openapi video estimate, openapi video pixverse estimate). Query it rather than hard-coding a number — costs change with model, resolution, and duration.
Next steps
OpenAPI commands
Full reference for every listenhub openapi command used in these recipes.
OAuth commands
The interactive listenhub commands, including local-file upload for cover and image.
Authentication
API key vs. OAuth, where credentials live, and how the CLI resolves them.
Quickstart
Install, authenticate, and generate your first episode end to end.