Music Generation
Generate songs, instrumentals, and soundtracks with Mureka or Suno, 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 runs on two providers, Mureka and Suno, chosen per request with the provider field on the endpoints that serve both. 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, /cover, /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.
Providers
provider takes mureka, suno, or default, which resolves to Mureka. Only two endpoints read it; the rest are served by one provider and ignore it.
| Endpoint | Mureka | Suno | Provider selection |
|---|---|---|---|
/generate | ✓ | ✓ | provider, defaults to Mureka |
/extend | ✓ | ✓ | provider, defaults to Mureka |
/cover | — | ✓ | Always Suno |
/instrumental | ✓ | — | Always Mureka |
/soundtrack | ✓ | — | Always Mureka |
/track | ✓ | — | Always Mureka |
/remix | ✓ | — | Always Mureka |
/recognize | ✓ | — | Always Mureka |
/describe | ✓ | — | Always Mureka |
/stem | ✓ | — | Always Mureka |
/remix and /cover both re-perform an existing recording, but they are not interchangeable: /remix re-sings a Mureka song with new lyrics, /cover re-records uploaded audio in a new style through Suno.
GET /v1/music/tasks and GET /v1/music/tasks/{taskId} return tasks from both providers. Each task names the one that ran it in its provider field.
Models
model is scoped to the provider serving the request: Mureka names on Mureka endpoints, Suno tiers on Suno ones. The credits column applies to /generate, /instrumental, and /cover, which price by tier. /extend prices from a separate table — 10 credits for mureka-7.6 and for auto, which resolves to mureka-7.6 before pricing, 15 for every other model including all Suno tiers — and the remaining endpoints are flat-rated, listed under Credits.
Mureka models:
| Model | Credits | Notes |
|---|---|---|
auto | 10 | Default for Mureka. Lets the service pick a model. |
mureka-7.6 | 5 | |
mureka-8 | 10 | |
mureka-9 | 10 | Not available for /instrumental. |
mureka-o2 | 10 |
Suno models:
| Model | Credits | Notes |
|---|---|---|
V4 | 15 | |
V4_5 | 15 | |
V4_5PLUS | 15 | |
V4_5ALL | 15 | |
V5 | 15 | |
V5_5 | 20 | |
V6 | 20 | Default for Suno. Newest tier. |
Every tier stays selectable. A Suno request that omits model runs on V6.
Stem separation (/stem) uses a different model set: audio-separation-1 (default) or audio-separation-2 (also produces MIDI).
Suno Parameters
Suno takes seven parameters with no Mureka equivalent. On /generate and /cover they go inside providerParams; on /extend they are top-level form fields.
| Field | Type | Applies to | Description |
|---|---|---|---|
negativeTags | string | /generate, /cover, /extend | Comma-separated style tags to steer away from |
vocalGender | string | /generate, /cover, /extend | m or f. Note this differs from /track, which takes male / female |
styleWeight | number | /generate, /cover, /extend | 0–1. How closely the result follows style |
weirdnessConstraint | number | /generate, /cover, /extend | 0–1. How far the result may drift from convention |
audioWeight | number | /generate, /cover, /extend | 0–1. Weight of the source recording; meaningful only on requests that carry one |
personaId | string | /generate, /cover | Suno persona that performs the track. On /cover, custom mode only |
personaModel | string | /generate, /cover | Model tier the persona was created with. Pass alongside personaId |
Keys outside this list are dropped from providerParams before the request reaches Suno.
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.params.modelreports the tier the task ran on, which is whatcreditCostis priced against.
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.app/.../track-1.mp3"
}
],
"creditCost": 10,
"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. Served by Mureka unless you pass provider=suno. Mureka rejects non-instrumental requests that carry no lyrics; Suno accepts a request with either lyrics or prompt.
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 on Mureka. Suno reads it as lyrics, so prefer lyrics and pass style through style |
lyrics | string | No | Lyrics. Required by Mureka unless instrumental is true. Suno sets the song's lyrics from it |
style | string | No | Style tag. Used as a fallback for prompt |
title | string | No | Track title |
instrumental | boolean | No | Generate without vocals |
customMode | boolean | No | Suno only. Defaults to true, where style and title carry the descriptors and the lyric body comes from lyrics. Set it to false to drive the whole song from prompt alone |
model | string | No | See Models. Defaults to auto on Mureka, V6 on Suno |
vocalId | string | No | Reuse a Mureka vocal id. Mureka only |
provider | string | No | default (Mureka), mureka, or suno. Defaults to default |
providerParams | object | No | Provider-specific parameters. For Suno, see Suno 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 |
Cover an Existing Recording
POST /v1/music/cover
Re-record an uploaded track in a new style, keeping its melody. Suno only. Sends JSON.
curl -X POST "https://api.marswave.ai/openapi/v1/music/cover" \
-H "Authorization: Bearer $LISTENHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"uploadUrl": "https://example.com/original.mp3",
"style": "acoustic folk, fingerpicked guitar",
"title": "Night Walk (Acoustic)",
"prompt": "[verse]\nWalking down the empty street at night",
"model": "V6"
}'const response = await fetch('https://api.marswave.ai/openapi/v1/music/cover', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.LISTENHUB_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
uploadUrl: 'https://example.com/original.mp3',
style: 'acoustic folk, fingerpicked guitar',
title: 'Night Walk (Acoustic)',
prompt: '[verse]\nWalking down the empty street at night',
model: 'V6',
}),
});
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/cover',
headers={'Authorization': f'Bearer {os.environ["LISTENHUB_API_KEY"]}'},
json={
'uploadUrl': 'https://example.com/original.mp3',
'style': 'acoustic folk, fingerpicked guitar',
'title': 'Night Walk (Acoustic)',
'prompt': '[verse]\nWalking down the empty street at night',
'model': 'V6',
},
)
data = response.json()['data']
print('Task ID:', data['taskId'])Request parameters:
| Field | Type | Required | Description |
|---|---|---|---|
uploadUrl | string | Yes | Source recording: a publicly reachable URL, or a ListenHub asset URL you own — ownership is verified and a short-lived download link is issued |
model | string | No | Suno model, see Models. Defaults to V6 |
customMode | boolean | No | Defaults to true. Set it to false to drive the cover from prompt alone |
prompt | string | In custom mode | Lyrics for the new vocal, required when customMode is true and instrumental is false. In non-custom mode it is a free-form description instead |
style | string | In custom mode | Music style |
title | string | In custom mode | Track title |
instrumental | boolean | No | Produce the cover without vocals. Defaults to false |
providerParams | object | No | Suno-specific parameters, see Suno Parameters |
Returns 202 with { "taskId": "...", "status": "pending" }. Priced by Suno model tier, same as /generate.
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 | A Mureka model name, see Models. Defaults to auto, which this endpoint resolves to mureka-7.6 |
extendAt | number | Yes | Time offset to extend from. Must be between 8 and 420 seconds |
extendType | string | No | tail (forward, default) or head (backward, mureka-8 only) |
lyrics | string | Yes | 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 |
Request parameters (Suno path):
| Field | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | suno. Without it the request takes the Mureka path |
uploadUrl | string | Yes | Source recording: a publicly reachable URL, or a ListenHub asset URL you own |
continueAt | number | Yes | Second to continue from. Must be greater than 0 |
model | string | No | Suno model, see Models. Defaults to V6 |
prompt | string | No | Lyrics for the continuation. Ignored when instrumental is true |
style | string | No | Music style |
title | string | No | Track title |
instrumental | boolean | No | Continue without vocals |
negativeTags | string | No | See Suno Parameters |
vocalGender | string | No | See Suno Parameters |
styleWeight | number | No | See Suno Parameters |
weirdnessConstraint | number | No | See Suno Parameters |
audioWeight | number | No | See Suno Parameters |
model is validated before any credits are reserved — a name outside the model tables is rejected outright. Passing provider narrows the check to that provider's tiers.
The two paths share only uploadUrl, prompt, style, title, and instrumental. audio, providerSongId, extendAt, extendType, and lyrics are Mureka-only; continueAt is Suno-only.
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. This reads lyrics off a recording; to write new lyrics from a prompt, see Lyrics Generation.
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.items.length} of ${data.total} 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},
)
data = response.json()['data']
print(len(data['items']), 'of', data['total'], '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 |
data is a page, not an array: items holds this page of tasks, total the full count, and page / pageSize echo what was applied.
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 | mureka or suno. Already resolved — a task is never stored as default |
taskType | string | GENERATE, INSTRUMENTAL, REMIX, EXTEND, COVER, SOUNDTRACK, TRACK, RECOGNIZE, DESCRIBE, STEM, REGION_EDIT |
status | string | pending, generating, uploading, success, failed |
params | object | Echo of the generation request, including model — the tier the task ran on |
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
/generate, /instrumental, and /cover price by model tier, listed under Models. The rest are fixed: /remix 10, /extend 10 on mureka-7.6 and on auto (which resolves to mureka-7.6) and 15 on every other model, /soundtrack 15, /track 15, /describe 15, /recognize 3, and /stem 10 on audio-separation-1 or 100 on audio-separation-2.
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 the endpoints listed below, including async polling. What neither exposes yet is provider selection and the Suno-specific parameters — send those over HTTP.
JavaScript SDK
OpenAPIClient.createMusicGenerate / createMusicCover / 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.