---
title: "Read voice-over lines in their voice and link each to its line (costs credits)"
method: POST
path: "/v1/storyboards/{id}/voice-over"
tags: ["Storyboards"]
---

# Read voice-over lines in their voice and link each to its line (costs credits)

`POST /v1/storyboards/{id}/voice-over`

## Request

- Base URL: `https://nanostudiopro.com/api`
- URL: `https://nanostudiopro.com/api/v1/storyboards/{id}/voice-over`
- Auth: HTTP bearer

The film's narration (`doc.voiceOver`): lines on the timeline, each anchored to a moment (`fromPanelId`, `offsetS` seconds after it starts, never before: a title read goes on the first moment at 0 and plays as the film starts), on every path that reaches it or only on `path` (decision id -> option id). Each line named in `line_ids`, else every line with words and no sound yet, is read by ElevenLabs text-to-speech (`eleven_multilingual_v2`) in its voice (`voiceId`, else the narrator's, else the default storyteller), brought to -20 LUFS, added to the film's sounds (kind `voice`) and linked to its line. The player plays a line at its `level` (0-2) times the `voiceOver` layer (`mix`), and the music, ambience and effects dip under it. Free by hand: `{op: "addVoiceOverLine", line: {id, fromPanelId, text, offsetS?, voiceId?, path?, level?, soundId?}}` (a `soundId` of your own upload plays as it is), `updateVoiceOverLine {id, patch}` (new words or another voice unlink a read sound), `removeVoiceOverLine {id}`, `setNarrator {voiceId | null}`. **Costs credits**: `audio_voice_100char` per 100 characters (see `/v1/pricing`). Quote first with `dry_run: true`: `credits`, each line's `characters`, `credits` and voice, and `voices`, the voice ids on offer. Without `dry_run` it answers `202 { task_id }`: poll `GET /v1/tasks/{id}`. Each line is charged once its sound is on the storyboard, through your token so its daily cap applies. Paid tier only (`402 audio_requires_purchase` on trial credits). A storyboard whose owner approves agent spending answers `403` except for the quote.

## Request body

- object
  - `line_ids` string[]
  - `dry_run` boolean

## Response `200`

The quote (dry_run)

- object
  - `dry_run` boolean
  - `available` boolean
  - `reason` string
  - `credits` integer
  - `per_100_chars` integer
  - `model` string
  - `lines` object[]
    - `line_id` string
    - `characters` integer
    - `credits` integer
    - `voice_id` string
    - `voice_name` string, nullable
  - `narrator_voice_id` string
  - `voices` object[]
    - `voice_id` string
    - `name` string
    - `description` string
    - `storyteller` boolean
  - `running_task_id` string, nullable

## Other responses

- `202`: The run started
- `400`: Write the line's words first
- `401`: Missing or invalid token
- `402`: Insufficient credits
- `403`: The owner approves an agent's spending on this storyboard
- `409`: Voice-over is being read for this film already
- `429`: Rate limit exceeded

---

[API](https://skmtc.dev/nanostudiopro/apis/nano-studio-pro-api.md) · [All operations](https://skmtc.dev/nanostudiopro/apis/nano-studio-pro-api/llms.txt) · [OpenAPI document](https://skmtc.dev/nanostudiopro/apis/nano-studio-pro-api/revisions/b454211e598e?raw)
