---
name: soundpiece
description: Build integrations with the Soundpiece audio API, an HTTP API that generates songs, instrumentals, samples and sound effects, remixes tracks, separates stems, cuts audio to length, normalises loudness and mixes a voice over music. Use when writing code that calls api.soundpiece.co, or when a user wants to generate, remix or adapt audio programmatically with Soundpiece.
license: Proprietary
metadata:
  author: soundpiece
  version: "2.0"
---

# Soundpiece API

Soundpiece is an RPC-style HTTP API for generating and adapting audio. Each endpoint is a named
action (`create.new_song`, `adapt.auto_duck`). There are no resource hierarchies.

- Base URL: `https://api.soundpiece.co/v1`, e.g. `https://api.soundpiece.co/v1/create.new_song`.
- Auth: `Authorization: Bearer <key>` on every API call. Keys start with `spk_`. Read the key from
  the `SOUNDPIECE_API_KEY` environment variable; never write it into code.
- Docs: https://docs.soundpiece.co. Every page is available as Markdown by adding `.md` to its URL.
  Index: https://docs.soundpiece.co/llms.txt.

## How every endpoint works

Every operation is asynchronous and follows the same three steps:

1. **Submit:** `PUT /v1/<endpoint>` with a JSON body that includes an `idempotency_key` (a UUID you
   choose). The response is `200` with one key named for the result, e.g.
   `{"song": {"id": "op_01ks2p3hc0etfb59tdwh9edd1r", "status": "processing", ...}}`.
2. **Poll:** `GET /v1/<endpoint>?id=<id>&wait=true`. With `wait=true` the call returns as soon as
   the operation is `ready` or `failed`, or after 20 seconds if it is still `processing`; repeat
   until it is not `processing`. Or pass `callback_url` on the submit to receive a webhook.
3. **Download:** when `status` is `ready`, each output has a signed `url` (FLAC, 44.1 kHz) that
   needs no auth header. It expires at `expires_at`; poll again for a fresh URL.

`status` is `processing`, `ready` or `failed`. On `ready`, `output` (one file) or `outputs` (a
list) is populated; on `failed`, `error` is populated with `code` and `message`. Never both.
Operation ids match `^op_[0-9a-z]{26}$`; treat them as opaque.

## Use the reference client

Do not hand-write the HTTP loop. Copy the reference client from
https://docs.soundpiece.co/client.md into the project unchanged: `soundpiece.py` (Python, needs
`requests`) or `soundpiece.mjs` (JavaScript, Node 18+, no dependencies). It provides:

- `run(endpoint, body, idempotency_key=None, timeout=600)` (JS: `run(endpoint, body, { idempotencyKey, timeout })`):
  submits, long-polls until `ready`, returns the result object, raises `SoundpieceError` on
  failure and gives up after `timeout` seconds.
- `download(url, path)`: streams one output to a file.
- `send(method, endpoint, ...)`: one API call with a 30 s timeout, retrying network errors, `429`
  and `5xx` up to 5 attempts, honouring `Retry-After` (1, 2, 4, 8, 16 s when it is missing or 0).
- `SoundpieceError`: a rejected request (`4xx`, message carries `detail`), repeated failure, or a
  `failed` operation (message is `<error.code>: <error.message>`).

```python
import soundpiece

result = soundpiece.run("adapt.normalise_loudness", {
    "source": {"url": "https://example.com/audio/track.mp3"},
})
soundpiece.download(result["output"]["url"], "track.flac")
```

`run` creates a fresh idempotency key per call and reuses it across its own retries. For a job that
must survive a crash or restart, create the key yourself, store it with the job before the first
call, and pass it as `idempotency_key` on every attempt.

## Endpoints

Every submit takes `idempotency_key` (required, UUID) and `callback_url` (optional, HTTPS).
Sources are `{"url": "https://..."}`: a public URL to WAV, MP3 or FLAC audio, at most 50 MB and
5 minutes long.

| Endpoint | What it does | Body (beyond the common fields) | Result key | Output | Credits |
|---|---|---|---|---|---|
| `create.new_song` | Song with vocals | `lyrics` (1-3000 chars) and either `prompt` (1-1024) or `reference`; `n_variations` 1-3 (default 1); `loudness` | `song` | `outputs` | 3 per variation |
| `create.new_instrumental` | Instrumental, no vocals | either `prompt` or `reference`; `n_variations`; `loudness` | `instrumental` | `outputs` | 3 per variation |
| `create.new_sample` | Short clip at a duration | `prompt`, `duration` (seconds), `loopable` (default false) | `sample` | `output` | 3 |
| `create.new_fx` | One-shot sound effect | `prompt` (1-450 chars), `duration` (seconds) | `fx` | `output` | 3 |
| `create.remix_song` | New variations of a song with vocals | `source` (10 s to 5 min), `lyrics`, `prompt`; `n_variations`; `loudness` | `remixed_song` | `outputs` | 3 per variation |
| `create.remix_instrumental` | New variations of an instrumental | `source`; optional `prompt`; `n_variations`; `loudness` | `remixed_instrumental` | `outputs` | 3 per variation |
| `adapt.separate_stems` | Split a mix into stems | `source` | `stems` | `outputs`, each with `kind`: `vocals`, `drums`, `bass`, `other`, `instrumental` | 3 |
| `adapt.auto_cut_down` | Trim to a length at natural cut points | `source` (at least 2.5 s), `target_duration` (5-300 s, at most twice the source); `loudness` | `auto_cut` | `output` | 1 |
| `adapt.normalise_loudness` | Normalise to a target loudness | `source`; `loudness` (optional, defaults apply) | `normalised_audio` | `output` | 1 |
| `adapt.auto_duck` | Mix a voice over music, ducking the music under speech | `music`, `voice` (different sources); `loudness` (optional, defaults apply) | `ducked_audio` | `output` | 1 |

The result object echoes the request's fields (never the source URLs). Each endpoint has a guide
with a complete example, its result, errors, source limits and cost, at
`https://docs.soundpiece.co/guides/<group>/<name>.md`, e.g.
https://docs.soundpiece.co/guides/adapt/normalise_loudness.md. Field details and response shapes:
https://docs.soundpiece.co/api-reference, with each endpoint at
`/api-reference/<group>/<name>/submit` (the `PUT`) and `/api-reference/<group>/<name>/poll` (the
`GET`).

## Loudness

`loudness` is either `{"target_lufs": <-30 to -6, default -14>, "allow_compression": <default true>}`
or `{"preset": "streaming" | "podcast" | "broadcast", "allow_compression": <default true>}`, never
both (`preset` with `target_lufs` is a `422`, `extra_forbidden`). `{}` means the defaults.

| Preset | Target | For |
|---|---|---|
| `streaming` | -14 LUFS | music for streaming services and online video (same as the default) |
| `podcast` | -16 LUFS | podcasts and spoken word |
| `broadcast` | -23 LUFS | TV and radio broadcast (EBU R128) |

A preset's value is fixed and never changes. Peaks are always kept at or below -1 dBTP; that
ceiling is not configurable. When audio can't reach the target by a level change alone, `allow_compression: true`
compresses the dynamics to land close to it; `false` keeps the dynamics and lands quieter.

- **Opt-in** on `create.new_song`, `create.new_instrumental`, `create.remix_song`,
  `create.remix_instrumental` and `adapt.auto_cut_down`: omit `loudness` to get outputs exactly as
  produced; include it (even `{}`) to normalise them.
- **Always applied** on `adapt.normalise_loudness` and `adapt.auto_duck`: `loudness` is optional
  and omitting it means -14 LUFS with compression allowed.
- Not available on `create.new_sample`, `create.new_fx` or `adapt.separate_stems`.

Normalising costs no extra credits. When normalisation was applied, each output carries
`loudness: {lufs, true_peak_dbtp, compressed}` measured on the delivered audio, and the operation
echoes `loudness: {target_lufs, allow_compression}`, plus `preset` only when one was sent. Details:
https://docs.soundpiece.co/loudness.md.

## Errors

- HTTP errors have a JSON body with `detail`: a string, or for `422` a list of
  `{loc, msg, type}`.
  - `401`: missing, invalid or revoked key.
  - `403` (`{"detail": "not_authorized"}`): the key is valid but the account has no API access
    right now (e.g. its plan ended). Don't retry; the account owner must contact support.
  - `402`: not enough credits (`detail.type` is `insufficient_credits`). Nothing was started or
    charged; top up and resend with the same idempotency key.
  - `404`: only for an unknown endpoint URL.
  - `422`: invalid request. An unknown operation id on a poll is a `422` with type
    `operation_not_found`, not a `404`.
  - `429` and `5xx`: retry with the same idempotency key, honouring `Retry-After`.
  - Any other `4xx`: fix the request; don't retry it unchanged.
- Operation failures (`status: "failed"`) carry a stable `error.code`. Branch on the code, never on
  `message`:
  - every endpoint: `internal_error`, `generation_timeout`;
  - endpoints with a source or reference: `source_unreachable`, `source_format_unsupported`,
    `source_too_short`, `source_too_long`, `source_too_large`;
  - generation endpoints: `tos_violation`;
  - `create.remix_song`: `source_has_no_vocals` (use `create.remix_instrumental` instead);
  - `adapt.auto_cut_down`: `target_duration_too_long`.
  - On `adapt.auto_duck`, `error.source` (`music` or `voice`) names the failing input when it is
    known; it is absent otherwise.

Full catalogue: https://docs.soundpiece.co/errors.md.

## Production checklist

- Read the API key from the environment, never from code.
- Use one idempotency key per job; store it before the first attempt and reuse it on every retry
  and re-run, so nothing is created or charged twice.
- Put a timeout on every request (more than 20 s, so a long poll can finish).
- Retry network errors, `429` and `5xx`, honouring `Retry-After`; never retry other `4xx`
  unchanged.
- Poll with `wait=true` and a deadline, or use webhooks.
- Webhooks: verify the signature (Standard Webhooks: `webhook-id`, `webhook-timestamp`,
  `webhook-signature`); return `2xx` within 10 seconds and process afterwards; deduplicate on
  `webhook-id`; fall back to polling.
- Download results promptly; get a fresh URL by polling again; never store download URLs.
- Branch on `error.code`, never `error.message`.
- Handle `402`.

Details: https://docs.soundpiece.co/best-practices.md.

## Gotchas

- **One idempotency key per job.** A new key per attempt creates duplicate work and duplicate
  charges. Reusing a key that was used for a different endpoint is a `422`
  (`idempotency_key_reused`).
- **Use `wait=true` when polling.** Without it, a poll returns immediately and a tight loop wastes
  your rate budget.
- **Download URLs expire.** They are signed and short-lived; check `expires_at`, and poll the
  operation again for a fresh URL. Store the audio, not the URL.
- **`adapt.auto_duck` needs two different sources.** The same URL, or two URLs serving the same
  file, is a `422` with type `same_source` at `["body", "voice", "url"]`. The output is as long as
  the longer input.
- **Loudness is opt-in on most endpoints but always applied on `adapt.normalise_loudness` and
  `adapt.auto_duck`.** On the opt-in endpoints, sending `{}` turns normalisation on.
- **`create.remix_song` keeps the source's vocal line,** so its source must contain vocals.
- **Credits are charged when a submit succeeds,** per variation where `n_variations` applies
  (`n_variations: 3` on `create.new_song` costs 9). A replayed submit with the same key is not
  charged again.
- **Webhooks need an active signing secret** on the account; without one, no callbacks are sent
  even if `callback_url` is set. The body is exactly what the matching `GET` returns; the event
  kind is in the `webhook-type` header (`<endpoint>.ready` or `<endpoint>.failed`).
