---
title: Music video endpoints
description: Every /v1/videos/music endpoint — create a music video from a song, poll it, review and edit its scenes, render, download the video, one scene or a look's reference sheet, delete — with parameters, responses and errors.
layout: reference
---

The music videos API lives under `https://lovelyrouter.com/v1/videos/music`. Authenticate every request with your API key as `Authorization: Bearer $LOVELY_API_KEY` ([Authentication](/docs/authentication)). Requests are JSON or multipart; responses are JSON, except the video files. Videos are billed from your prepaid balance.

For a walkthrough — how a song becomes a video, writing the brief, timing and pricing — start with the [music videos guide](/docs/models/music-videos).

## The music video object

Every endpoint returns this object, or a list of them.

:::params Attributes
- `id` string
  Unique id, `mv_` followed by 24 characters.
- `object` string
  Always `music_video`.
- `model` string
  The model it renders with: `ltx-music-video` or `minimax-h3-music-video`.
- `status` string
  `analyzing`, `planning`, `ready` (waiting for [Render](#render-a-music-video), with `auto_render: false`), `in_progress`, `completed`, `failed` or `cancelled`.
- `step` string
  The step in more detail: `analyzing`, `planning`, `ready`, `rendering`, `stitching` (joining the scenes), `succeeded`, `failed` or `cancelled`.
- `planning_step` string | null
  While `planning`: `casting` (the director defines the singer and the looks), `profile` (the looks' reference sheets are drawn), `directing` (the shots are written) or `frames` (each scene's first frame is drawn). `null` in any other status.
- `progress` integer
  0–100: analysis to 5, writing the scenes to 15, rendering to 95, joining to 100.
- `created_at` integer
  Unix seconds.
- `completed_at` integer | null
  When it finished, once `completed`.
- `expires_at` integer | null
  When the files are deleted, 7 days after `completed_at`.
- `seconds` number | null
  The song's length (or the slice's) once it is analysed; the video's length once it is finished.
- `start` number | null
  Where the [slice](/docs/models/music-videos#slice) begins in the song, in seconds. `null` when the whole song is used.
- `end` number | null
  Where the slice ends, in seconds — after analysis, as applied (an `end` past the song's end becomes its end). `null` when the whole song is used.
- `size` string
  The aspect you asked for (`16:9`, `9:16`, `1:1`), then the video's pixels once it is finished, such as `1472x832`.
- `mode` string
  `image` (with the [character profile](/docs/models/music-videos#character-profile)) or `text`.
- `brief` object
  `story`, `style`, `character` and `locations` as you sent them (`null` when not sent). With `character_id` and no `character`, `character` is the saved character's description.
- `character_id` string | null
  The [saved character](/docs/api/characters) the video was made with, or `null`.
- `director_model` string
  The chat model that writes the scenes.
- `image_model` string | null
  The image model that draws the reference sheets and the first frames, in `mode: "image"`.
- `first_frames` boolean
  Whether `minimax-h3-music-video` also drew a first frame per scene (`first_frames` in the request).
- `tempo` number | null
  The song's tempo in beats per minute, after analysis.
- `language` string | null
  The language the lyrics were transcribed in, after analysis.
- `character` string | null
  The singer as cast by the director: face, hair, body, no clothes. `null` with `mode: "text"` and until casting is done.
- `looks` array
  The singer's looks (outfits), at most four; empty with `mode: "text"` and until casting is done.
  - `id` integer
    The look's number, from `1` (the main look).
  - `outfit` string
    What the singer wears in it.
  - `url` string | null
    Its turnaround reference sheet (front, three-quarter, side, back) once it is drawn. See [Download a look's sheet](#download-a-looks-sheet).
- `scenes` array
  One entry per scene, in order; empty until the song is analysed.
  - `index` integer
    0-based position.
  - `look` integer | null
    The look the singer wears in it (`looks[].id`). `null` with `mode: "text"` and until the director has written it.
  - `start` number
    Where the scene starts in the song, in seconds.
  - `seconds` number
    Its length.
  - `lyrics` string | null
    The words sung in it, as transcribed.
  - `prompt` string | null
    What it renders: the character and its look's outfit (with `mode: "text"`, your `character` text), the `style`, then the shot. `null` until the director has written it.
  - `status` string
    `pending`, `rendering`, `done` or `failed`.
  - `url` string | null
    The scene's own mp4 once it is `done`. See [Download a scene](#download-a-scene).
- `error` object | null
  For `failed` and `cancelled` videos: `code` and `message` (which says whether anything was charged).
- `url` string | null
  The finished mp4, once `completed`. See [Download the video](#download-the-video).
- `hold_usd` number
  The amount held from your balance: the whole song's price, from the end of the analysis until the video is settled. `0` before.
- `charge_usd` number | null
  What the video cost, once it is settled. `null` before.
- `other_usd` number
  What the director's chat calls and the reference sheets' and first frames' image calls have cost so far. They are billed as their own calls, not in `charge_usd`.
- `powered_by` string
  `LTX` or `MiniMax H3`.
:::

```json title="The music video object" aside
{
  "id": "mv_8c1f3a9e2b7d4c60a5e9f1b3",
  "object": "music_video",
  "model": "ltx-music-video",
  "status": "completed",
  "step": "succeeded",
  "planning_step": null,
  "progress": 100,
  "created_at": 1792915200,
  "completed_at": 1792915330,
  "expires_at": 1793520130,
  "seconds": 16.13,
  "start": null,
  "end": null,
  "size": "1472x832",
  "mode": "image",
  "brief": {
    "story": "A night drive through a neon city that ends at the sea at dawn",
    "style": "Cinematic 35 mm film, teal and orange, light rain",
    "character": "A woman in her twenties with short black hair and a red leather jacket",
    "locations": null
  },
  "character_id": null,
  "director_model": "gemini-3.8-flash",
  "image_model": "gpt-image-2",
  "first_frames": false,
  "tempo": 118,
  "language": "en",
  "character": "A woman in her twenties with short black hair in a blunt bob, dark brown eyes, light olive skin, a slim build and a small scar above her left eyebrow",
  "looks": [
    {
      "id": 1,
      "outfit": "A red leather jacket over a black T-shirt, dark jeans, black boots, silver hoop earrings",
      "url": "https://lovelyrouter.com/v1/videos/music/mv_8c1f3a9e2b7d4c60a5e9f1b3/looks/1/content"
    }
  ],
  "scenes": [
    {
      "index": 0,
      "look": 1,
      "start": 0,
      "seconds": 5.375,
      "lyrics": null,
      "prompt": "A woman in her twenties with short black hair in a blunt bob, dark brown eyes, light olive skin, a slim build and a small scar above her left eyebrow, wearing a red leather jacket over a black T-shirt, dark jeans, black boots, silver hoop earrings. Style: Cinematic 35 mm film, teal and orange, light rain. She drives through the neon city, signs smearing across the wet windshield; the camera pushes in from the passenger seat.",
      "status": "done",
      "url": "https://lovelyrouter.com/v1/videos/music/mv_8c1f3a9e2b7d4c60a5e9f1b3/scenes/0/content"
    },
    {
      "index": 1,
      "look": 1,
      "start": 5.375,
      "seconds": 5.042,
      "lyrics": "we never looked back",
      "prompt": "A woman in her twenties with short black hair in a blunt bob, … wearing a red leather jacket over a black T-shirt, … Style: Cinematic 35 mm film, teal and orange, light rain. Medium close-up: she sings \"we never looked back\" to the camera, hair whipping in the wind of the open window, handheld.",
      "status": "done",
      "url": "https://lovelyrouter.com/v1/videos/music/mv_8c1f3a9e2b7d4c60a5e9f1b3/scenes/1/content"
    },
    {
      "index": 2,
      "look": 1,
      "start": 10.417,
      "seconds": 5.708,
      "lyrics": null,
      "prompt": "A woman in her twenties with short black hair in a blunt bob, … wearing a red leather jacket over a black T-shirt, … Style: Cinematic 35 mm film, teal and orange, light rain. She steps out of the car on an empty beach road as the sky turns pink; the camera tracks alongside her toward the sea.",
      "status": "done",
      "url": "https://lovelyrouter.com/v1/videos/music/mv_8c1f3a9e2b7d4c60a5e9f1b3/scenes/2/content"
    }
  ],
  "error": null,
  "url": "https://lovelyrouter.com/v1/videos/music/mv_8c1f3a9e2b7d4c60a5e9f1b3/content",
  "hold_usd": 0,
  "charge_usd": 1.1288,
  "other_usd": 0.1621,
  "powered_by": "LTX"
}
```

## Create a music video

::endpoint POST /v1/videos/music

Starts a music video from a song and returns `202` with the music video object at once. The work runs in the background; follow it with [Retrieve a music video](#retrieve-a-music-video). Send multipart (`song` and `character_image` as files) or JSON (the same as `http(s)` or `data:` URLs). The singer can come inline with the call (`character`, `character_image`) or from a [saved character](/docs/api/characters) (`character_id`); both work, and neither is required. By default the singer is cast and drawn first ([character profile](/docs/models/music-videos#character-profile)). Starting needs a balance that covers the shortest video (15 seconds); the whole song's price is held once its length is known.

:::params Body parameters
- `model` string, required
  `ltx-music-video` or `minimax-h3-music-video` (see [Models](/docs/models/music-videos#models)).
- `song` file or string, required
  The song: MP3, WAV, FLAC, OGG or M4A, 5 seconds to 6 minutes, up to 60 MB. A file, or an `http(s)` / `data:` URL (also accepted as `song_url`).
- `start` number
  Use only part of the song: where the slice begins, in seconds (default `0`). Analysis, scenes, lyrics and the soundtrack all follow the slice, and you pay for its length. See [Use part of a song](/docs/models/music-videos#slice).
- `end` number
  Where the slice ends, in seconds (default: the song's end). `end` − `start` must be at least 5 seconds and at most 6 minutes.
- `consent` boolean, required
  `true`: you accept the model's terms — the [LTX terms](/docs/models/ltx-license#consent) or the [MiniMax H3 terms](/docs/models/minimax-h3-license#consent), its [territory clause](/docs/models/minimax-h3-license#territory) included — and the responsibility for following them, and confirm that anyone recognisable in your brief or picture is 18 or older and agreed to this use of their likeness.
- `story` string
  What happens over the song. Up to 4,000 characters, like the other brief fields.
- `style` string
  The look: film or animation, colours, light, lens. Added to every scene's prompt.
- `character` string
  The singer (face, hair, build, the main outfit). Casting keeps every detail and fills in the rest; with `mode: "text"` it is added to every scene's prompt as it is.
- `locations` string
  The places the story moves through.
- `lyrics` string
  The song's words as written, for the right spelling. Optional: the lyrics are transcribed anyway.
- `language` string
  The song's language for the transcription, such as `en`, `zh` or `ja`. Default: detected.
- `size` string, default `16:9`
  `16:9`, `9:16` or `1:1`: 1472×832, 832×1472 or 1152×1152 with `ltx-music-video`; 1344×768, 768×1344 or 1024×1024 with `minimax-h3-music-video`.
- `mode` string, default `image`
  `image`: the [character profile](/docs/models/music-videos#character-profile) — the director casts the singer and up to four looks, `image_model` draws a reference sheet per look, and every scene starts from a first frame drawn from its look's sheet (`minimax-h3-music-video`: every scene is filmed from the sheet itself; see `first_frames`). `text`: no pictures, every scene from its prompt alone; not with `minimax-h3-music-video`, which needs the profile.
- `image_model` string, default `gpt-image-2`
  `mode: "image"` only. An [image model](/docs/models/images) your key can use **that accepts reference images (edits)**, such as `gpt-image-2`; a generation-only model is refused with `400`. Billed as normal image jobs, typically one per look plus one per scene.
- `first_frames` boolean, default `false`
  `minimax-h3-music-video` only (other models always draw first frames in `mode: "image"`). This model films the singer from their look's sheet, with no first frame; `true` also draws each scene's opening frame and gives it to the model alongside the sheet: more control over where and how each scene opens, one more picture per scene, and slower renders. The video is then billed at $0.2 per second instead of $0.13.
- `character_image` file or string
  `mode: "image"` only. A picture of your singer, the source of look 1's sheet: PNG, JPEG or WebP up to 20 MB, as a file or a URL (also `character_image_url`).
- `character_id` string
  `mode: "image"` only. A [saved character](/docs/api/characters) of your account: its sheet is used as `character_image` would be, and its description as `character` when you don't send one. Not together with `character_image`.
- `director_model` string, default `gemini-3.8-flash`
  The chat model that writes the scenes. Any chat model your key can use; billed as normal chat calls.
- `scene_seconds` number, default `5`
  The scene length to aim for, 2–10 seconds. Cuts land on beats, so scenes vary around it.
- `auto_render` boolean, default `true`
  `false`: stop at status `ready` once the scenes are written and their first frames drawn, so you can [edit them](#edit-scenes) before you [render](#render-a-music-video).
- `seed` integer
  Repeatable scenes (0–2147483647). Default: random.
:::

:::params Query parameters
- `wait` integer
  0–300. Hold the request until the video changes (a step, a finished scene) or the time runs out; `200` if it has ended by then.
:::

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/videos/music \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -F model=ltx-music-video \
  -F song=@song.mp3 \
  -F story="A night drive through a neon city that ends at the sea at dawn" \
  -F style="Cinematic 35 mm film, teal and orange, light rain" \
  -F character="A woman in her twenties with short black hair and a red leather jacket" \
  -F start=42.5 -F end=102.5 \
  -F consent=true

# With a saved character instead of the inline character text and picture:
curl https://lovelyrouter.com/v1/videos/music \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -F model=ltx-music-video -F song=@song.mp3 \
  -F character_id=chr_3b9e1f0c7a2d4e58b6c1a9f2 \
  -F consent=true
```

```python title="Python" aside
import os, requests

video = requests.post(
    "https://lovelyrouter.com/v1/videos/music",
    headers={"Authorization": f"Bearer {os.environ['LOVELY_API_KEY']}"},
    data={
        "model": "ltx-music-video",
        "story": "A night drive through a neon city that ends at the sea at dawn",
        "style": "Cinematic 35 mm film, teal and orange, light rain",
        "character": "A woman in her twenties with short black hair and a red leather jacket",
        "consent": "true",
    },
    files={"song": open("song.mp3", "rb")},
).json()
```

```js title="Node.js" aside
const res = await fetch('https://lovelyrouter.com/v1/videos/music', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.LOVELY_API_KEY}`, 'content-type': 'application/json' },
  body: JSON.stringify({
    model: 'ltx-music-video',
    song_url: 'https://example.com/song.mp3',
    story: 'A night drive through a neon city that ends at the sea at dawn',
    style: 'Cinematic 35 mm film, teal and orange, light rain',
    character: 'A woman in her twenties with short black hair and a red leather jacket',
    consent: true,
  }),
});
const video = await res.json();
```

:::aside
```json title="Response"
{
  "id": "mv_8c1f3a9e2b7d4c60a5e9f1b3",
  "object": "music_video",
  "model": "ltx-music-video",
  "status": "analyzing",
  "step": "analyzing",
  "planning_step": null,
  "progress": 2,
  "seconds": null,
  "size": "16:9",
  "mode": "image",
  "character": null,
  "looks": [],
  "scenes": [],
  "error": null,
  "url": null,
  "hold_usd": 0,
  "charge_usd": null,
  "other_usd": 0,
  "powered_by": "LTX"
}
```
:::

## Retrieve a music video

::endpoint GET /v1/videos/music/{id}

Returns the music video object. Poll it until `status` is `completed`, `failed` or `cancelled`.

:::params Query parameters
- `wait` integer
  0–300. Long-poll: return as soon as the step changes or another scene finishes, or when the time runs out. A video at `ready` returns at once.
:::

```bash title="cURL" aside
curl "https://lovelyrouter.com/v1/videos/music/MUSIC_ID?wait=120" \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

```python title="Python" aside
video = requests.get(
    f"https://lovelyrouter.com/v1/videos/music/{video['id']}?wait=120",
    headers={"Authorization": f"Bearer {os.environ['LOVELY_API_KEY']}"},
).json()
```

```js title="Node.js" aside
const H = { Authorization: `Bearer ${process.env.LOVELY_API_KEY}` };
const video = await (await fetch(`https://lovelyrouter.com/v1/videos/music/${id}?wait=120`, { headers: H })).json();
```

## List music videos

::endpoint GET /v1/videos/music

Your music videos, newest first.

:::params Query parameters
- `limit` integer, default `20`
  1–100.
- `after` string
  A music video id: list the ones created before it (the next page).
:::

```bash title="cURL" aside
curl "https://lovelyrouter.com/v1/videos/music?limit=10" \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```json title="Response"
{
  "object": "list",
  "data": [{ "id": "mv_8c1f3a9e2b7d4c60a5e9f1b3", "object": "music_video", "status": "completed" }],
  "first_id": "mv_8c1f3a9e2b7d4c60a5e9f1b3",
  "last_id": "mv_8c1f3a9e2b7d4c60a5e9f1b3",
  "has_more": false
}
```
:::

## Edit scenes

::endpoint PATCH /v1/videos/music/{id}

Rewrites scene prompts while the video is `ready` (created with `auto_render: false`). Each prompt you send replaces the scene's `prompt` as it is — the character, its look and the `style` are not added again, so keep them in it. In `mode: "image"` the first frames are already drawn; a new prompt changes the scene's motion, not its first frame. Returns the music video object; `409` in any other status.

:::params Body parameters
- `scenes` array, required
  The scenes to change.
  - `index` integer, required
    The scene's 0-based `index`.
  - `prompt` string, required
    Its new prompt, 1–4,000 characters.
:::

```bash title="cURL" aside
curl -X PATCH https://lovelyrouter.com/v1/videos/music/MUSIC_ID \
  -H "Authorization: Bearer $LOVELY_API_KEY" \
  -H "content-type: application/json" \
  -d '{"scenes": [{"index": 2, "prompt": "A woman in her twenties with short black hair and a red leather jacket. She sings \"we never looked back\" on an empty pier at dawn, the camera orbiting her."}]}'
```

## Render a music video

::endpoint POST /v1/videos/music/{id}/render

Starts rendering a video that is `ready`. Returns the music video object, now `in_progress`; `409` in any other status. A plan that isn't rendered within a day is cancelled without charge.

```bash title="cURL" aside
curl -X POST https://lovelyrouter.com/v1/videos/music/MUSIC_ID/render \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

## Download the video

::endpoint GET /v1/videos/music/{id}/content

The finished mp4, with the song as its soundtrack. `Range` requests work, so players can seek. Kept for 7 days; `409` before it is `completed`, `410` once deleted or expired.

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/videos/music/MUSIC_ID/content \
  -H "Authorization: Bearer $LOVELY_API_KEY" -o music-video.mp4
```

## Download a scene

::endpoint GET /v1/videos/music/{id}/scenes/{n}/content

One rendered scene's mp4, with its part of the song, as soon as that scene is `done` — before the whole video is finished. `n` is the scene's `index`. `409` while it isn't rendered.

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/videos/music/MUSIC_ID/scenes/0/content \
  -H "Authorization: Bearer $LOVELY_API_KEY" -o scene-0.mp4
```

## Download a look's sheet {#download-a-looks-sheet}

::endpoint GET /v1/videos/music/{id}/looks/{n}/content

A look's turnaround reference sheet (front, three-quarter, side, back), as soon as it is drawn — during planning, before any scene is rendered. `n` is the look's `id`, from `1`. `404` while it isn't drawn, and with `mode: "text"`.

```bash title="cURL" aside
curl https://lovelyrouter.com/v1/videos/music/MUSIC_ID/looks/1/content \
  -H "Authorization: Bearer $LOVELY_API_KEY" -o look-1.png
```

## Delete

::endpoint DELETE /v1/videos/music/{id}

On a finished video (`completed`, `failed` or `cancelled`): deletes its files and removes it from your list. On a plan waiting at `ready` (`auto_render: false`): discards it; nothing is charged. A video that has started analysing or rendering can't be stopped: `409`. Let it finish, then delete it.

```bash title="cURL" aside
curl -X DELETE https://lovelyrouter.com/v1/videos/music/MUSIC_ID \
  -H "Authorization: Bearer $LOVELY_API_KEY"
```

:::aside
```json title="Response (deleted)"
{ "id": "mv_8c1f3a9e2b7d4c60a5e9f1b3", "object": "music_video.deleted", "deleted": true }
```
:::

## Errors

Errors use the [standard error body](/docs/api/errors). A video that fails after it started is not an HTTP error: its `status` is `failed` and `error` says why.

| Status | `type` / `code` | Why |
| --- | --- | --- |
| `400` | `consent_required` | `consent` wasn't `true`. |
| `400` | `invalid_request_error` | A missing or unreadable song or picture, a file that's too large, a field out of range, a slice shorter than 5 seconds or longer than 6 minutes, `character_image`, `character_id` or `first_frames` without `mode: "image"`, `mode: "text"` with `minimax-h3-music-video`, both `character_image` and `character_id`, or a `director_model` / `image_model` your key can't use. |
| `401` | `authentication_error` | A missing or invalid API key. |
| `402` | `insufficient_quota` | The balance, or the key's spend limit, doesn't cover the shortest video. |
| `404` | `not_found_error` | Unknown model or music video, a model that doesn't make music videos, a `character_id` that isn't one of your saved characters, or a look whose sheet isn't drawn yet. |
| `409` | `invalid_request_error` | Editing or rendering a video that isn't `ready`, or downloading a file that isn't rendered yet. |
| `410` | `not_found_error` | The video was deleted or has expired. |
| `429` | `rate_limit_exceeded` | Two music videos are already in progress on your account, or your key's rate limit. |

Failed videos carry one of these `error.code` values: `invalid_request_error` (the song couldn't be used, such as shorter than 5 seconds or longer than 6 minutes, or a `start` past its end), `insufficient_quota` (the balance didn't cover the whole song), `capacity_exhausted` (no GPU became available), `api_error` (something failed on our side), `expired` (a `ready` plan waited a day) and `cancelled` (a plan discarded at `ready`). Nothing is charged for the video in any of them.
