> For the complete documentation index, see [llms.txt](https://bountyv.gitbook.io/vdocs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bountyv.gitbook.io/vdocs/youtube-tools-api/lyric-video.md).

# Lyric Video

Render an animated lyric video from a song + its lyrics. Asynchronous — returns a generation ID that you poll.

```
POST /api/v1/youtube/lyric-video
```

Supply a public `audio_url` and the full `lyrics`; we align the words to the audio and render a karaoke-wipe lyric video. The call returns a `generation_id`; poll `GET /api/v1/youtube/lyric-video/{id}` until `status` is `completed`.

## Authentication

```
Authorization: Bearer sk_live_...
```

Requires the `youtube` product entitlement. See [Authentication](/vdocs/authentication.md) for details.

## Billed per minute of song

Lyric Video is charged **per minute of the song, rounded up**, at the live rate. Because the song length isn't known until the render finishes, the charge is applied on the poll that observes completion (returned as `charged_cents` with `minutes_billed`). A failed render is never charged. Submitting requires at least one minute's balance up front.

## Request

**Content-Type:** `application/json` (or `multipart/form-data` to upload the song directly instead of hosting it yourself).

| Field        | Type   | Required           | Default      | Description                                                                                                                                     |
| ------------ | ------ | ------------------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `audio_url`  | string | ✅ (JSON lane)      | —            | Public https URL to the song (mp3/wav/m4a).                                                                                                     |
| `audio`      | file   | ✅ (multipart lane) | —            | The song file itself, uploaded directly instead of `audio_url`.                                                                                 |
| `lyrics`     | string | ✅                  | —            | The full plain-text lyrics.                                                                                                                     |
| `style`      | string | —                  | `ember`      | Preset id — e.g. `ember`, `neon`, `sunset`, `elegant`.                                                                                          |
| `aspect`     | string | —                  | `16:9`       | `16:9` \| `9:16` \| `1:1`.                                                                                                                      |
| `background` | string | —                  | `procedural` | `procedural` (motion graphics) \| `ai` (AI scene per section) \| `ai_motion` (AI video clip per section).                                       |
| `scenes`     | object | —                  | —            | AI backgrounds only — your own scene description per lyric section: `{"<section tag>": "<prompt>"}`. Ignored when `background` is `procedural`. |

### `scenes` — writing your own backdrops

With `background: "ai"` we generate one image per `[Section]` of your lyrics. By default the scene is inferred from that section's mood; pass `scenes` to describe it yourself instead.

Key each prompt by the section tag as it appears in the lyrics (a tag's descriptive half — anything after a `-` — is ignored, so `[Verse 1 - The Lie]` matches `Verse 1`, and matching is case/punctuation-insensitive). Use the key `all` to set one backdrop for the whole song, which is also the fallback for any section you don't name. Sections with no prompt keep the automatic mood-matched scene, so a partial map is fine. Repeated sections share a single scene. Max 40 entries, 800 characters each.

```json
{
  "background": "ai",
  "scenes": {
    "Verse 1": "foggy mountain ridge at dawn, cinematic, cold light",
    "Chorus": "rain-slicked neon city street at night, deep reflections"
  }
}
```

On the `multipart/form-data` lane, send the same object as a JSON-encoded `scenes` field.

## Response — submit

```json
{
  "generation_id": "gen_V1StGXR8_Z5",
  "status": "pending",
  "billing": "per_minute",
  "rate_per_min_cents": 100,
  "poll_url": "/api/v1/youtube/lyric-video/gen_V1StGXR8_Z5"
}
```

HTTP status: `202 Accepted`.

Poll `GET /api/v1/youtube/lyric-video/{id}` every 5–10 seconds until the job completes or fails. See [Job status](/vdocs/video-api/jobs.md) for general polling guidance.

## Response — poll

```json
{
  "status": "completed",
  "video": "https://cdn.substance-api.com/...mp4",
  "generation_id": "gen_V1StGXR8_Z5",
  "duration_s": 168.4,
  "minutes_billed": 3,
  "charged_cents": 300
}
```

| Field            | Type    | Description                                             |
| ---------------- | ------- | ------------------------------------------------------- |
| `status`         | string  | One of `pending`, `completed`, `failed`.                |
| `video`          | string  | Signed URL to the finished MP4 (only when `completed`). |
| `generation_id`  | string  | The generation identifier.                              |
| `duration_s`     | number  | Song duration in seconds (only when `completed`).       |
| `minutes_billed` | integer | `ceil(duration_s / 60)` — what you were charged for.    |
| `charged_cents`  | integer | Amount deducted from your balance for this render.      |
| `error`          | string  | Present when `status` is `failed`. Not charged.         |

While rendering: `{ "status": "pending" }`. On failure: `{ "status": "failed", "error": "..." }` (not charged).

## Example

```bash
# 1. Submit
curl -X POST https://substance-api.com/api/v1/youtube/lyric-video \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "audio_url": "https://example.com/song.mp3",
    "lyrics": "First line of the song\nSecond line...",
    "style": "ember",
    "aspect": "9:16",
    "background": "procedural"
  }'

# 2. Poll until completed
curl https://substance-api.com/api/v1/youtube/lyric-video/GEN_ID \
  -H "Authorization: Bearer sk_live_..."
```

## Errors

| Status | When                                                |
| ------ | --------------------------------------------------- |
| `400`  | Missing/invalid `audio_url`/`audio` or `lyrics`.    |
| `401`  | Missing/invalid API key.                            |
| `402`  | Balance below one minute's rate.                    |
| `403`  | Account doesn't have the `youtube` product enabled. |
| `502`  | The render couldn't be started.                     |
| `503`  | Pipeline temporarily offline.                       |

Jobs that fail mid-pipeline return `status: "failed"` when polled and are never charged. See [Errors](/vdocs/errors.md) for full details.

## Pricing

Billed per call — see [Credits & pricing](/vdocs/account/credits.md) for current rates.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://bountyv.gitbook.io/vdocs/youtube-tools-api/lyric-video.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
