Skip to content
pinokio edited this page Sep 28, 2026 · 2 revisions

API reference

Everything the web interface does is an HTTP call, so the app can be scripted. This page shows the common tasks with examples. The complete list of all 181 routes, generated from the code, is in the README's API reference; the interactive docs are at /docs and the OpenAPI schema at /openapi.json on a running app.

Basics

  • Address: http://127.0.0.1:<port>. Pinokio chooses the port; Docker uses 4200. The examples below use http://127.0.0.1:4200.
  • Authentication: off by default. If you set ALEXANDRIA_AUTH_PASSWORD, every request needs HTTP Basic credentials — see the README's Authentication section.
  • Background tasks: long jobs start in the background. Poll GET /api/status/<task> until running is false, or GET /api/status/eta for the progress and time left of whatever is running.

Task names you can poll:

task started by
script generating a script
review / batch_review reviewing one script / several
nicknames finding nicknames
persona generating personas
voices suggesting LoRA voices
audio rendering audio
audacity_export / chapter_export exports
drift_check Check Voices
lora_training / lora_test training / testing a voice adapter
voice_design Voice Designer previews
dataset_builder Dataset Builder batch generation
preparer / batch_preparer the Preparer
voicelab the Voice Lab pipeline
benchmark the benchmark harness

Settings

# read the current settings (API keys are redacted)
curl http://127.0.0.1:4200/api/config

# the default prompt texts
curl http://127.0.0.1:4200/api/default_prompts

# save settings (send the full settings object you read above, with your changes)
curl -X POST http://127.0.0.1:4200/api/config \
  -H "Content-Type: application/json" \
  -d @config.json

Script generation

# upload a book; it becomes the active book
curl -X POST http://127.0.0.1:4200/api/upload -F "file=@mybook.txt"

# generate a script from the active book (all fields optional)
curl -X POST http://127.0.0.1:4200/api/generate_script \
  -H "Content-Type: application/json" \
  -d '{"first_person_narrator": null, "strip_front_matter": true, "start_over": false}'

# follow its progress
curl http://127.0.0.1:4200/api/status/script
curl http://127.0.0.1:4200/api/status/eta

# review the script (a second model pass that fixes common mistakes)
curl -X POST http://127.0.0.1:4200/api/review_script
curl http://127.0.0.1:4200/api/status/review

# the current script
curl http://127.0.0.1:4200/api/annotated_script

generate_script always works on the active book — the last one uploaded, or one chosen with POST /api/uploads/select. It does not take a file name.

Voices

# the speakers and their current voice settings
curl http://127.0.0.1:4200/api/voices

# save voice settings
curl -X POST http://127.0.0.1:4200/api/save_voice_config \
  -H "Content-Type: application/json" \
  -d '{
    "NARRATOR": {"type": "custom", "voice": "Ryan", "character_style": "calm, measured narration"},
    "ELENA":    {"type": "clone", "ref_audio": "designed_voices/previews/preview_123.wav", "ref_text": "Hello there."},
    "MARCUS":   {"type": "lora", "adapter_id": "dark_voice_123", "adapter_path": "lora_models/dark_voice_123", "character_style": "menacing undertone"},
    "SOLDIER":  {"type": "design", "description": "Young strong soldier"}
  }'

Voice settings fields

field used by meaning
type all custom, builtin_lora, clone, lora, design or ensemble
voice custom the built-in voice name (Aiden, Dylan, Eric, …)
character_style custom, lora the identity anchor sent with every line
style_timeline custom, lora change points: {"from_index": N, "character_style": "..."}
seed all random seed; "-1" for random
ref_audio, ref_text clone the reference audio and its exact transcript
adapter_id, adapter_path lora the adapter
description design the base voice description
members ensemble the speakers who speak together
alias_of any use another speaker's voice

An alias:

curl -X POST http://127.0.0.1:4200/api/save_voice_config \
  -H "Content-Type: application/json" \
  -d '{
    "ELENA":       {"type": "clone", "ref_audio": "designed_voices/previews/preview_123.wav", "ref_text": "Hello there."},
    "YOUNG ELENA": {"type": "clone", "alias_of": "ELENA"}
  }'

When audio is rendered, "YOUNG ELENA" uses ELENA's voice.

Personas

# generate personas for characters that have no voice yet
curl -X POST http://127.0.0.1:4200/api/generate_personas \
  -H "Content-Type: application/json" \
  -d '{"new_only": true}'

# large casts: process speakers in batches
curl -X POST http://127.0.0.1:4200/api/generate_personas \
  -H "Content-Type: application/json" \
  -d '{"advanced": true, "batch_size": 40}'

curl http://127.0.0.1:4200/api/status/persona
curl -X POST http://127.0.0.1:4200/api/cancel_persona

Chunks and rendering

# all chunks
curl http://127.0.0.1:4200/api/chunks

# edit a chunk (any of text, instruct, speaker, pause_after)
curl -X POST http://127.0.0.1:4200/api/chunks/5 \
  -H "Content-Type: application/json" \
  -d '{"text": "Updated dialogue", "instruct": "Excited, bright energy."}'

# render one chunk
curl -X POST http://127.0.0.1:4200/api/chunks/5/generate

# render several: batched (the fast path used with local TTS)
curl -X POST http://127.0.0.1:4200/api/generate_batch_fast \
  -H "Content-Type: application/json" \
  -d '{"indices": [0, 1, 2, 3, 4]}'

# render several: one request per line, in parallel
curl -X POST http://127.0.0.1:4200/api/generate_batch \
  -H "Content-Type: application/json" \
  -d '{"indices": [0, 1, 2, 3, 4]}'

curl http://127.0.0.1:4200/api/status/audio

# merge into the finished audiobook
curl -X POST http://127.0.0.1:4200/api/merge

Saved scripts

curl http://127.0.0.1:4200/api/scripts

curl -X POST http://127.0.0.1:4200/api/scripts/save \
  -H "Content-Type: application/json" -d '{"name": "my-novel"}'

curl -X POST http://127.0.0.1:4200/api/scripts/load \
  -H "Content-Type: application/json" -d '{"name": "my-novel"}'

Voice Designer

# preview a voice from a description
curl -X POST http://127.0.0.1:4200/api/voice_design/preview \
  -H "Content-Type: application/json" \
  -d '{"description": "A warm, deep male voice with a calm and steady tone", "sample_text": "Hello, how are you?"}'

# save a preview as a designed voice
curl -X POST http://127.0.0.1:4200/api/voice_design/save \
  -H "Content-Type: application/json" \
  -d '{"name": "warm_narrator", "description": "A warm, deep male voice", "sample_text": "Hello.", "preview_file": "designed_voices/previews/preview_123.wav"}'

curl http://127.0.0.1:4200/api/voice_design/list
curl -X DELETE http://127.0.0.1:4200/api/voice_design/<voice_id>

Voice (LoRA) training

# upload a dataset: a ZIP of WAV files plus metadata.jsonl
curl -X POST http://127.0.0.1:4200/api/lora/upload_dataset -F "file=@dataset.zip"

curl http://127.0.0.1:4200/api/lora/datasets
curl -X DELETE http://127.0.0.1:4200/api/lora/datasets/<dataset_id>

# train (the values shown are the defaults; see the Training guide before changing lr)
curl -X POST http://127.0.0.1:4200/api/lora/train \
  -H "Content-Type: application/json" \
  -d '{
    "name": "soldier_voice",
    "dataset_id": "gruff_soldier",
    "epochs": 5,
    "lr": 1e-6,
    "lora_r": 32,
    "lora_alpha": 128,
    "batch_size": 1,
    "gradient_accumulation_steps": 8,
    "language": "english"
  }'

curl http://127.0.0.1:4200/api/status/lora_training
curl http://127.0.0.1:4200/api/lora/models

# test a trained adapter
curl -X POST http://127.0.0.1:4200/api/lora/test \
  -H "Content-Type: application/json" \
  -d '{"adapter_id": "soldier_voice_1234567890", "text": "Moving to position.", "instruct": "Tense, whispering."}'

curl -X DELETE http://127.0.0.1:4200/api/lora/models/<adapter_id>

Do not raise lr to 5e-6: voices trained at that rate never stop talking. See the Training guide.

Dataset Builder

curl http://127.0.0.1:4200/api/dataset_builder/list

curl -X POST http://127.0.0.1:4200/api/dataset_builder/create \
  -H "Content-Type: application/json" -d '{"name": "my_voice_dataset"}'

# the description and global seed
curl -X POST http://127.0.0.1:4200/api/dataset_builder/update_meta \
  -H "Content-Type: application/json" \
  -d '{"name": "my_voice_dataset", "description": "A warm male narrator", "global_seed": "42"}'

# the sample rows
curl -X POST http://127.0.0.1:4200/api/dataset_builder/update_rows \
  -H "Content-Type: application/json" \
  -d '{"name": "my_voice_dataset", "rows": [{"text": "Hello world.", "emotion": "cheerful"}]}'

# render one sample
curl -X POST http://127.0.0.1:4200/api/dataset_builder/generate_sample \
  -H "Content-Type: application/json" \
  -d '{"dataset_name": "my_voice_dataset", "sample_index": 0, "description": "A warm male voice, cheerful", "text": "Hello world.", "seed": -1}'

# render several samples in the background
curl -X POST http://127.0.0.1:4200/api/dataset_builder/generate_batch \
  -H "Content-Type: application/json" \
  -d '{"name": "my_voice_dataset", "description": "A warm male voice", "samples": [{"text": "Hello.", "emotion": "cheerful"}]}'

curl http://127.0.0.1:4200/api/dataset_builder/status/my_voice_dataset
curl -X POST http://127.0.0.1:4200/api/dataset_builder/cancel

# save as a training dataset; ref_index picks the reference sample
curl -X POST http://127.0.0.1:4200/api/dataset_builder/save \
  -H "Content-Type: application/json" -d '{"name": "my_voice_dataset", "ref_index": 0}'

curl -X DELETE http://127.0.0.1:4200/api/dataset_builder/my_voice_dataset

Downloads and export

# the merged audiobook
curl http://127.0.0.1:4200/api/audiobook --output audiobook.mp3

# Audacity: start the export, wait, then download the zip
curl -X POST http://127.0.0.1:4200/api/export_audacity
curl http://127.0.0.1:4200/api/status/audacity_export
curl http://127.0.0.1:4200/api/export_audacity --output audacity_export.zip

A whole book, from Python

import requests, time

BASE = "http://127.0.0.1:4200"

def wait_for(task):
    while requests.get(f"{BASE}/api/status/{task}").json().get("running"):
        time.sleep(5)

with open("mybook.txt", "rb") as f:
    requests.post(f"{BASE}/api/upload", files={"file": f})
requests.post(f"{BASE}/api/generate_script", json={})
wait_for("script")

requests.post(f"{BASE}/api/save_voice_config", json={
    "NARRATOR": {"type": "custom", "voice": "Ryan", "character_style": "calm narrator"},
    "HERO": {"type": "lora", "adapter_id": "hero_voice_123", "adapter_path": "lora_models/hero_voice_123"},
})

chunks = requests.get(f"{BASE}/api/chunks").json()
requests.post(f"{BASE}/api/generate_batch_fast", json={"indices": [c["id"] for c in chunks]})
wait_for("audio")

requests.post(f"{BASE}/api/merge")
wait_for("audio")
with open("audiobook.mp3", "wb") as f:
    f.write(requests.get(f"{BASE}/api/audiobook").content)

The same, from JavaScript

const BASE = "http://127.0.0.1:4200";

async function waitFor(task) {
  while ((await (await fetch(`${BASE}/api/status/${task}`)).json()).running) {
    await new Promise(r => setTimeout(r, 5000));
  }
}

const form = new FormData();
form.append("file", fileInput.files[0]);
await fetch(`${BASE}/api/upload`, { method: "POST", body: form });
await fetch(`${BASE}/api/generate_script`, {
  method: "POST", headers: { "Content-Type": "application/json" }, body: "{}"
});
await waitFor("script");

await fetch(`${BASE}/api/save_voice_config`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ NARRATOR: { type: "custom", voice: "Ryan", character_style: "calm" } })
});

const chunks = await (await fetch(`${BASE}/api/chunks`)).json();
await fetch(`${BASE}/api/generate_batch_fast`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ indices: chunks.map(c => c.id) })
});
await waitFor("audio");

await fetch(`${BASE}/api/merge`, { method: "POST" });

Clone this wiki locally