Skip to main content

Overview

By default every line of a book is read by one narrator: the project’s selected_voice_id. Character voices give the characters their own voices. Every paragraph carries a character_name (empty means the narrator). The book keeps a cast: one voice per character. When character voices are on, each character’s lines are narrated with that character’s cast voice.
Every endpoint on this page accepts an API key (X-API-Key) as well as a session token, and works only on projects you own. All paths are relative to https://api.audiopod.ai/api/v1.

Turning character voices on

Character voices are off by default. While they are off, the book narrates exactly as it always has: one narrator, plus any voice you set on a chapter or a single paragraph. Turn them on with multi_voice: true in any of these places: Turning character voices on by any of these routes (or sending multi_voice: true again) does two things at no cost:
  • every speaker in the book gets a row in the cast;
  • each character without a cast voice gets a library voice automatically, when a suitable one is available. When cast/assign or auto-cast is what turns them on, the character you chose keeps its voice and every other character without a cast voice gets a library voice on the same terms. The pick prefers voices whose own language is the book’s language, then voices that support it, then, when the character has a gender hint, a voice of that gender. It never takes the narrator’s voice or a voice another character already has. If no suitable library voice is left for the book’s language, the character stays without a cast voice and its lines read in the narrator voice; give it one with cast/assign. The pick is stable, so the same book is cast the same way every time. Change any pick with cast/assign.
Send multi_voice: false to turn them off. Lines then follow the voice order for character voices off: a line voiced only through the cast goes back to the chapter’s voice or the narrator, while a voice saved on the line stays with it (one you set, a batch voice_id it was narrated with, or the narrator voice narration saved on it). Locked lines keep the voice they had. The cast is kept for when you turn character voices back on. The project response carries multi_voice (true or false).

Finding the speakers

Character voices need to know who speaks each line. Speaker detection fills character_name, free. There are two passes: an instant pass over the text, described here, and our speaker detection pass, which reads each line in context and runs by itself when character voices are on. Detection reads a chapter in order and attributes a paragraph to a named character when it is clearly that character’s speech:
  • dialogue in «…», “…”, "…", or opened with an em dash (— Buon giorno, — disse Geppetto.);
  • a reporting verb next to a name, in English or Italian (said Eleanor, Eleanor said, disse Geppetto, gridò mastr'Antonio), titles included (maestro Ciliegia, Captain Reyes);
  • a narration paragraph ending in : that names who speaks next;
  • the turn-taking of a two-person exchange: once both speakers are known, untagged lines that follow alternate between them. Anything ambiguous (a third speaker, “he said”, a line addressing the person it would be given to) stops the alternation.
The instant pass is built for precision, so when in doubt a line stays with the narrator. A paragraph is voiced whole, so it is given to a character only when at least 60% of its words are speech; a long narration with a short quote stays with the narrator. Detection only fills paragraphs that have no character_name and are not locked. It never replaces a name you set, and a paragraph whose speaker you cleared stays with the narrator. You can also run either pass later with POST .../cast/detect, or use screenplay annotation to split mixed paragraphs into separate lines.

Voice order

When a paragraph is narrated, its voice is the first of these that applies. Character voices off (the default):
  1. a voice sent for that one paragraph (POST .../paragraphs/{id}/narrate with voice_id), or a voice_id sent on narration/batch;
  2. the paragraph’s own voice_id;
  3. the chapter’s voice_id;
  4. the project’s selected_voice_id (the narrator).
Character voices on (multi_voice: true):
  1. a voice sent for that one paragraph (POST .../paragraphs/{id}/narrate with a different voice_id);
  2. a voice you set on the paragraph (PUT .../paragraphs/{id} with voice_id);
  3. the character’s cast voice;
  4. a voice_id sent on narration/batch (a narrator for that run);
  5. the chapter’s voice_id;
  6. the project’s selected_voice_id (the narrator).
With character voices on, a batch voice_id and a chapter voice_id replace the narrator only: character lines keep their cast voice, and a line you gave its own voice keeps it. A voice you set for a single line is yours and outranks the cast: a PUT with voice_id, or narrating one paragraph with a different voice_id. Send {"voice_id": null} in a PUT to release it. Narration saves the voice it used on each line it narrates, except a line voiced through the cast. A batch voice_id is a narrator override, and it is saved this way on the narrator lines it voices. With character voices off, a batch voice_id voices every selected line and replaces the voice saved on it, including one you set with a PUT. While character voices are off, a saved voice comes before the chapter and narrator voices, so changing the narrator does not reach lines that are already narrated. With character voices on, a saved voice you did not set is ignored, so a narrator or cast change reaches every line that is narrated again; it applies again if you turn character voices off. Casting itself costs nothing, except auto-cast, which designs new voices. You pay when you narrate, at the normal narration rate, whichever voice reads the line.
Cast before you narrate. A new voice only takes effect on a line’s next narration, so a voice change on a narrated book means narrating those lines again, at the normal rate.

Quick start

For a file, send "multi_voice": true with file_key and filename on POST .../manuscript/parse.

1. Find who speaks each line

Besides detection when you add the manuscript, two calls fill character_name. Neither touches locked paragraphs.

Speaker detection pass with character voices on

When a book has character voices on, adding a manuscript (parse, paste, generate, or a parse after an upload that sent multi_voice) runs a second, free pass after the text is parsed: our speaker detection reads each dialogue paragraph in context. It:
  • gives a paragraph a speaker only when it is confident, and leaves narration and unnamed speakers with the narrator;
  • fills only paragraphs that have no character_name, are not locked, and whose speaker you did not clear;
  • merges alternate names of the same character as aliases (an alias you set is never changed);
  • records whether each character is male or female when the text shows it. The hint is used only for a character that has no cast voice when the pass finishes: it does not change a library voice already picked (for example when the manuscript was parsed). To change such a voice, use cast/assign.
Narration of a passage that has not started waits until this pass finishes, so lines are not narrated before their speaker is known. When you asked for auto_direction, direction is written after the pass. The pass covers books up to the same word limit as annotation.

Detect speakers on an existing book

POST /audiobook/projects/{project_id}/cast/detect
string
default:"heuristic"
heuristic: the instant pass, answered at once. llm: our speaker detection pass, run as a background job; the response carries job_id.
Either method is free and only fills paragraphs that have no character_name and are not locked, so it never overwrites a name you set. With character voices on, each newly found character gets a library voice when the catalog has a suitable one left (one that suits the book’s language and is not the narrator’s or another character’s); a character left without one reads in the narrator voice until you give it one with cast/assign.
integer
Paragraphs given a speaker by this call. Always 0 for method=llm; read the count from the job status.
array
The cast after detection, same shape as GET /cast. For method=llm, the cast as it is when the job starts.
string
heuristic or llm.
string | null
method=llm only: the detection job.
Poll GET /audiobook/projects/{project_id}/cast/detect/{job_id}/status until status is COMPLETED or FAILED, or subscribe a webhook endpoint to the audiobook.cast.detected event:
string
PENDING · PROCESSING · COMPLETED · FAILED. A failed job keeps the speakers it had already written.
integer
Paragraphs given a speaker.
string[]
Speakers written to at least one paragraph.
integer
Names merged onto another name of the same character.
integer
Characters given a free library voice (character voices on only).
integer
Dialogue paragraphs the pass looked at.
method=llm returns 409 while another detection job runs for the book, 400 when the book is over the annotation word limit, and 503 when speaker detection is unavailable right now.

Split mixed paragraphs: screenplay annotation

POST /audiobook/projects/{project_id}/screenplay/annotate Our narration engine reads each paragraph and splits it into lines: narration, and dialogue attributed to the named character who speaks it. A paragraph like ”— Ohi! tu m’hai fatto male! — gridò la vocina.” becomes a dialogue line for the speaker plus a narration line. Each line also gets a short performance direction. It works in any language and with any dialogue punctuation: quotation marks, guillemets, or the em dashes common in Italian, French and Spanish fiction.
string[]
Annotate only these chapters (UUIDs). Omit, or send {}, to annotate the whole book. An empty list returns 400 empty_selection.
string
The annotation job. Poll its status.
integer
Chapters in scope.
Annotation is free, on every plan. Poll GET /audiobook/projects/{project_id}/screenplay/annotate/{job_id}/status until status is COMPLETED or FAILED:
string
PENDING · PROCESSING · COMPLETED · FAILED.
number
0–100.
integer
Chapters processed.
integer
Paragraphs that were split into more than one line.
string[]
Named speakers found.
integer
Paragraphs sent for annotation that were left unchanged: the engine could not annotate them, the result failed the word-count check below, or the paragraph changed (was edited, locked or queued for narration) while the job ran. Locked paragraphs, paragraphs being narrated or queued, and paragraphs marked skip_narration are never sent for annotation, so they are not counted here; they are simply left as they are.
What annotation changes, and what it leaves alone:
  • A word-count check guards the text. A result is only applied when the lines together have within ±5% of the original paragraph’s word count. Otherwise the paragraph is left alone and counted in skipped_paragraphs. The check counts words; it does not compare them one by one, so read the changed paragraphs before you narrate.
  • Paragraph ids change where a paragraph is split. The first line keeps the original paragraph’s id and new lines are inserted after it. Re-read the chapter’s paragraphs after annotation; do not reuse ids you stored before it.
  • Any paragraph whose text changes loses its audio and goes back to pending: every split paragraph, and any paragraph whose text is rewritten even without a split. Annotate before narrating.
  • character_name is rewritten on every unlocked paragraph in scope, including names you set by hand. Lock a paragraph (PUT .../paragraphs/{id} with {"locked": true}) to protect it.
  • A direction you wrote yourself is kept; the engine’s direction fills only the others. See Auto Direction.
  • New characters get a library voice. With character voices on, each character annotation finds that has no cast voice gets a library voice (never a designed one) when the catalog has a suitable one left (one that suits the book’s language and is not the narrator’s or another character’s); a character left without one reads in the narrator voice until you give it one with cast/assign. With character voices off, no voices are picked.
409 means an annotation is already running for this project, a paragraph in scope is being narrated, or the book is exporting. 400 means no chapters matched chapter_ids, or the scope is too large for one pass; annotate a range of chapters instead.

2. Review the cast

GET /audiobook/projects/{project_id}/cast
array
One entry per speaker: the narrator bucket first, then characters by line count. Names are grouped case-insensitively. A merged alternate name keeps its own entry, with alias_of set, and its lines are not counted in the main speaker’s paragraph_count.
string | null
The speaker’s name as tagged on the paragraphs. null for the narrator.
boolean
true for the narrator bucket: every paragraph with no character_name.
integer
Lines this speaker has (paragraphs marked skip_narration are not counted).
integer | null
For the narrator, the project’s selected_voice_id. For a character with character voices on, the character’s cast voice (an alias shows its main speaker’s) when the cast has one. Otherwise, the voice most of the character’s lines carry, or null.
string | null
One of the speaker’s lines, for a quick check.
string | null
Set when this name is merged onto another speaker.
string[]
Names merged onto this speaker.
integer
Named speakers, not counting the narrator.

3. Choose a voice for a character

POST /audiobook/projects/{project_id}/cast/assign Pick voice ids from GET /audiobook/voices/available, including your own cloned voices. For a book that is not in English, filter by the full locale (?language=it-IT) to see the voices suited to it.
string
The character to voice (max 120 characters). Matching is case-insensitive and includes every alias of that name.
boolean
default:"false"
Set to true (or omit character_name) to change the narrator instead: the project’s selected_voice_id.
integer
required
The voice to use.
integer
The character’s unlocked lines (aliases included). Each takes the cast voice, except a line you gave its own voice.
boolean
true when the call changed the project’s narrator voice.
integer
How many of those lines already have audio. They keep it until you narrate them again.
How it works:
  • The voice is saved on the book’s cast, not copied onto lines. Lines attributed to the character later (by detection, annotation or a PUT) take the cast voice too. Choosing a character’s voice turns character voices on for the book. When this call is what turns them on, every other character without a cast voice gets a free library voice at the same time, as with multi_voice: true.
  • Locked lines keep the voice they have. So does a line you gave its own voice with PUT .../paragraphs/{id}.
  • Narrated lines keep their audio. The new voice is heard on the line’s next narration; see Re-narrate.
  • The narrator is the project’s selected_voice_id. Assigning it does not turn character voices on.

4. Merge alternate names

Books call the same person different things: mastr’Antonio and Maestro Ciliegia, Mr. Darcy and Darcy. An alias makes them one speaker with one voice. POST /audiobook/projects/{project_id}/cast/alias
string
required
The alternate name, as it appears in the cast (1–120 characters).
string | null
The main name to merge it onto. Send null or omit it to remove the alias again.
integer
Lines under the alternate name whose stored voice was set to the main speaker’s.
array
The cast after the change, same shape as GET /cast.
The paragraphs keep their original character_name; the merge is stored on the book. With character voices on, the alternate name’s lines take the main speaker’s cast voice, and locked lines keep the voice they had. 400 means the alias is not allowed, for example a name aliased onto itself.

One paragraph at a time

PUT /audiobook/projects/{project_id}/paragraphs/{paragraph_id} tags or voices a single line, for a correction the bulk calls do not cover:
string | null
Speaker for this paragraph (max 120 characters). Send null to give the line to the narrator. A speaker you set or clear is yours: detection never changes it.
integer | null
A voice for this line only. It outranks the cast and sticks to the line. Send null to release it, so the line follows the voice order again.
With character voices on, tagging a line with a character is enough: it takes that character’s cast voice. A locked paragraph refuses every change except unlocking it (409). Changing these fields does not delete the line’s audio; the new voice is heard on its next narration.

5. Narrate and re-narrate

Confirm rights once per book with POST .../rights-attestation (narration returns 400 RIGHTS_NOT_ATTESTED until you do), then narrate with POST .../narration/batch:
boolean
Turn character voices on or off for this run and later ones (saved on the book first). true also gives uncast characters a library voice.
integer
A narrator for this run. With character voices on it voices only the narrator’s lines; with them off it voices every selected line and replaces the voice saved on it. It is saved on the lines it voices, like any narration voice, but does not count as a voice you set for the line.
If a selected line resolves to no voice at all (no cast voice, no chapter voice, no narrator), the batch returns 400. To hear a new voice on lines that are already narrated, narrate just those paragraphs again with paragraph_ids and include_completed. Collect the ids of the character’s lines from the chapters’ paragraph lists. Paragraphs keep the name they were tagged with, so after an alias merge match the main name and every alias (from GET /cast), case-insensitively. A main name with no lines of its own has no member of its own; its aliases point to it through alias_of:
To narrate the whole book, omit paragraph_ids and chapter_ids. An empty list ([]) is refused with 400 and "code": "empty_selection", so a filter that matched nothing never narrates, or bills, the whole book. The same rule applies to chapter_ids on annotation and Auto Direction, and to character_names on auto-cast.
Re-narration is billed like any narration, so only the lines you send are charged. A single line can also be redone with POST .../paragraphs/{paragraph_id}/narrate, where the first regeneration of a paragraph is free.

Change voices on a narrated book

  • A new narrator, characters on. cast/assign with {"is_narrator": true, "voice_id": <new>}, then narrate again with include_completed: true. Every narrator line takes the new voice, except chapters with their own voice_id (clear it with PUT .../chapters/{chapter_id} and {"voice_id": null}) and lines you gave their own voice.
  • A new voice for one character. cast/assign the character, then narrate their lines again as shown above.
  • Characters off. Narration saved the voice it used on each line. To re-voice the whole book, send the voice in the batch: {"voice_id": <new>, "include_completed": true}. It voices every selected line and is saved on each, replacing the voice the line had, including one you set with a PUT.

Auto-cast: design a voice per character

POST /audiobook/projects/{project_id}/cast/auto Instead of library voices, auto-cast designs a new custom voice for each character from how the book portrays them, saves it to your voices and to the book’s cast, and turns character voices on. It is a paid feature with a prepaid charge.
string[]
Characters to cast. Omit to cast the speakers with the most lines who are not cast yet. An empty list returns 400 empty_selection.
integer
default:"6"
How many characters to cast, 1–12.
  • Prepaid. 250 credits per character are reserved when you start the job. Each is charged when that character’s voice is made, and refunded if it fails. If the balance cannot cover every character, the call returns 402 and nothing is charged.
  • Plan. Auto-cast needs an active paid subscription plan that includes monthly credits. Without one (the free plan, or a paid add-on that grants no credits) it returns 402 with "code": "PREMIUM_TIER_REQUIRED". Library voices (cast/assign and the automatic pick) work on every plan.
  • Who is skipped. Without character_names, a character is already cast when its cast voice is one you chose (cast/assign) or one auto-cast designed, or one of its lines has a voice you set with a PUT. A free library voice from the automatic pick does not count, and neither does a voice narration saved on a line. So omitting character_names designs a voice for each character that only has a library pick, up to max_characters, at 250 credits per character designed. Name the characters in character_names to choose exactly what you pay for.
  • 409: an auto-cast job is already running for this project. 400: there are no characters to cast; detect speakers first. 503: auto-cast is temporarily unavailable, and nothing was charged.

Errors

Next steps

Audiobook API

The full pipeline: projects, manuscripts, narration, review and ACX export.

Voice Management

Clone a voice from a short sample and cast it as a character.