# Lacuna Music

> Generate AI music tracks via the Lacuna Music API. Use when the user wants AI-generated music, BGM, jingles, soundtrack stingers, lofi/synthwave/orchestral/any-genre tracks, vocal songs from custom lyrics, or any on-demand audio generation. Triggers on phrases like "generate music", "make me a track", "compose a song", "AI music", "background music", "BGM for X", "jingle", "soundtrack".

- Skill: `joylink-ltd/lacuna-music` (Agent Skill)
- Install (CLI): `npx skillmds@latest add joylink-ltd/lacuna-music`
- Raw SKILL.md: https://api.skillmd.com/api/skills/joylink-ltd/lacuna-music/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: JOYLINK-LTD (https://skillmd.com/u/joylink-ltd)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/joylink-ltd/lacuna-music

---


# lacuna-music

Generate AI music programmatically through the [Lacuna Music API](https://lacuna.fm).

## When to use this

Activate when the user wants to:
- Generate a track from a style description (`"lofi piano, 70 bpm"`, `"synthwave, retro drums"`)
- Add background music to a video, demo, presentation, or app
- Produce a jingle, intro, outro, or stinger from a brief
- Compose a vocal song from custom lyrics

## Pick a transport

Three packages, same underlying API. Pick the first that fits the user's environment:

| When the user is using…                                            | Use         |
| ------------------------------------------------------------------ | ----------- |
| Claude Code, Claude Desktop, Cursor, Zed, or another MCP client    | `lacuna-mcp` |
| A Node / TypeScript script or backend                              | `lacuna-sdk` |
| Their shell, a CI job, a one-off prompt                            | `lacuna-toolkit` |

### `lacuna-mcp` (preferred for AI agents)

```sh
claude mcp add lacuna -- npx -y lacuna-mcp
```

Set `LACUNA_API_KEY` in the MCP env block. After adding, three tools become available: `generate_music`, `get_generation`, `wait_for_generation`. Call `generate_music` then `wait_for_generation` to receive the final `audio_url`.

### `lacuna-sdk`

```ts
import Lacuna from 'lacuna-sdk'

const lacuna = new Lacuna({ apiKey: process.env.LACUNA_API_KEY })

const task = await lacuna.music.generations.create({
  style: 'lofi piano, 70 bpm, mellow',
  title: 'Study session',
  instrumental: true,
})

const finished = await lacuna.music.generations.waitFor(task.id)
console.log(finished.tracks[0]?.audio_url)
```

### `lacuna-toolkit`

```sh
npx lacuna-toolkit music generate \
  --style "synthwave, retro drums, 110 bpm" \
  --title "Neon Drive" \
  --instrumental \
  --wait \
  --output json
```

## Authentication

Get a key at [lacuna.fm/profile/api](https://lacuna.fm/profile/api). It begins with `lyr_live_` and is shown once at creation. Pass it via `LACUNA_API_KEY` env var.

Music API access requires a **Pro** plan or above. Lower tiers receive `403 permission_error / tier_insufficient` — do not retry; tell the user to upgrade.

## Generation parameters

| Field                  | Required                | Models             | Notes                                                                   |
| ---------------------- | ----------------------- | ------------------ | ----------------------------------------------------------------------- |
| `style`                | yes                     | all                | Free-text style description, up to 1000 chars.                          |
| `title`                | yes                     | all                | Track title.                                                            |
| `lyrics`               | yes if not instrumental | all                | Plain text, up to 5000 chars. Use `[Verse]` / `[Chorus]` markers.       |
| `instrumental`         | no                      | all                | `true` skips lyrics.                                                    |
| `model`                | no                      | all                | `aether` (default) or `echo`. See model table below.                     |
| `vocal_gender`         | no                      | aether             | `'m'` or `'f'` — lead vocal hint.                                       |
| `negative_tags`        | no                      | aether             | Style tags to avoid.                                                    |
| `style_weight`         | no                      | aether             | 0–1.                                                                    |
| `weirdness_constraint` | no                      | aether             | 0–1.                                                                    |
| `audio_weight`         | no                      | aether             | 0–1.                                                                    |

The API rejects model-incompatible fields with `400 invalid_param` (e.g. passing `style_weight` with `model: 'echo'`).

### Models

| Codename | Best for                                                           | Notes                                                               |
| -------- | ------------------------------------------------------------------ | ------------------------------------------------------------------- |
| `aether` | Default. General-purpose, supports vocals + advanced weight knobs. | Supports vocal-gender and weight controls.                          |
| `echo`   | Full structured tracks up to three minutes.                        | Length is model-determined; no vocal-gender / weight knobs.         |

## Lifecycle

1. `create` returns immediately with a task in `pending` status.
2. Generation typically completes in **60–120 seconds**.
3. `waitFor` (SDK) / `--wait` (CLI) / `wait_for_generation` (MCP) polls until `ready` or `failed`.
4. On `ready`, `task.tracks[]` contains one or more renders, each with `audio_url`, `duration`, `title`, `lyrics`, `image_url`, `tags`.
5. On `failed`, inspect `task.error` — credits are refunded automatically.

For production workflows, prefer the `job.completed` webhook over polling. See [the SDK webhook docs](https://www.npmjs.com/package/lacuna-sdk) for verification helpers.

## Credits and pricing

- Cost depends on the selected model — confirm on the [pricing page](https://lacuna.fm/pricing).
- Failed generations refund automatically.
- If a request returns `402 insufficient_credits`, do not retry — tell the user to top up.

## Working with `audio_url`

`audio_url` points to the generated output on Lacuna's CDN and does not have a 24-hour expiry. Downloading a separate copy is optional and depends on the user's own storage or processing workflow.

## Constraints to respect

- Use the default 5-second polling interval unless the workflow has a specific reason to change it.
- Don't hardcode API keys; always read from `LACUNA_API_KEY`.
- Don't retry on `403 tier_insufficient` or `402 insufficient_credits` — these are user-action errors.
- On `503 model_unavailable`, the requested model is temporarily circuit-broken (`error.model` names which one). Switch to a different model and retry; do not loop on the same one. The SDK does **not** auto-fallback because each model has a different credit cost.

## Resources

- API and pricing: <https://lacuna.fm>
- Get an API key: <https://lacuna.fm/profile/api>
- SDK: [`lacuna-sdk`](https://www.npmjs.com/package/lacuna-sdk)
- CLI: [`lacuna-toolkit`](https://www.npmjs.com/package/lacuna-toolkit)
- MCP server: [`lacuna-mcp`](https://www.npmjs.com/package/lacuna-mcp)
- Source code: <https://github.com/JOYLINK-LTD/lacuna-toolkit>

