ListenHubDocs
API Reference

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:

PatternEndpointsHow results come back
Async generation/generate, /cover, /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.

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.

EndpointMurekaSunoProvider 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:

ModelCreditsNotes
auto10Default for Mureka. Lets the service pick a model.
mureka-7.65
mureka-810
mureka-910Not available for /instrumental.
mureka-o210

Suno models:

ModelCreditsNotes
V415
V4_515
V4_5PLUS15
V4_5ALL15
V515
V5_520
V620Default 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.

FieldTypeApplies toDescription
negativeTagsstring/generate, /cover, /extendComma-separated style tags to steer away from
vocalGenderstring/generate, /cover, /extendm or f. Note this differs from /track, which takes male / female
styleWeightnumber/generate, /cover, /extend0–1. How closely the result follows style
weirdnessConstraintnumber/generate, /cover, /extend0–1. How far the result may drift from convention
audioWeightnumber/generate, /cover, /extend0–1. Weight of the source recording; meaningful only on requests that carry one
personaIdstring/generate, /coverSuno persona that performs the track. On /cover, custom mode only
personaModelstring/generate, /coverModel 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

  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 pending → generating → uploading → success.
  3. On success, read the finished tracks array (title, tags, duration, signed audioUrl). On failed, read errorMessage. params.model reports the tier the task ran on, which is what creditCost is 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:

FieldTypeRequiredDescription
promptstringNoStyle/description prompt on Mureka. Suno reads it as lyrics, so prefer lyrics and pass style through style
lyricsstringNoLyrics. Required by Mureka unless instrumental is true. Suno sets the song's lyrics from it
stylestringNoStyle tag. Used as a fallback for prompt
titlestringNoTrack title
instrumentalbooleanNoGenerate without vocals
customModebooleanNoSuno 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
modelstringNoSee Models. Defaults to auto on Mureka, V6 on Suno
vocalIdstringNoReuse a Mureka vocal id. Mureka only
providerstringNodefault (Mureka), mureka, or suno. Defaults to default
providerParamsobjectNoProvider-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:

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

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:

FieldTypeRequiredDescription
uploadUrlstringYesSource recording: a publicly reachable URL, or a ListenHub asset URL you own — ownership is verified and a short-lived download link is issued
modelstringNoSuno model, see Models. Defaults to V6
customModebooleanNoDefaults to true. Set it to false to drive the cover from prompt alone
promptstringIn custom modeLyrics for the new vocal, required when customMode is true and instrumental is false. In non-custom mode it is a free-form description instead
stylestringIn custom modeMusic style
titlestringIn custom modeTrack title
instrumentalbooleanNoProduce the cover without vocals. Defaults to false
providerParamsobjectNoSuno-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):

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
modelstringNoA Mureka model name, see Models. Defaults to auto, which this endpoint resolves to mureka-7.6
extendAtnumberYesTime offset to extend from. Must be between 8 and 420 seconds
extendTypestringNotail (forward, default) or head (backward, mureka-8 only)
lyricsstringYesLyrics for the new section
promptstringNoStyle/description
stylestringNoMusic style
titlestringNoTrack title
instrumentalbooleanNoGenerate the new section without vocals

Request parameters (Suno path):

FieldTypeRequiredDescription
providerstringYessuno. Without it the request takes the Mureka path
uploadUrlstringYesSource recording: a publicly reachable URL, or a ListenHub asset URL you own
continueAtnumberYesSecond to continue from. Must be greater than 0
modelstringNoSuno model, see Models. Defaults to V6
promptstringNoLyrics for the continuation. Ignored when instrumental is true
stylestringNoMusic style
titlestringNoTrack title
instrumentalbooleanNoContinue without vocals
negativeTagsstringNoSee Suno Parameters
vocalGenderstringNoSee Suno Parameters
styleWeightnumberNoSee Suno Parameters
weirdnessConstraintnumberNoSee Suno Parameters
audioWeightnumberNoSee 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:

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.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:

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

FieldTypeDescription
idstringTask ID
providerstringmureka or suno. Already resolved — a task is never stored as default
taskTypestringGENERATE, INSTRUMENTAL, REMIX, EXTEND, COVER, SOUNDTRACK, TRACK, RECOGNIZE, DESCRIBE, STEM, REGION_EDIT
statusstringpending, generating, uploading, success, failed
paramsobjectEcho of the generation request, including model — the tier the task ran on
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

/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.

On this page