Music Generation
Generate songs, instrumentals, and soundtracks with Mureka, and analyze existing audio — lyrics recognition, description, and stem separation.
The Music API turns text, lyrics, images, or reference audio into music, and analyzes existing tracks. Generation is backed by Mureka. All endpoints live under https://api.marswave.ai/openapi/v1/music and authenticate with Authorization: Bearer $LISTENHUB_API_KEY.
The API splits into two response patterns:
| Pattern | Endpoints | How results come back |
|---|---|---|
| Async generation | /generate, /instrumental, /soundtrack, /track, /remix, /extend | Returns 202 with a taskId. Poll GET /v1/music/tasks/{taskId} until status is success. |
| Sync analysis | /recognize, /describe, /stem | Returns 200 with the result in the same response. No polling. |
Every response is wrapped in { "code": 0, "message": "", "data": { ... } }. A non-zero code means an error — see Error Handling. The examples below read fields from data.
Models
Generation endpoints accept a model parameter using a provider-neutral contract. Mureka models:
| Model | Notes |
|---|---|
auto | Default. Lets the service pick a model. |
mureka-7.6 | |
mureka-8 | |
mureka-9 | Not available for /instrumental. |
mureka-o2 |
Stem separation (/stem) uses a different model set: audio-separation-1 (default) or audio-separation-2 (also produces MIDI).
Async Task Lifecycle
- Submit a generation request. The response carries a
taskIdand an initialstatusofpending. - Poll
GET /v1/music/tasks/{taskId}.statusmoves throughpending→generating→uploading→success. - On
success, read the finishedtracksarray (title, tags, duration, signedaudioUrl). Onfailed, readerrorMessage.
Recommended polling: wait ~30 seconds after submission, then poll every 10 seconds. A music task usually completes in 1–3 minutes.
{
"code": 0,
"message": "",
"data": {
"id": "68e780390fc5c9a54f695a7e",
"provider": "mureka",
"taskType": "GENERATE",
"status": "success",
"params": {
"model": "auto",
"prompt": "r&b, slow, passionate, male vocal",
"instrumental": false
},
"tracks": [
{
"title": "Night Walk",
"tags": "r&b, slow",
"duration": 142.5,
"audioUrl": "https://assets.listenhub.ai/.../track-1.mp3"
}
],
"creditCost": 20,
"createdAt": 1730000000000,
"updatedAt": 1730000180000
}
}Track audioUrl values are signed and expire about 1 hour after the task is fetched. Download the audio or re-fetch the task to refresh the URLs before they expire.
Generate from Text or Lyrics
POST /v1/music/generate
Create a song from a style prompt and/or lyrics. Sends JSON. For Mureka, non-instrumental requests should include lyrics.
curl -X POST "https://api.marswave.ai/openapi/v1/music/generate" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "r&b, slow, passionate, male vocal",
"lyrics": "[verse]\nWalking down the empty street at night\n[chorus]\nFeel the rhythm, feel the light",
"title": "Night Walk",
"model": "auto"
}'const response = await fetch('https://api.marswave.ai/openapi/v1/music/generate', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt: 'r&b, slow, passionate, male vocal',
lyrics: '[verse]\nWalking down the empty street at night\n[chorus]\nFeel the rhythm, feel the light',
title: 'Night Walk',
model: 'auto',
}),
});
const { data } = await response.json();
console.log('Task ID:', data.taskId);import os
import requests
response = requests.post(
'https://api.marswave.ai/openapi/v1/music/generate',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'prompt': 'r&b, slow, passionate, male vocal',
'lyrics': '[verse]\nWalking down the empty street at night\n[chorus]\nFeel the rhythm, feel the light',
'title': 'Night Walk',
'model': 'auto',
},
)
data = response.json()['data']
print('Task ID:', data['taskId'])Request parameters:
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | No | Style/description prompt |
lyrics | string | No | Lyrics. Required for Mureka when not instrumental |
style | string | No | Style tag. Used as a fallback for prompt |
title | string | No | Track title |
instrumental | boolean | No | Generate without vocals |
model | string | No | See Models. Defaults to auto |
vocalId | string | No | Reuse a Mureka vocal id |
provider | string | No | default (Mureka), mureka, or suno. Defaults to default |
providerParams | object | No | Provider-specific parameters |
Returns 202 with { "taskId": "...", "status": "pending" }. Poll the task to get the result.
Generate an Instrumental
POST /v1/music/instrumental
Create a standalone instrumental from a text prompt or a reference audio file. Provide exactly one of prompt or referenceAudio. Sends multipart/form-data.
curl -X POST "https://api.marswave.ai/openapi/v1/music/instrumental" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-F "prompt=lofi hip hop, mellow, rainy night" \
-F "model=auto"const form = new FormData();
form.append('prompt', 'lofi hip hop, mellow, rainy night');
form.append('model', 'auto');
const response = await fetch('https://api.marswave.ai/openapi/v1/music/instrumental', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` },
body: form,
});
const { data } = await response.json();
console.log('Task ID:', data.taskId);import os
import requests
response = requests.post(
'https://api.marswave.ai/openapi/v1/music/instrumental',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
data={'prompt': 'lofi hip hop, mellow, rainy night', 'model': 'auto'},
)
data = response.json()['data']
print('Task ID:', data['taskId'])Request parameters:
| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | One of | Style/genre description. Mutually exclusive with referenceAudio |
referenceAudio | file | One of | Reference audio (mp3/m4a, max 10MB). Mutually exclusive with prompt |
model | string | No | auto, mureka-7.6, mureka-8, or mureka-o2. Defaults to auto |
Generate a Soundtrack from Image or Video
POST /v1/music/soundtrack
Generate music that matches an image or video. Provide exactly one of image or video. Sends multipart/form-data.
curl -X POST "https://api.marswave.ai/openapi/v1/music/soundtrack" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-F "image=@scene.jpg" \
-F "prompt=cinematic, hopeful, orchestral" \
-F "model=auto"import { readFile } from 'node:fs/promises';
const form = new FormData();
form.append('image', new Blob([await readFile('scene.jpg')]), 'scene.jpg');
form.append('prompt', 'cinematic, hopeful, orchestral');
form.append('model', 'auto');
const response = await fetch('https://api.marswave.ai/openapi/v1/music/soundtrack', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` },
body: form,
});
const { data } = await response.json();
console.log('Task ID:', data.taskId);import os
import requests
with open('scene.jpg', 'rb') as f:
response = requests.post(
'https://api.marswave.ai/openapi/v1/music/soundtrack',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
files={'image': f},
data={'prompt': 'cinematic, hopeful, orchestral', 'model': 'auto'},
)
data = response.json()['data']
print('Task ID:', data['taskId'])Request parameters:
| Field | Type | Required | Description |
|---|---|---|---|
image | file | One of | Image (jpg/jpeg/png/webp). Mutually exclusive with video |
video | file | One of | Video (mp4/mov/avi/mkv/webm). Mutually exclusive with image |
prompt | string | No | Style/description prompt |
model | string | No | auto, mureka-7.6, mureka-8, mureka-9, or mureka-o2. Defaults to auto |
Generate a Single Track
POST /v1/music/track
Generate one instrument or vocal track from a reference audio file or an existing Mureka providerSongId. Provide exactly one of audio or providerSongId. Sends multipart/form-data.
curl -X POST "https://api.marswave.ai/openapi/v1/music/track" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-F "audio=@reference.mp3" \
-F "generateType=Drums" \
-F "prompt=funk, tight groove, 110 bpm"import { readFile } from 'node:fs/promises';
const form = new FormData();
form.append('audio', new Blob([await readFile('reference.mp3')]), 'reference.mp3');
form.append('generateType', 'Drums');
form.append('prompt', 'funk, tight groove, 110 bpm');
const response = await fetch('https://api.marswave.ai/openapi/v1/music/track', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` },
body: form,
});
const { data } = await response.json();
console.log('Task ID:', data.taskId);import os
import requests
with open('reference.mp3', 'rb') as f:
response = requests.post(
'https://api.marswave.ai/openapi/v1/music/track',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
files={'audio': f},
data={'generateType': 'Drums', 'prompt': 'funk, tight groove, 110 bpm'},
)
data = response.json()['data']
print('Task ID:', data['taskId'])Request parameters:
| Field | Type | Required | Description |
|---|---|---|---|
generateType | string | Yes | Target track type. One of Vocals, Instrumental, Drums, Bass, Guitar, Keyboard, Percussion, Strings, Synth, FX, Brass, Woodwinds |
prompt | string | Yes | Style/genre description |
audio | file | One of | Reference audio (mp3/m4a/wav, max 10MB). Mutually exclusive with providerSongId |
providerSongId | string | One of | Mureka song id from a previous result. Mutually exclusive with audio |
lyrics | string | When Vocals | Lyrics. Required when generateType is Vocals |
vocalGender | string | No | male or female. Only for generateType=Vocals |
generateStart | number | No | Range start in seconds |
generateEnd | number | No | Range end in seconds |
Remix an Existing Song
POST /v1/music/remix
Re-perform an existing song with new lyrics. Provide the source audio in exactly one way: an uploaded audio file, an internal ListenHub audioUrl, or a Mureka providerSongId. Sends multipart/form-data.
curl -X POST "https://api.marswave.ai/openapi/v1/music/remix" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-F "audio=@original.mp3" \
-F "lyrics=[verse]\nA brand new story to tell" \
-F "prompt=upbeat pop, bright synths"import { readFile } from 'node:fs/promises';
const form = new FormData();
form.append('audio', new Blob([await readFile('original.mp3')]), 'original.mp3');
form.append('lyrics', '[verse]\nA brand new story to tell');
form.append('prompt', 'upbeat pop, bright synths');
const response = await fetch('https://api.marswave.ai/openapi/v1/music/remix', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` },
body: form,
});
const { data } = await response.json();
console.log('Task ID:', data.taskId);import os
import requests
with open('original.mp3', 'rb') as f:
response = requests.post(
'https://api.marswave.ai/openapi/v1/music/remix',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
files={'audio': f},
data={
'lyrics': '[verse]\nA brand new story to tell',
'prompt': 'upbeat pop, bright synths',
},
)
data = response.json()['data']
print('Task ID:', data['taskId'])Request parameters:
| Field | Type | Required | Description |
|---|---|---|---|
lyrics | string | Yes | New lyrics |
prompt | string | Yes | Style/genre description |
audio | file | One source | Audio file (mp3/m4a, max 10MB) |
audioUrl | string | One source | Internal ListenHub audio URL (must belong to you or be public) |
providerSongId | string | One source | Mureka song id from a previous result |
Extend a Song
POST /v1/music/extend
Continue an existing song from a chosen point in time. Sends multipart/form-data. Supply the source via audio, uploadUrl, or providerSongId.
curl -X POST "https://api.marswave.ai/openapi/v1/music/extend" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-F "audio=@original.mp3" \
-F "model=mureka-8" \
-F "extendAt=30" \
-F "extendType=tail"import { readFile } from 'node:fs/promises';
const form = new FormData();
form.append('audio', new Blob([await readFile('original.mp3')]), 'original.mp3');
form.append('model', 'mureka-8');
form.append('extendAt', '30');
form.append('extendType', 'tail');
const response = await fetch('https://api.marswave.ai/openapi/v1/music/extend', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` },
body: form,
});
const { data } = await response.json();
console.log('Task ID:', data.taskId);import os
import requests
with open('original.mp3', 'rb') as f:
response = requests.post(
'https://api.marswave.ai/openapi/v1/music/extend',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
files={'audio': f},
data={'model': 'mureka-8', 'extendAt': '30', 'extendType': 'tail'},
)
data = response.json()['data']
print('Task ID:', data['taskId'])Request parameters (Mureka path):
| Field | Type | Required | Description |
|---|---|---|---|
audio | file | One source | Audio file (mp3/m4a, max 10MB). Mutually exclusive with uploadUrl / providerSongId |
uploadUrl | string | One source | Audio URL (any reachable external link or internal GCS URL) |
providerSongId | string | One source | Mureka song id from a previous result |
model | string | No | See Models |
extendAt | number | No | Time offset (seconds, 8–420) to extend from |
extendType | string | No | tail (forward, default) or head (backward, mureka-8 only) |
lyrics | string | No | Lyrics for the new section |
prompt | string | No | Style/description |
style | string | No | Music style |
title | string | No | Track title |
instrumental | boolean | No | Generate the new section without vocals |
/extend also supports a Suno path with continueAt, uploadUrl, and Suno model versions. Pass provider=suno and the Suno-specific fields. Mureka is the default provider.
Recognize Lyrics
POST /v1/music/recognize
Transcribe lyrics with timestamped sections from an audio file. Synchronous — the result is in the response. Sends multipart/form-data.
curl -X POST "https://api.marswave.ai/openapi/v1/music/recognize" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-F "audio=@song.mp3"import { readFile } from 'node:fs/promises';
const form = new FormData();
form.append('audio', new Blob([await readFile('song.mp3')]), 'song.mp3');
const response = await fetch('https://api.marswave.ai/openapi/v1/music/recognize', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` },
body: form,
});
const { data } = await response.json();
console.log('Sections:', data.result.lyricsSections.length);import os
import requests
with open('song.mp3', 'rb') as f:
response = requests.post(
'https://api.marswave.ai/openapi/v1/music/recognize',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
files={'audio': f},
)
data = response.json()['data']
print('Sections:', len(data['result']['lyricsSections']))Request parameters:
| Field | Type | Required | Description |
|---|---|---|---|
audio | file | Yes | Audio file (mp3/m4a, max 10MB) |
The data.result object contains duration and a lyricsSections array.
Describe Audio
POST /v1/music/describe
Analyze an audio file and return a description plus tags, genres, and instruments. Synchronous. Sends multipart/form-data.
curl -X POST "https://api.marswave.ai/openapi/v1/music/describe" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-F "audio=@song.mp3"import { readFile } from 'node:fs/promises';
const form = new FormData();
form.append('audio', new Blob([await readFile('song.mp3')]), 'song.mp3');
const response = await fetch('https://api.marswave.ai/openapi/v1/music/describe', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` },
body: form,
});
const { data } = await response.json();
console.log(data.result.description, data.result.genres);import os
import requests
with open('song.mp3', 'rb') as f:
response = requests.post(
'https://api.marswave.ai/openapi/v1/music/describe',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
files={'audio': f},
)
data = response.json()['data']
print(data['result']['description'], data['result']['genres'])Request parameters:
| Field | Type | Required | Description |
|---|---|---|---|
audio | file | Yes | Audio file to analyze (mp3/m4a, max 10MB) |
The data.result object contains description, tags, genres, and instruments.
Separate Stems
POST /v1/music/stem
Split an audio file into stems (vocals, bass, drums, other) and return download URLs. Synchronous. Sends multipart/form-data.
curl -X POST "https://api.marswave.ai/openapi/v1/music/stem" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-F "audio=@song.mp3" \
-F "model=audio-separation-1"import { readFile } from 'node:fs/promises';
const form = new FormData();
form.append('audio', new Blob([await readFile('song.mp3')]), 'song.mp3');
form.append('model', 'audio-separation-1');
const response = await fetch('https://api.marswave.ai/openapi/v1/music/stem', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` },
body: form,
});
const { data } = await response.json();
console.log('Stems ZIP:', data.result.zipUrl);import os
import requests
with open('song.mp3', 'rb') as f:
response = requests.post(
'https://api.marswave.ai/openapi/v1/music/stem',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
files={'audio': f},
data={'model': 'audio-separation-1'},
)
data = response.json()['data']
print('Stems ZIP:', data['result']['zipUrl'])Request parameters:
| Field | Type | Required | Description |
|---|---|---|---|
audio | file | Yes | Audio file to separate (mp3/m4a, max 10MB) |
model | string | No | audio-separation-1 (default) or audio-separation-2 (also produces MIDI) |
The data.result object contains zipUrl, midiZipUrl (when audio-separation-2), and expiresAt. Download links expire about 24 hours after generation.
List Tasks
GET /v1/music/tasks
List your music tasks, newest first.
curl -X GET "https://api.marswave.ai/openapi/v1/music/tasks?page=1&pageSize=20&status=success" \
-H "Authorization: Bearer $LISTENHUB_API_KEY"const response = await fetch(
'https://api.marswave.ai/openapi/v1/music/tasks?page=1&pageSize=20',
{ headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` } }
);
const { data } = await response.json();
console.log(`${data.length} tasks`);import os
import requests
response = requests.get(
'https://api.marswave.ai/openapi/v1/music/tasks',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
params={'page': 1, 'pageSize': 20},
)
print(len(response.json()['data']), 'tasks')Query parameters:
| Field | Type | Required | Description |
|---|---|---|---|
page | integer | No | Page number, min 1. Defaults to 1 |
pageSize | integer | No | Items per page, 1–100. Defaults to 20 |
status | string | No | Filter by pending, generating, uploading, success, or failed |
Get a Task
GET /v1/music/tasks/{taskId}
Fetch a single task. This is the endpoint you poll after submitting an async generation request.
curl -X GET "https://api.marswave.ai/openapi/v1/music/tasks/{taskId}" \
-H "Authorization: Bearer $LISTENHUB_API_KEY"const response = await fetch(
`https://api.marswave.ai/openapi/v1/music/tasks/${taskId}`,
{ headers: { 'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}` } }
);
const { data } = await response.json();
console.log('Status:', data.status);
if (data.status === 'success') console.log('Audio:', data.tracks[0].audioUrl);import os
import requests
response = requests.get(
f'https://api.marswave.ai/openapi/v1/music/tasks/{task_id}',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
)
data = response.json()['data']
print('Status:', data['status'])
if data['status'] == 'success':
print('Audio:', data['tracks'][0]['audioUrl'])Task response fields:
| Field | Type | Description |
|---|---|---|
id | string | Task ID |
provider | string | default, mureka, or suno |
taskType | string | GENERATE, INSTRUMENTAL, REMIX, EXTEND, COVER |
status | string | pending, generating, uploading, success, failed |
params | object | Echo of the generation request |
tracks | array | Finished tracks: title, tags, duration (seconds), signed audioUrl |
creditCost | number | Credits consumed |
errorMessage | string | Failure reason (only when status is failed) |
createdAt | number | Creation time (ms timestamp) |
updatedAt | number | Last update time (ms timestamp) |
Credits
Each endpoint consumes credits by model tier. For async generation, credits are reserved at submission, confirmed on success, and refunded automatically on failure. The exact cost is returned per task as creditCost (and per analysis call as creditCost in the result). Check your live balance with GET /v1/user/subscription, and see Pricing for credit-to-feature mapping.
SDK and CLI
The official SDK and CLI wrap every endpoint on this page, including async polling.
JavaScript SDK
OpenAPIClient.createMusicGenerate / createMusicInstrumental / createMusicSoundtrack / createMusicTrack / createMusicRemix, plus recognizeMusic / describeMusic / stemMusic and getMusicTask / listMusicTasks.
CLI
listenhub openapi music generate | instrumental | soundtrack | track | remix | recognize | describe | stem | list | get — with --no-wait and --timeout for polling.