all dispatches
Music Generation APIDec 19, 20249 min read

How to Build AI Music Apps with Eachlabs in 2026

Generating one song is a weekend project. Shipping a music feature is a queue, durable storage, a mastering pass and an idempotent webhook handler.

How to Build AI Music Apps with Eachlabs in 2026

Generating one song is a weekend project. Shipping a music feature that a thousand people use on a Tuesday is a different discipline, and the gap between them is not the model.

It's that generation takes long enough to break a request cycle, outputs mostly can't be reproduced, and the thing your users hear has to arrive at a consistent loudness whether the model felt like delivering that or not.

Eachlabs is the developer-first way to put an AI music generation API behind an app: songs, instrumentals, soundtracks, lyrics, stems and mastering under one key and one request shape, chainable into backend workflows. This guide is the build, not the demo. Model selection, the request, handling the output, and the production concerns that decide whether the feature survives its first real week.

When one platform is the right call, and when it isn't

Plainly, so you can skip the rest if it doesn't apply: if your app needs exactly one text-to-music call and will never need anything else, a single-purpose tool is less to reason about. Use it.

The threshold is the second step. Real music features are chains. Generate the track, separate the stems so the user can mute the vocal, master the result to a delivery spec, then store it against a user record. That's four calls, and if they live with four vendors you now own four auth schemes, four response shapes and four retry policies. The glue leaks, not the calls.

The second threshold is the second modality. The moment your music feature needs a cover image or a short video to go with the track, a platform that already speaks image and video through the same envelope stops being a preference and starts being the reason you ship on time.

Step one: pick the model from the input you have

This is the decision that most affects output quality, and almost everyone gets it wrong by choosing on reputation instead of on input shape.

If your user types a description and you need control over length, tempo and key, use ACE-Step's ace-step-1-5-text-to-music. It takes duration in seconds from 10 to 600, defaulting to 30, plus bpm from 30 to 300 and key_scale as a plain string like C Major or Am. guidance_scale runs 1 to 20 and defaults to 7, with higher values following the prompt more literally. infer_method picks the solver: ode is Euler-based, faster, and deterministic for a given seed, while sde is stochastic and more varied at the cost of reproducibility. For an app, ode is almost always the right answer.

If your user supplies or writes lyrics, use Mureka's mureka-generate-song. lyrics is required, up to 3000 characters, across ten languages. It honours square-bracket structure tags such as [Verse], [Chorus] and [Bridge], and timestamps them in the response, which is how you build a UI that can jump to the chorus. gender selects the vocal, n defaults to 2 variants, and model spans auto through the current flagship tiers plus the reasoning-oriented mureka-o2 for stronger structural coherence. Leave it on auto until you have a reason not to.

If you need an instrumental with no vocal at all, mureka-generate-instrumental is the direct path, and note that model is its required field rather than the prompt.

If your app has an image or a video to score against, mureka-generate-soundtrack composes from visual context. Pass image_url or video_url; at least one is required and a prompt alone will not do. They're mutually exclusive, and if you send both, image_url wins.

And if your users don't have lyrics, mureka-generate-lyrics takes a prompt and writes them, which makes a two-step flow that feels like magic in a UI: describe a song, get lyrics, approve them, generate.

Key, model, request, poll. Then audio.
Key, model, request, poll. Then audio.

Step two: the request

Every model uses the same envelope, so this shape is the whole integration:

curl -X POST https://api.eachlabs.ai/v1/prediction/ \
  -H "Authorization: Bearer $EACHLABS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ace-step-1-5-text-to-music",
    "version": "0.0.1",
    "input": {
      "prompt": "Lo-fi hip hop, dusty piano, soft vinyl crackle, relaxed head-nod groove",
      "duration": 60,
      "bpm": 84,
      "key_scale": "F minor",
      "infer_method": "ode"
    },
    "webhook_url": "https://api.yourapp.com/hooks/eachlabs"
  }'

What comes back is a prediction ID, not audio:

{
  "status": "success",
  "message": "Prediction created successfully",
  "predictionID": "03781b27-2c4a-411a-a9e8-cfd2d6726773"
}

Keys go in as Authorization: Bearer only. Query-string keys are rejected outright, so credentials never land in a URL or an access log. Keep the key server-side; a music feature that ships its API key to the client is a music feature that funds strangers.

Step three: handle the output like an asset, not a response

Predictions move through created, starting, processing, then settle on success, error or cancelled. Poll GET /v1/prediction/{id} every three to five seconds, or pass a webhook_url and stop polling.

import os, time, requests

BASE = "https://api.eachlabs.ai/v1/prediction/"
H = {"Authorization": f"Bearer {os.environ['EACHLABS_API_KEY']}"}
TERMINAL = {"success", "error", "cancelled"}

def generate_track(prompt: str, seconds: int = 60, bpm: int | None = None):
    payload = {"prompt": prompt, "duration": seconds, "infer_method": "ode"}
    if bpm:
        payload["bpm"] = bpm

    r = requests.post(BASE, headers=H, timeout=30, json={
        "model": "ace-step-1-5-text-to-music",
        "version": "0.0.1",
        "input": payload,
        "webhook_url": "",
    })
    r.raise_for_status()
    pid = r.json()["predictionID"]

    while True:
        d = requests.get(BASE + pid, headers=H, timeout=30).json()
        if d["status"] in TERMINAL:
            break
        time.sleep(4)

    if d["status"] != "success":
        raise RuntimeError(f"{pid} ended {d['status']}: {d.get('output')}")
    return {"url": d["output"], "took": d["metrics"]["predict_time"], "prediction_id": pid}

Then do the two things that separate a feature from a demo.

Copy the file to your own storage immediately. The returned URL is an output location, not your asset store. Write it to a deterministic key and record the prediction ID beside it.

Master it. audio-mastering takes an audio input and a required mastering_preset of streaming, podcast or broadcast. Generated music arrives at inconsistent loudness, and an app where one track is quiet and the next is loud feels broken in a way users can't articulate but do notice.

If your feature lets people edit what they generated, mureka-stem-song takes a url and separates a track into stems, and mureka-extend-song takes lyrics plus an extend_at position with extend_type as tail or head. Extend rather than regenerate. Regeneration produces a different song.

Pick the model from the job, not from the demo.
Pick the model from the job, not from the demo.

Production concerns: latency, reliability, orchestration

Latency. Read metrics.predict_time from your own predictions rather than trusting any published figure, since it scales with requested duration. The design consequence is fixed regardless of the number: never block an HTTP request on generation. Submit, return a job ID, deliver by webhook. A request that waits for audio will eventually be killed by a proxy or a load balancer, and it will happen in production rather than in staging. Which means the queue is the feature. Show progress, let people navigate away, notify them when it lands.

Reliability. Two failure modes, handled differently. A 400 arrives before any provider work starts, so nothing was spent, and it's almost always a parameter outside its range: a duration above 600, a bpm below 30, lyrics past 3000 characters. Validate client-side against the model's own schema, which GET /v1/model?slug=ace-step-1-5-text-to-music returns. A 429 is different: it's an account concurrency cap rather than a per-second rate limit, the details field names the cap that applied, and a rejected request creates no prediction at all. That's a resubmit path, not a failure path, and conflating the two is how a queue silently drops a user's job.

On webhooks, two things to build on day one. Payloads use a compact succeeded or failed vocabulary rather than the six polling states, so keep the parsers separate. And the same webhook can be delivered more than once, so make the handler idempotent on the prediction ID. Delivering a user two copies of their track is the good outcome here; overwriting a good asset is the bad one.

Orchestration. Generate, separate, master, store is a chain, and chains belong in a workflow rather than in application code. POST /v1/workflows/trigger/{workflowID}/{versionID} pins the version in the path, so editing the workflow cannot silently change what your live app runs. Executions report running, completed, failed or cancelled, and are readable via GET /v1/workflows/executions/{executionID}.

The rule that saves the most pain: retry the step, not the chain. Re-running a generative step does not reproduce it, it produces something new. If mastering fails, master the stored track again. Re-running the chain hands your user a different song than the one they approved.

Generation is slow enough that the queue is the design.
Generation is slow enough that the queue is the design.

The honest limitations

This would be less useful without the rough edges, so here they are.

Structure is the weak point. You'll get a convincing sixty seconds and a less convincing four minutes; long generations wander. Build long things from controlled short things, using extend and stem layering, rather than asking for length directly.

Vocals need a human ear. Diction drifts, and a clean lyric can come back with two words fused. If your app ships vocals straight to users without a listen step, some of what ships will be wrong.

Reproducibility is partial. Not every endpoint exposes a seed, and where it does, determinism depends on the solver. Treat generated audio as an artifact you must keep, because for much of this catalog you genuinely cannot make it again.

And rights are yours to establish. Generated music removes the per-use library fee, which is why teams reach for it, but the rights you actually hold come from the specific model's terms, and those differ across this catalog. Read them for the exact slug you ship, keep the prediction ID as your record of what produced what, and get your own legal sign-off rather than treating a guide like this one as clearance.

Store the artifact. Regenerating is not the same as retrying.
Store the artifact. Regenerating is not the same as retrying.

Wrapping up

The build is smaller than it looks and the operations are bigger. One POST and one poll gets you audio in an afternoon. What takes the other two weeks is the queue, the storage, the mastering pass, the idempotent webhook handler and the listen step, and none of that is glamorous enough to appear in a tutorial.

So build in that order. Get one call working, then put a queue in front of it, then master the output, then store it properly. By the time you add stems and extend, you'll have a feature rather than a demo.

You can run these models on Eachlabs, with the stem separation, mastering and workflow steps behind the same key.

Frequently Asked Questions

What outputs do I get, and what formats can I ship?

A successful prediction returns a URL in output pointing at the generated audio, plus metrics.predict_time for what the run took. Some endpoints return several outputs when you raise n, so handle both a single value and an array. Two practical notes. Copy the file into your own storage immediately rather than serving the returned URL to users, since that location is an output, not your asset store. And run everything through audio-mastering with the preset that matches your delivery target, because loudness consistency across a library is the difference between an app that feels finished and one that doesn't.

How much integration effort is a music feature, realistically?

The first working call is an afternoon. A shippable feature is closer to two weeks, and almost none of that time is the API. It's a job queue, because generation is too slow for a request cycle. Durable storage with deterministic keys and the prediction ID recorded alongside, because most outputs are not reproducible. A mastering step. An idempotent webhook handler. And a review or preview step if vocals are involved. Keep the chain in a workflow with the version pinned so what you tested is what runs, and the second month is much quieter than the first.

How does this scale when a lot of users generate at once?

The ceiling you'll hit is concurrency, not request rate. There's no per-second limit on predictions, but there is an account concurrency cap, and exceeding it returns 429 with a details field naming the cap. Since a rejected request creates no prediction, the correct handling is to hold the job in your own queue and resubmit with backoff, never to surface it to the user as a failure. Design the queue as a first-class part of the feature: a bounded worker pool, per-user fairness so one power user can't starve everyone else, and webhooks rather than polling so you're not spending requests waiting on long jobs.

Which model is the best AI music generation API for an app?

There isn't a single best one, and any answer that names one without asking what your users give you is guessing. Match the model to the input. Text plus a need for exact length, tempo and key goes to ace-step-1-5-text-to-music. Lyrics and vocals go to mureka-generate-song, where structure tags come back timestamped so your UI can navigate the track. An image or clip to score against goes to mureka-generate-soundtrack. Instrumental-only goes to mureka-generate-instrumental. Because all of them share one request envelope, testing three against your actual prompts costs an afternoon and a changed string, which is a better way to pick than reading a recommendation.