> ## Documentation Index
> Fetch the complete documentation index at: https://docs.soundpiece.co/llms.txt
> Use this file to discover all available pages before exploring further.

# New song

> Generate a song with vocals from your lyrics and a prompt or a reference track

`create.new_song` writes and performs a complete song, with vocals singing the lyrics you give it. You describe the sound either in words (a `prompt`) or by pointing at a track whose style to follow (a `reference`). Use it for jingles, personalised songs, demo tracks and anything else that needs a sung vocal.

For music without vocals, use [`create.new_instrumental`](/guides/create/new_instrumental).

***

## The request

| Field | Required | What it is |
| - | - | - |
| `lyrics` | yes | The words to sing, up to 3,000 characters. Structure them with section markers such as `[Verse]` and `[Chorus]`. |
| `prompt` | one of these two | A description of the sound, up to 1,024 characters, e.g. `"synthwave, melancholic, mid-tempo"`. |
| `reference` | one of these two | A track whose style to follow: `{"url": "https://..."}`, a public link to a WAV, MP3 or FLAC file. |
| `n_variations` | no | How many different songs to make from the same request, from 1 to 3. Defaults to 1. |
| `loudness` | no | Normalise every song to a target loudness, at no extra cost. Leave it out to get them as produced. See [Loudness](/loudness). |
| `idempotency_key` | yes | A UUID that identifies this job. The [reference client](/client) fills it in for you. |
| `callback_url` | no | An HTTPS address to receive a [webhook](/webhooks) when the work is finished. |

Send `prompt` or `reference`, not both.

Every field, with its exact rules: [submit](/api-reference/create/new_song/submit) and [poll](/api-reference/create/new_song/poll) in the API Reference.

***

## Example

This makes two versions of a song from a prompt, both normalised for streaming, and saves them. It uses the [reference client](/client); save `soundpiece.py` or `soundpiece.mjs` next to it first.

```python song.py theme={null}
import soundpiece

song = soundpiece.run("create.new_song", {
    "lyrics": "[Verse]\nDriving all night with the radio on\n\n[Chorus]\nCity lights, carry me home",
    "prompt": "synthwave, melancholic, mid-tempo",
    "n_variations": 2,
    "loudness": {"preset": "streaming"},
})

for number, output in enumerate(song["outputs"], start=1):
    soundpiece.download(output["url"], f"song-{number}.flac")
    print(f"Saved song-{number}.flac")
```

```javascript song.mjs theme={null}
import { run, download } from "./soundpiece.mjs";

const song = await run("create.new_song", {
  lyrics: "[Verse]\nDriving all night with the radio on\n\n[Chorus]\nCity lights, carry me home",
  prompt: "synthwave, melancholic, mid-tempo",
  n_variations: 2,
  loudness: { preset: "streaming" },
});

for (const [index, output] of song.outputs.entries()) {
  await download(output.url, `song-${index + 1}.flac`);
  console.log(`Saved song-${index + 1}.flac`);
}
```

```bash cURL theme={null}
#!/usr/bin/env bash
# Submit, wait and download by hand. Needs curl, jq and uuidgen.
set -euo pipefail
API=https://api.soundpiece.co/v1
AUTH="Authorization: Bearer $SOUNDPIECE_API_KEY"

# 1. Submit. Sending this exact body again (same idempotency_key) is safe.
body=$(jq -n --arg key "$(uuidgen)" '{
  idempotency_key: $key,
  lyrics: "[Verse]\nDriving all night with the radio on\n\n[Chorus]\nCity lights, carry me home",
  prompt: "synthwave, melancholic, mid-tempo",
  n_variations: 2,
  loudness: { preset: "streaming" }
}')
response=$(curl --silent --show-error --max-time 30 --request PUT "$API/create.new_song" \
  --header "$AUTH" --header "Content-Type: application/json" --data "$body")
result=$(jq -e .song <<<"$response") || { echo "$response"; exit 1; }
id=$(jq -r .id <<<"$result")

# 2. Wait. Each call returns as soon as the work is finished, or after 20 seconds.
while [ "$(jq -r .status <<<"$result")" = processing ]; do
  response=$(curl --silent --show-error --max-time 30 "$API/create.new_song?id=$id&wait=true" \
    --header "$AUTH")
  result=$(jq -e .song <<<"$response") || { echo "$response"; exit 1; }
done

# 3. Download each song, or show what went wrong.
if [ "$(jq -r .status <<<"$result")" = failed ]; then
  jq .error <<<"$result"
  exit 1
fi
number=0
for url in $(jq -r '.outputs[].url' <<<"$result"); do
  number=$((number + 1))
  curl --silent --show-error --fail --output "song-$number.flac" "$url"
  echo "Saved song-$number.flac"
done
```

***

## The result

`run` returns the finished `song` object, with one entry in `outputs` per variation:

```json theme={null}
{
  "id": "op_01ks2p3hc0etfb59tdwh9edd1r",
  "status": "ready",
  "created_at": "2026-10-10T09:15:02Z",
  "idempotency_key": "550e8400-e29b-41d4-a716-446655440000",
  "lyrics": "[Verse]\nDriving all night with the radio on\n\n[Chorus]\nCity lights, carry me home",
  "prompt": "synthwave, melancholic, mid-tempo",
  "n_variations": 2,
  "loudness": { "target_lufs": -14.0, "allow_compression": true, "preset": "streaming" },
  "outputs": [
    {
      "url": "https://cdn.soundpiece.co/...signed...",
      "expires_at": "2026-10-10T09:31:40Z",
      "duration": 87.4,
      "loudness": { "lufs": -14.0, "true_peak_dbtp": -1.3, "compressed": false }
    },
    {
      "url": "https://cdn.soundpiece.co/...signed...",
      "expires_at": "2026-10-10T09:31:40Z",
      "duration": 92.1,
      "loudness": { "lufs": -14.1, "true_peak_dbtp": -1.0, "compressed": true }
    }
  ],
  "error": null
}
```

| Field | What it means |
| - | - |
| `lyrics`, `prompt`, `n_variations` | What you asked for. `prompt` is `null` when you sent a `reference` instead; the reference URL is never echoed. |
| `loudness` | Present only if you asked for normalisation: the target, and `preset` when you named one. |
| `outputs[].url` | A signed link to one song, as a FLAC file (44.1 kHz). It needs no API key. |
| `outputs[].expires_at` | When the link stops working. Poll the operation again for a fresh one. |
| `outputs[].duration` | The song's length in seconds. There is no length setting: it follows from the lyrics and the style. |
| `outputs[].loudness` | Present only if you asked for normalisation: `lufs`, `true_peak_dbtp` and `compressed`, measured on that song. |

***

## Choosing parameters

* **Lyrics.** Put each section marker (`[Intro]`, `[Verse]`, `[Chorus]`, `[Bridge]`, `[Outro]`, `[Instrumental]`) on its own line, and each sung line on its own line under it.

* **Prompt or reference?** A prompt is quickest: mix genre, mood, tempo and instruments, separated by commas, e.g. `"rock song, energetic, male vocal"`. A reference is better when you have a track whose feel you want to match. To use one, send it in place of `prompt`:

  ```json theme={null}
  {
    "lyrics": "[Verse]\nDriving all night with the radio on\n\n[Chorus]\nCity lights, carry me home",
    "reference": { "url": "https://example.com/audio/reference.mp3" }
  }
  ```

* **`n_variations`.** Each variation is a different take on the same request. Ask for two or three when you want to pick the best; each one costs the same as a single song.

* **Loudness.** Off unless you include the block. Add `{"preset": "streaming"}` (or `podcast`, `broadcast`, or a `target_lufs`) when the songs go straight to a platform. See the [loudness normalisation guide](/guides/adapt/normalise_loudness#choosing-a-target-by-destination).

***

## Errors

**When you submit** (the request is refused, nothing is charged):

| Status | `type` | Why |
| - | - | - |
| `422` | `string_too_short` / `string_too_long` | `lyrics` is empty or over 3,000 characters, or `prompt` is empty or over 1,024. |
| `422` | `missing` | `lyrics` is missing, or neither `prompt` nor `reference` was sent. |
| `422` | `extra_forbidden` | Both `prompt` and `reference` were sent (or any field the endpoint doesn't know). |
| `422` | `source_too_large` | The reference's server says the file is over 50 MB. (When it doesn't say, the size is checked during processing instead.) |
| `422` | `idempotency_key_reused` | The key was already used for a different endpoint. |
| `402` | `insufficient_credits` | Your balance is below 3 credits per variation. |

**While processing** (the operation finishes as `failed`, with an `error.code`):

| Code | Why |
| - | - |
| `tos_violation` | The lyrics, prompt or reference were rejected by our content policy. |
| `source_unreachable`, `source_format_unsupported`, `source_too_short`, `source_too_long`, `source_too_large` | Only with a `reference`: it couldn't be fetched, isn't WAV, MP3 or FLAC audio, or is outside the limits below. |
| `internal_error`, `generation_timeout` | Something went wrong on our side. Resubmit with the same idempotency key. |

**Reference limits for this endpoint:** WAV, MP3 or FLAC, from 1 second to 5 minutes long, and at most 50 MB.

See [Errors](/errors) for the response shapes, and for the errors every endpoint can return (`401`, `403`, `429`).

***

## Cost

**3 credits per variation**, so `n_variations: 2` costs 6. Normalisation costs nothing extra. Resending a request with the same idempotency key is not charged again.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.