AI music API in TypeScript: generate, wait and save an MP3

Call Sume's Music Router from TypeScript: generateMusicRouter, waitForJob, then download the audio artifact. $0.125 per track, Google lists Lyria 3.5 at $0.08.

5 min readSume
All posts

To generate AI music from TypeScript, install @sume-com/sdk, call generateMusicRouter with a prompt, wait with waitForJob, and download the audio artifact from the finished job. Sume charges a fixed $0.125 per accepted Music generation, whatever the prompt length. Google's Gemini API pricing page, read on 2026-10-05, lists Lyria 3.5 at $0.08 per song with no free tier.

The SDK side comes from the TypeScript SDK page and Waiting for runs and jobs; the request fields come from the Music Router docs. The SDK is @sume-com/sdk@0.2.0, has no runtime dependencies, and sends x-api-key only, so do not add an Authorization header yourself.

The whole script

This runs on Node 18+ as an ES module. It submits with mode: "async", which the SDK docs recommend for waitForJob, then reads the audio artifact and writes it to disk. model is optional: omit it or send sume/music-auto and Sume picks the engine (Lyria 3.5 today); send lyria-3.5 or lyria-3-pro to pin one.

import { writeFile } from "node:fs/promises";
import { createSumeClient, generateMusicRouter, waitForJob } from "@sume-com/sdk";

async function main() {
  const apiKey = process.env.SUME_API_KEY;
  if (!apiKey) throw new Error("Set SUME_API_KEY first");
  const client = createSumeClient({ apiKey });

  const { data, error } = await generateMusicRouter({
    client,
    headers: { "Idempotency-Key": "launch-bed-001" },
    body: {
      model: "sume/music-auto",
      prompt:
        "Bright indie-pop bed, 112 BPM, G major. Muted guitar, claps, " +
        "warm bass. Builds at 0:15. A 30-second track. Instrumental, no vocals.",
      mode: "async",
    },
  });
  if (error || !data) throw new Error(JSON.stringify(error));

  const job = await waitForJob(data.data.request_id, { client });
  if (job.status !== "completed") {
    throw new Error(`music job ${job.id} ended as ${job.status}`);
  }
  const audio = job.result?.artifacts?.find((a) => a.type === "audio");
  if (!audio?.url) throw new Error("no audio artifact on the job");

  const file = await fetch(audio.url);
  await writeFile("bed.mp3", Buffer.from(await file.arrayBuffer()));
  console.log("saved bed.mp3 from job", job.id);
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

What each step does

  • Idempotency-Key makes a retry of the same submit return the original job instead of a second charge. Use one key per track you intend to make, not one per attempt.
  • waitForJob polls /v1/jobs/:id/status every 2 seconds at minimum, or longer when the server's next_poll_after_seconds asks for it. Its default deadline is 20 minutes.
  • It resolves for failed and canceled jobs too, so the script checks job.status rather than relying on a catch block.
  • The audio is in result.artifacts[] where type is audio, usually audio/mpeg on media.sume.com. Raw provider URLs are not public outputs.
  • The raw job JSON carries request.routed_model, which names the engine that ran when you submitted sume/music-auto.

Limits to design around

There is no duration field and no negative_prompt: Sume rejects duration and duration_seconds, and a non-empty negative_prompt returns a 400. Put the length in the prompt ("a 30-second track") and the exclusions in the positive text ("Instrumental, no vocals"). Prompts run 1 to 5,000 characters.

A waitForJob timeout does not cancel the job. It keeps running and still bills, so store the job id and read it again with getApiJob later instead of resubmitting under a new key.

Music Router request fields used above (Sume docs, read 2026-10-05)
FieldValue in the scriptRule
modelsume/music-autoOmit it for the same default; lyria-3.5 and lyria-3-pro pin an engine
prompt30-second indie-pop brief1 to 5,000 characters
modeasyncPair with waitForJob; sync and subscribe cap at 30 seconds
Idempotency-Keylaunch-bed-001Same key and payload returns the original job

Next

Once the file is saved, put it under narration with a Timeline soundtrack block, which takes gain_db, loop, fade_out_seconds and duck_db. See Add background music to a video with an API.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume