ListenHubOpenAPI
API Reference

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:

PatternEndpointsHow results come back
Async generation/generate, /instrumental, /soundtrack, /track, /remix, /extendReturns 202 with a taskId. Poll GET /v1/music/tasks/{taskId} until status is success.
Sync analysis/recognize, /describe, /stemReturns 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:

ModelNotes
autoDefault. Lets the service pick a model.
mureka-7.6
mureka-8
mureka-9Not 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

  1. Submit a generation request. The response carries a taskId and an initial status of pending.
  2. Poll GET /v1/music/tasks/{taskId}. status moves through pendinggeneratinguploadingsuccess.
  3. On success, read the finished tracks array (title, tags, duration, signed audioUrl). On failed, read errorMessage.

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:

FieldTypeRequiredDescription
promptstringNoStyle/description prompt
lyricsstringNoLyrics. Required for Mureka when not instrumental
stylestringNoStyle tag. Used as a fallback for prompt
titlestringNoTrack title
instrumentalbooleanNoGenerate without vocals
modelstringNoSee Models. Defaults to auto
vocalIdstringNoReuse a Mureka vocal id
providerstringNodefault (Mureka), mureka, or suno. Defaults to default
providerParamsobjectNoProvider-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:

FieldTypeRequiredDescription
promptstringOne ofStyle/genre description. Mutually exclusive with referenceAudio
referenceAudiofileOne ofReference audio (mp3/m4a, max 10MB). Mutually exclusive with prompt
modelstringNoauto, 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:

FieldTypeRequiredDescription
imagefileOne ofImage (jpg/jpeg/png/webp). Mutually exclusive with video
videofileOne ofVideo (mp4/mov/avi/mkv/webm). Mutually exclusive with image
promptstringNoStyle/description prompt
modelstringNoauto, 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:

FieldTypeRequiredDescription
generateTypestringYesTarget track type. One of Vocals, Instrumental, Drums, Bass, Guitar, Keyboard, Percussion, Strings, Synth, FX, Brass, Woodwinds
promptstringYesStyle/genre description
audiofileOne ofReference audio (mp3/m4a/wav, max 10MB). Mutually exclusive with providerSongId
providerSongIdstringOne ofMureka song id from a previous result. Mutually exclusive with audio
lyricsstringWhen VocalsLyrics. Required when generateType is Vocals
vocalGenderstringNomale or female. Only for generateType=Vocals
generateStartnumberNoRange start in seconds
generateEndnumberNoRange 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:

FieldTypeRequiredDescription
lyricsstringYesNew lyrics
promptstringYesStyle/genre description
audiofileOne sourceAudio file (mp3/m4a, max 10MB)
audioUrlstringOne sourceInternal ListenHub audio URL (must belong to you or be public)
providerSongIdstringOne sourceMureka 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):

FieldTypeRequiredDescription
audiofileOne sourceAudio file (mp3/m4a, max 10MB). Mutually exclusive with uploadUrl / providerSongId
uploadUrlstringOne sourceAudio URL (any reachable external link or internal GCS URL)
providerSongIdstringOne sourceMureka song id from a previous result
modelstringNoSee Models
extendAtnumberNoTime offset (seconds, 8–420) to extend from
extendTypestringNotail (forward, default) or head (backward, mureka-8 only)
lyricsstringNoLyrics for the new section
promptstringNoStyle/description
stylestringNoMusic style
titlestringNoTrack title
instrumentalbooleanNoGenerate 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:

FieldTypeRequiredDescription
audiofileYesAudio 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:

FieldTypeRequiredDescription
audiofileYesAudio 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:

FieldTypeRequiredDescription
audiofileYesAudio file to separate (mp3/m4a, max 10MB)
modelstringNoaudio-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:

FieldTypeRequiredDescription
pageintegerNoPage number, min 1. Defaults to 1
pageSizeintegerNoItems per page, 1100. Defaults to 20
statusstringNoFilter 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:

FieldTypeDescription
idstringTask ID
providerstringdefault, mureka, or suno
taskTypestringGENERATE, INSTRUMENTAL, REMIX, EXTEND, COVER
statusstringpending, generating, uploading, success, failed
paramsobjectEcho of the generation request
tracksarrayFinished tracks: title, tags, duration (seconds), signed audioUrl
creditCostnumberCredits consumed
errorMessagestringFailure reason (only when status is failed)
createdAtnumberCreation time (ms timestamp)
updatedAtnumberLast 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.

On this page