# Generate a Song with an AI API: From Prompt to MP3

> A full tutorial for generating a song through the Tchavi API: prompt vs lyrics, the async job, polling, errors caught before billing, and real cost.

- Published: 2026-11-04
- Author: Gael — Tchavi founder
- Canonical: https://tchavi.com/en/blog/generate-a-song-with-an-ai-api

---


Generating a song through an API comes down to one call: `POST /v1/music/generations`, with a `prompt` and, if you want specific words, a `lyrics` field. The response isn't the audio file — it's a job, which finishes a minute or so later with an MP3 to fetch. This guide starts from an empty file and ends with a finished track: the exact code in cURL and Node.js, the two models available, what separates the prompt from the lyrics, the errors the API returns before charging you anything, and the real cost.

## In short

- **`POST /v1/music/generations`** is asynchronous: it answers `202` with a job `id` while the track renders in the background.
- **Two models**: `minimax-music-2.6` writes and performs a song from a description (329.242 credits, about KES 25 at October 2026 rates); `minimax-music-cover` re-sings a track you supply (658.484 credits, about KES 50). Billing is flat per track, up to five minutes, with no duration parameter.
- **The `prompt` carries musical direction** — key, tempo, instruments, voice, mood. **The `lyrics` carry the words** — with `[Verse]` / `[Chorus]` markers. Mixing the two up gives a vaguer result.
- You poll `GET /v1/jobs/:id` until `completed` or `failed`. A finished job answers with `output.audio_url` — not `video_url`, which belongs to the video endpoint.
- Three combinations are refused with a `400`, before any billing: a cover with no `source_audio_url`, a `source_audio_url` sent to a generator, and `instrumental` together with `lyrics`.
- On our own runs, the song took 65.9 s to render for a 1:10 track; the cover took 76.4 s for a 1:06 track.

## The first call: write a song

Create a key in your dashboard, then send a `prompt`:

```bash
curl -X POST https://tchavi.com/api/v1/music/generations \
  -H "Authorization: Bearer $TCHAVI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-music-2.6",
    "prompt": "Afrobeats mid-tempo, 100 BPM, key of C major, highlife guitar, round bass, warm female vocal, joyful reunion mood"
  }'
```

The response arrives right away, well before the track exists:

```json
{
  "id": "0f3c1a2b-...",
  "object": "music.generation",
  "status": "pending",
  "model": "minimax-music-2.6",
  "created_at": "2026-11-05T09:12:03.000Z",
  "tchavi": {
    "credits_used": 329.242,
    "credits_remaining": 4821.5
  }
}
```

`credits_used` is charged on submission, not when the job finishes. If the job fails, that amount is refunded in full — there's nothing special your code has to do for that.

## Polling the job to a finished file

The returned `id` is polled on the [async jobs endpoint](https://tchavi.com/en/docs/jobs). A simple cURL loop:

```bash
JOB_ID="0f3c1a2b-..."

while true; do
  STATUS=$(curl -s https://tchavi.com/api/v1/jobs/$JOB_ID \
    -H "Authorization: Bearer $TCHAVI_API_KEY")
  echo "$STATUS" | grep -o '"status":"[a-z]*"'
  case "$STATUS" in
    *'"status":"completed"'*|*'"status":"failed"'*) break ;;
  esac
  sleep 5
done

echo "$STATUS"
```

A finished job answers with the file:

```json
{
  "id": "0f3c1a2b-...",
  "status": "completed",
  "model": "minimax-music-2.6",
  "type": "MUSIC",
  "output": {
    "audio_url": "https://cdn.tchavi.com/.../track.mp3",
    "file_size_mb": 1.7,
    "url_expires_at": "2026-11-06T09:14:51.000Z"
  }
}
```

That's `output.audio_url`, not `output.video_url` — the latter belongs to video jobs, not music. `output.url_expires_at` is the deadline to download the file: past that, the signed link stops working.

## With the Node.js SDK (coming soon)

The two cURL calls above are enough to write a song and fetch the file today — that's the path that works right now. The SDK, `@tchavi/sdk`, doesn't do this yet: `client.music.generations` is landing in a future release, not published yet. The snippet below shows what that will look like once the SDK ships it — don't install it expecting this to work today:

```bash
npm install @tchavi/sdk
```

```javascript
import Tchavi from '@tchavi/sdk';

const client = new Tchavi({ apiKey: process.env.TCHAVI_API_KEY });

const submission = await client.music.generations.create({
  model: 'minimax-music-2.6',
  prompt:
    'Afrobeats mid-tempo, 100 BPM, key of C major, highlife guitar, round bass, warm female vocal',
});

console.log(submission.id, submission.tchavi.credits_used);

let job = await client.jobs.retrieve(submission.id);
while (job.status !== 'completed' && job.status !== 'failed') {
  await new Promise((r) => setTimeout(r, 5000));
  job = await client.jobs.retrieve(submission.id);
}

console.log(job.status, job.output?.audio_url);
```

Or, shorter, the helper that does both in one call (it waits up to 10 minutes by default, enough for a full song to render):

```javascript
const job = await client.music.generations.createAndWait({
  model: 'minimax-music-2.6',
  prompt: 'Afrobeats mid-tempo, 100 BPM, highlife guitar, round bass, warm female vocal',
});

console.log(job.status, job.output?.audio_url);
```

Once shipped, the SDK should validate `prompt` and `model` before it even sends the request, and reject `instrumental` combined with `lyrics` locally, so you'd get the error immediately, without a round trip. Until then, both checks already happen server-side with the cURL calls shown above.

Here is what those two calls produce — a song written, sung and mixed by the model, from the prompt and lyrics below:

<MusicDemo track="song" />

## The prompt and the lyrics: two jobs, not one

This is the detail that changes the result the most: `prompt` and `lyrics` don't overlap.

The **`prompt`** describes the music itself: genre, tempo in BPM, key, instruments, voice type, mood. A prompt that only gives a mood — "a happy song about friendship" — leaves the model to guess the tempo and the key, and the result varies more from one call to the next. A prompt that states the tempo and the key gives something more predictable each time.

The **`lyrics`** carry the words and the structure of the track, with section markers:

```
[Verse]
We lost touch after you left Cotonou
Then ran into each other, out of the blue
[Chorus]
Come back, come back, the city kept you a seat
Come back, come back, nothing's really changed its beat
```

Leave out `lyrics` and `minimax-music-2.6` writes its own from the `prompt`. Send `instrumental: true` for no vocals at all — but never together with `lyrics`: one asks for no voice, the other supplies words to sing, and the API refuses the contradiction before starting the job.

## Covering an existing track: `minimax-music-cover`

`minimax-music-cover` keeps the melody of a track you supply and changes everything around it — genre, voice, instruments, arrangement. It **requires** `source_audio_url`, a publicly reachable MP3 or WAV:

```bash
curl -X POST https://tchavi.com/api/v1/music/generations \
  -H "Authorization: Bearer $TCHAVI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "minimax-music-cover",
    "prompt": "Soft acoustic version, female vocal, nylon-string guitar",
    "source_audio_url": "https://example.com/my-demo.mp3"
  }'
```

Twice the price of the model that writes a song from scratch: 658.484 credits, about KES 50, still a flat fee per track.

The cover below starts from the song you just heard, not from someone else's recording: same melody, acoustic arrangement.

<MusicDemo track="cover" />

## Errors caught before billing

Three combinations are refused with a `400`, before any credits are charged:

| Case                                             | Response                                             |
| ------------------------------------------------ | ---------------------------------------------------- |
| `minimax-music-cover` with no `source_audio_url` | `400 bad_request` — the model has nothing to re-sing |
| `source_audio_url` sent to `minimax-music-2.6`   | `400 bad_request` — a generator doesn't need one     |
| `instrumental: true` with `lyrics` set           | `400 bad_request` — the two contradict each other    |
| `prompt` or `model` missing                      | `400 bad_request` (or rejected locally by the SDK)   |

None of these errors cost you a credit. If a job does start and fails later — on the model's side — the credits charged at submission are refunded in full; you see it in `tchavi.credits_refunded` on the job.

## What it costs

KES figures are at October 2026 rates (about 13,204 credits per KES 1,000, so 1 credit is about KES 0.0757):

| Model                 | What it does                                         | Credits per track | Price in KES |
| --------------------- | ---------------------------------------------------- | ----------------- | ------------ |
| `minimax-music-2.6`   | Writes and performs a song, up to 5 minutes          | 329.242           | about KES 25 |
| `minimax-music-cover` | Re-sings a track you supply, in a style you describe | 658.484           | about KES 50 |

That's a flat fee per track, not per second: a thirty-second song and a five-minute one cost the same. At that rate, a KES 250 top-up (about 3,300 credits) covers roughly 10 songs with `minimax-music-2.6`, or about 5 covers with `minimax-music-cover`.

The 50 credits granted at email verification don't cover a song: `minimax-music-2.6` needs 329.242 at minimum. You'll need to top up your account before testing music — the cost of a try stays low, but it isn't free.

## What we measured

Two runs, not a benchmark:

- `minimax-music-2.6`: 65.9 seconds to render a 1:10 track.
- `minimax-music-cover`: 76.4 seconds to render a 1:06 track.

Render time doesn't track the length of the output: both took around a minute, whether the result ran one minute or five.

## How to try it

- Read the [music generation docs](https://tchavi.com/en/docs/music-generation) and the [async jobs docs](https://tchavi.com/en/docs/jobs).
- Both models have their own page with a demo: [`minimax-music-2.6`](https://tchavi.com/en/m/minimax/minimax-music-2.6) and [`minimax-music-cover`](https://tchavi.com/en/m/minimax/minimax-music-cover).
- The [music catalogue](https://tchavi.com/en/models/music) lists both side by side.

## FAQ

### Can you choose the length of the track?

No. There's no duration parameter: the model decides, up to five minutes, and the price doesn't change based on what it produces.

### What happens if `lyrics` is present but empty?

Only send the field when you actually have words to give. An empty string isn't treated as "no lyrics."

### Can music be tested with the free signup credits?

No. The 50 credits granted at email verification don't cover a track: `minimax-music-2.6` needs 329.242. A top-up is required before your first try.

### Can the job be polled indefinitely?

Yes, `GET /v1/jobs/:id` stays available after the job finishes, as long as the file hasn't expired. `output.url_expires_at` gives the deadline to download the MP3.

### Does the SDK handle covers too?

Not yet on npm: `client.music.generations` is landing in a future SDK release. Until then, call `POST /v1/music/generations` directly with `model: "minimax-music-cover"` and a `source_audio_url`, as shown above.

[create your account and top up KES 250](https://tchavi.com/en/register?next=%2Fdashboard%2Fbilling%3Famount%3D250%26currency%3DKES%26from%3Dblog-musique-api-en)

> _This article is part of the series "Building an AI product from Africa". Tchavi is an OpenAI-compatible AI API gateway: one key gives access to over 100 text, image, audio and video models, and you pay for it with mobile money, no bank card ([countries and operators](https://tchavi.com/en/docs/payments)). More guides on [the blog](https://tchavi.com/en/blog). Follow us: [LinkedIn](https://www.linkedin.com/company/tchavi) and [X](https://x.com/tchaviai)._

