---
title: Voiceover - Narrate a Video Post
description: Have the AI write and record a voiceover for a video post made through the PostNitro Embed API, in one of the editor's voices and a tone you pick, with captions. Charged in AI tokens, not API credits.
canonical: https://postnitro.ai/docs/embed/api/initiate/voiceover
---

# Voiceover

A video post (`postType: "VIDEO"`) can be narrated, as a video is with the editor's [voiceover](https://postnitro.ai/docs/how-to/make-videos/add-a-voiceover). Ask for one with `videoSettings.voiceover` on [Generate](https://postnitro.ai/docs/embed/api/initiate/generate#video-posts), [Import](https://postnitro.ai/docs/embed/api/initiate/import#video-posts) or [Edit](https://postnitro.ai/docs/embed/api/edit). You pick the voice and the tone, and the AI does the rest:

1. It writes one spoken line per slide, from the slides, in your tone.
2. It records each line in your voice.
3. Each slide lasts as long as its narration needs, and the narration's words show as captions.

You don't send a script or a recording: the AI writes and records it. A `script` field is refused.

## Example

An imported video in the Bold style, with music under it at a quarter of its volume, narrated in the Nova voice:

```bash
curl --location 'https://embed-api.postnitro.ai/post/initiate/import' \
--header 'Content-Type: application/json' \
--header 'embed-api-key: pn-your-api-key' \
--data '{
    "postType": "VIDEO",
    "templateId": "your-template-id",
    "brandId": "your-brand-id",
    "responseType": "MP4",
    "slides": [
        {
            "type": "starting_slide",
            "sub_heading": "Remote work",
            "heading": "3 habits of fast teams",
            "description": "The ones that actually stick."
        },
        {
            "type": "body_slide",
            "heading": "Write things down",
            "description": "Decisions live in docs, not in calls."
        },
        {
            "type": "ending_slide",
            "heading": "Try one this week",
            "cta_button": "Follow for more"
        }
    ],
    "videoSettings": {
        "motionStyle": "bold",
        "backgroundMusicId": "your-audio-id",
        "backgroundMusicVolume": 25,
        "voiceover": { "voice": "nova", "tone": "Hype and punchy" }
    }
}'
```

`{}` gives a voiceover with every default: the Coral voice, an upbeat tone, and captions.

```json
"videoSettings": {
    "voiceover": {}
}
```

## Fields

All of them are optional.

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `voice` | string | `"coral"` | Who reads it: one of the [voices](#voices) |
| `tone` | string | `"upbeat"` | How it's written and read: `"upbeat"`, `"calm"`, `"professional"`, `"storyteller"`, or your own words, up to 80 characters, such as `"Hype and punchy"` |
| `captions` | boolean | `true` | `true` shows the narration's words as captions on the video; `false` hides them |

Any other field returns a `422`. On an edit, `"voiceover": false` takes the video's narration off: see [Editing a narrated video](#editing-a-narrated-video).

### Voices

The voices are OpenAI's, the same as the editor's:

`alloy`, `ash`, `ballad`, `coral`, `echo`, `fable`, `nova`, `onyx`, `sage`, `shimmer`, `verse`

To hear them first, play them in the editor: see [Pick a voice and a tone](https://postnitro.ai/docs/how-to/make-videos/add-a-voiceover#pick-a-voice-and-a-tone).

### Tones

The tone sets both how the AI writes the script and how the voice reads it. `upbeat`, `calm`, `professional` and `storyteller` are the editor's. Anything else is your own words, up to 80 characters, such as `"Hype and punchy"` or `"breathless, like a sports commentator"`.

## Length

Each slide lasts as long as its narration needs, and the video runs as long as all its slides take ([Length](https://postnitro.ai/docs/embed/api/initiate/video-motion#length)). The AI keeps the whole narration within the video's 2:00 limit. [Post Output](https://postnitro.ai/docs/embed/api/post-output)'s `duration` includes it.

## With music

Music from [`backgroundMusicId`](https://postnitro.ai/docs/embed/api/initiate/import#background-music) plays under the voiceover. It's lowered to 30% while the narration plays, so the voice is easy to hear.

## What it costs

A voiceover is charged as the editor's is, from your plan's **AI tokens**, not from your API credits, as AI images bill against their own quota:

- The script costs the tokens the AI used to write it.
- The recording costs 5,000 tokens per minute of audio.

For example, a 5-slide video with 27.1 seconds of narration took 2,866 AI tokens. The [`VOICEOVER` step](#the-voiceover-step) says what each one took.

The post's API [credits](https://postnitro.ai/docs/embed/api/subscription) don't change: 1 per slide for import, 2 per slide for AI generation, with a voiceover or without.

## When it can't be made

A voiceover is best-effort, as [AI images](https://postnitro.ai/docs/embed/api/initiate/import#ai-image-generation) are. If the plan's AI tokens are used up, or the narration fails, the video is still made, without narration, and the post completes. The `VOICEOVER` step says why. A slide that couldn't be recorded has no narration, and the step says how many.

## The `VOICEOVER` step

A video that asks for a voiceover has a `VOICEOVER` step in [Check Post Status](https://postnitro.ai/docs/embed/api/request-status)'s `logs`. It comes after `SAVE_JSON` (and `GENERATE_IMAGES`, when the request asks for images) and before `COMPOSE_MOTION`.

| Status | Message |
|--------|---------|
| `COMPLETED` | `Narrated 5 slides in nova, Hype and punchy: 27.1s, 2,866 AI tokens` |
| `COMPLETED` | The same, ending `; 1 slide couldn't be recorded` when some slides got no narration |
| `FAILED` | `Skipped the voiceover: the plan's AI tokens are used up` |
| `FAILED` | `Skipped the voiceover: voiceovers are part of paid plans` |
| `FAILED` | `Skipped the voiceover: <reason>`, for any other failure |

A `FAILED` step doesn't fail the post.

## Editing a narrated video

An [edit](https://postnitro.ai/docs/embed/api/edit) of a narrated video narrates its new slides again, in its own voice and tone, and is charged again in AI tokens.

- Send no `voiceover` and it keeps its voice, tone and captions.
- Send some fields, such as only `tone`, and just those change.
- Send `"voiceover": false` and the narration is taken off.

An edit can also narrate a video that has no voiceover yet: send `voiceover`, or `{}` for the defaults.

```json
{
    "embedPostId": "post123",
    "responseType": "MP4",
    "slides": [
        {
            "type": "starting_slide",
            "heading": "Two habits, done well"
        },
        {
            "type": "body_slide",
            "heading": "Write it down",
            "description": "In one place everyone reads."
        },
        {
            "type": "ending_slide",
            "heading": "Start today",
            "cta_button": "Follow for more"
        }
    ],
    "videoSettings": {
        "voiceover": { "tone": "calm" }
    }
}
```

## Reading it back

[Read a Post](https://postnitro.ai/docs/embed/api/read-design) gives a narrated video's voice, tone and captions in `videoSettings.voiceover`. It doesn't give the script.

```json
"videoSettings": {
    "motionStyle": "bold",
    "backgroundMusicId": "cmqi7jyir0003v0m91hxnyyaq",
    "backgroundMusicVolume": 25,
    "voiceover": { "voice": "nova", "tone": "Hype and punchy", "captions": true }
}
```

## Errors

Refused with a `422` when you make the request, before anything is made or charged:

| Message | When |
|---------|------|
| `videoSettings.voiceover must be an object: { voice, tone, captions }, all optional, or false for none` | `voiceover` is a string, a list, `true` or `null` |
| `videoSettings.voiceover has invalid field 'script'. Allowed fields are: voice, tone, captions. The AI writes the script` | A `script`, or any other field it doesn't take |
| `videoSettings.voiceover.voice must be one of: alloy, ash, ballad, coral, echo, fable, nova, onyx, sage, shimmer, verse` | Not one of the [voices](#voices) |
| `videoSettings.voiceover.tone must be upbeat, calm, professional, storyteller, or your own words, up to 80 characters` | An empty tone, not a string, or longer than 80 characters |
| `videoSettings.voiceover.captions must be true or false` | `captions` isn't a boolean |

`videoSettings` on a `CAROUSEL` or `IMAGE` post returns a `422` too.
