Neural Documentation

Transform documents into narrated audio with the Narrator API. Our pipeline handles document extraction, script generation with character detection, and high-fidelity text-to-speech synthesis with 100+ unique voices.

Base URL: https://api.narrator.ai/api/v1

API Capabilities

1.
Document Extraction

PDF, DOCX, PPTX, images to markdown/JSON

2.
Script Formatting

AI-powered character detection & dialogue tagging

3.
TTS Synthesis

100+ voices, streaming & batch, multi-character


Authentication

REQUIRED

All API requests require authentication via the x-api-key header.

BASH cURL
curl "https://api.narrator.ai/api/v1/tts/voices" \
  -H "x-api-key: YOUR_API_KEY"

Additional Headers

Some endpoints (script formatting, AI models) require an OpenRouter API key:

x-openrouter-api-key: sk-or-v1-xxxxxxxxxxxxx

List Voices

GET /tts/voices

Get a list of all available TTS voices. Returns voice aliases that can be used with synthesis endpoints.

BASH cURL
curl -X GET "https://api.narrator.ai/api/v1/tts/voices" \
  -H "x-api-key: YOUR_API_KEY"
JSON Response
{
  "voices": [
    "Thundervox",
    "Wonderstruck",
    "Chronicler",
    "Serenity",
    "Storyteller",
    // ... 100+ more voices
  ],
  "count": 107
}

Popular Voice Categories

Narration: Storyteller, Chronicler, Narrator, Mythweaver
Calm: Serenity, Tranquil, Peaceful, Stillwater
Expressive: Thundervox, Wonderstruck, Amazement, Firebrand
Characters: Margaret, David, Elizabeth, James, Professor

Synthesize Speech

POST /tts/synthesize

Convert text to speech and receive a complete WAV audio file. Best for non-real-time generation (podcasts, articles, audiobooks).

Parameter Type Description
text * string Text to convert to speech. Max 10,000 chars. Markdown auto-converted to plain text.
voice string Voice alias (e.g., "Thundervox", "Chronicler"). Defaults to "Thundervox".
temperature float Randomness/creativity (0.2-1.2, default: 0.8). Lower = more consistent.
top_k int Limits sampling to K most likely tokens (default: 250).
cfg_alpha float Voice guidance strength (0.5-3.0, default: 1.5). Higher = stronger voice character.
seed int Random seed for reproducible output.
PYTHON requests
import requests

url = "https://api.narrator.ai/api/v1/tts/synthesize"

payload = {
    "text": "Welcome to the future of audio narration.",
    "voice": "Chronicler",
    "temperature": 0.8
}

headers = {
    "x-api-key": "YOUR_API_KEY",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

# Save audio file (WAV format, 24kHz, PCM Float32)
with open("speech.wav", "wb") as f:
    f.write(response.content)
BASH cURL
curl -X POST "https://api.narrator.ai/api/v1/tts/synthesize" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello world!", "voice": "Thundervox"}' \
  --output speech.wav

Real-time Streaming

WEBSOCKET /tts/stream

Ultra-low latency streaming via WebSocket. Audio chunks are received as they're generated - perfect for real-time playback.

1. Connect

Connect with voice and api_key as query parameters.

2. Send Text

Send text as plain string. Server handles chunking automatically.

3. Receive Audio

Get base64-encoded PCM Float32 chunks for immediate playback.

JAVASCRIPT Browser
// Connect to WebSocket with voice and API key
const ws = new WebSocket(
  'wss://api.narrator.ai/api/v1/tts/stream?voice=Chronicler&api_key=YOUR_API_KEY'
);

const audioContext = new AudioContext();
let scheduledTime = 0;

ws.onmessage = async (event) => {
  const msg = JSON.parse(event.data);

  if (msg.status === 'ready') {
    // Connection ready - send text to synthesize
    ws.send("Hello! This is real-time streaming synthesis.");

  } else if (msg.type === 'audio') {
    // Decode base64 to raw bytes
    const audioBytes = Uint8Array.from(atob(msg.data), c => c.charCodeAt(0));
    const audioFloat32 = new Float32Array(audioBytes.buffer);

    // Create AudioBuffer for playback
    const audioBuffer = audioContext.createBuffer(1, audioFloat32.length, msg.sample_rate);
    audioBuffer.getChannelData(0).set(audioFloat32);

    // Schedule for seamless playback
    const source = audioContext.createBufferSource();
    source.buffer = audioBuffer;
    source.connect(audioContext.destination);
    source.start(audioContext.currentTime + scheduledTime);
    scheduledTime += audioBuffer.duration;

  } else if (msg.type === 'finalComplete') {
    console.log('Synthesis complete!');
    scheduledTime = 0; // Reset for next request
  }
};

Message Types

{"status": "ready"} - Connection established
{"type": "audio", "data": "base64...", "sample_rate": 24000} - Audio chunk
{"type": "complete", "chunk": 1, "total_chunks": 3} - Text chunk progress
{"type": "finalComplete", "total_chunks": 3} - All synthesis done
{"status": "error", "error": "message"} - Error occurred

Script Narration

WEBSOCKET /tts/script/stream

Stream multi-character narration with different voices per character. Perfect for stories, dialogues, and audiobook production.

JAVASCRIPT Browser
const ws = new WebSocket(
  'wss://api.narrator.ai/api/v1/tts/script/stream?api_key=YOUR_API_KEY'
);

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.status === 'ready') {
    // Send script with character-voice mappings
    ws.send(JSON.stringify({
      "content": `[NARRATOR] Once upon a time, in a distant land...
[PRINCESS] "I wish for adventure," she sighed.
[NARRATOR] said the princess.
[KNIGHT] "Then adventure you shall have!"
[NARRATOR] replied the brave knight.`,
      "voices": {
        "NARRATOR": "Chronicler",
        "PRINCESS": "Serenity",
        "KNIGHT": "Thundervox"
      },
      "default_voice": "Chronicler",
      "filename": "chapter1"
    }));

  } else if (msg.type === 'audio') {
    console.log(`Audio from ${msg.character} (${msg.section}/${msg.total_sections})`);
    playAudioChunk(msg.data, msg.sample_rate);

  } else if (msg.type === 'sectionComplete') {
    console.log(`Completed: ${msg.character}`);

  } else if (msg.type === 'finalComplete') {
    console.log('All narration complete!');
  }
};
Request Field Type Description
content * string Script with [CHARACTER] delimiters for each speaking part.
voices * object Mapping of character names to voice aliases.
default_voice string Fallback voice for unmapped characters.
filename string Optional script filename for tracking.

Extract Document Content

POST /extraction

Extract content from documents (PDF, DOCX, PPTX, images) into markdown or JSON format for further processing.

Supported File Types

PDF DOCX PPTX XLSX PNG JPEG GIF HTML Markdown TXT
BASH cURL
curl -X POST "https://api.narrator.ai/api/v1/extraction" \
  -H "x-api-key: YOUR_API_KEY" \
  -F "[email protected]" \
  -F "to_formats=md" \
  -F "to_formats=json"
PYTHON requests
import requests

url = "https://api.narrator.ai/api/v1/extraction"

with open("document.pdf", "rb") as f:
    files = {"file": ("document.pdf", f, "application/pdf")}
    data = {"to_formats": ["md"]}
    headers = {"x-api-key": "YOUR_API_KEY"}

    response = requests.post(url, files=files, data=data, headers=headers)
    result = response.json()

# Access extracted markdown content
markdown_content = result["document"]["md_content"]
print(markdown_content)
JSON Response
{
  "document": {
    "filename": "document.pdf",
    "md_content": "# Chapter 1\n\nOnce upon a time...",
    "json_content": {...}
  },
  "status": "completed",
  "processing_time": 2.5
}

Format Script

POST /script/format-json

AI-powered script formatting. Automatically detects characters, extracts dialogue, and creates narration-ready scripts with [CHARACTER] tags.

Note: This endpoint requires an OpenRouter API key for AI processing. Pass it via the x-openrouter-api-key header.

PYTHON requests
import requests

url = "https://api.narrator.ai/api/v1/script/format-json"

payload = {
    "text": """Once upon a time, there was a princess.
"I would like a cup of coffee," said the princess.
"I'll have the same," replied the prince with a smile.""",
    "model": "gpt-4o-mini"
}

headers = {
    "x-api-key": "YOUR_API_KEY",
    "x-openrouter-api-key": "sk-or-v1-xxxxxxxxxxxxx",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)
result = response.json()

# Use the formatted script
print(result["formatted_text"])
# Output:
# [NARRATOR] Once upon a time, there was a princess.
# [PRINCESS] "I would like a cup of coffee,"
# [NARRATOR] said the princess.
# [PRINCE] "I'll have the same,"
# [NARRATOR] replied the prince with a smile.
JSON Response
{
  "filename": "input.txt",
  "success": true,
  "script": {
    "title": "Untitled",
    "lines": [
      {"character": "NARRATOR", "text": "Once upon a time...", "line_number": 1},
      {"character": "PRINCESS", "text": "\"I would like...\"", "line_number": 2}
    ],
    "characters": ["NARRATOR", "PRINCESS", "PRINCE"]
  },
  "formatted_text": "[NARRATOR] Once upon a time...",
  "processing_time": 3.2,
  "model_used": "gpt-4o-mini"
}

Complete Workflow Example

TUTORIAL

End-to-end example: Upload a document, extract content, format it as a script with character detection, and synthesize multi-voice narration.

PYTHON complete_workflow.py
import requests
import asyncio
import websockets
import json
import base64

BASE_URL = "https://api.narrator.ai/api/v1"
API_KEY = "YOUR_API_KEY"
OPENROUTER_KEY = "sk-or-v1-xxxxxxxxxxxxx"

headers = {
    "x-api-key": API_KEY,
    "x-openrouter-api-key": OPENROUTER_KEY,
}

# Step 1: Extract content from document
with open("story.pdf", "rb") as f:
    extract_resp = requests.post(
        f"{BASE_URL}/extraction",
        files={"file": f},
        data={"to_formats": ["md"]},
        headers={"x-api-key": API_KEY}
    )
    extracted_text = extract_resp.json()["document"]["md_content"]

print(f"Extracted {len(extracted_text)} characters")

# Step 2: Format as script with character detection
script_resp = requests.post(
    f"{BASE_URL}/script/format-json",
    json={"text": extracted_text, "model": "gpt-4o-mini"},
    headers={**headers, "Content-Type": "application/json"}
)
script_data = script_resp.json()
formatted_script = script_data["formatted_text"]
characters = script_data["script"]["characters"]

print(f"Detected characters: {characters}")

# Step 3: Create voice mapping for each character
voice_mapping = {
    "NARRATOR": "Chronicler",
    "PRINCESS": "Serenity",
    "PRINCE": "Thundervox",
    "WITCH": "Mysterion",
}

# Step 4: Stream multi-character narration
async def stream_narration():
    uri = f"wss://api.narrator.ai/api/v1/tts/script/stream?api_key={API_KEY}"
    audio_chunks = []

    async with websockets.connect(uri) as ws:
        # Wait for ready
        ready = json.loads(await ws.recv())
        print(f"Connected: {ready}")

        # Send script with voice mappings
        await ws.send(json.dumps({
            "content": formatted_script,
            "voices": voice_mapping,
            "default_voice": "Chronicler",
            "filename": "my_story"
        }))

        # Collect audio chunks
        while True:
            msg = json.loads(await ws.recv())

            if msg["type"] == "audio":
                audio_chunks.append(base64.b64decode(msg["data"]))
                print(f"  {msg['character']} ({msg['section']}/{msg['total_sections']})")

            elif msg["type"] == "finalComplete":
                print(f"Complete! {msg['total_sections']} sections")
                break

    # Save combined audio (would need WAV header in production)
    with open("narration.raw", "wb") as f:
        f.write(b"".join(audio_chunks))

    print("Saved narration.raw")

asyncio.run(stream_narration())

Workflow Summary

1
Upload Document

PDF, DOCX, etc.

2
Extract Text

/extraction

3
Format Script

/script/format-json

4
Narrate

/tts/script/stream


FastRTC Integration

GUIDE

Build real-time voice assistants by combining FastRTC with the Narrator API. FastRTC handles WebRTC/WebSocket streaming with automatic voice activity detection and pause detection, while Narrator provides high-quality TTS for responses.

What is FastRTC?

FastRTC is a Python library that transforms functions into real-time audio/video streams. It handles the complexity of WebRTC connections, voice activity detection, and turn-taking automatically.

WebRTC Streaming Voice Activity Detection Pause Detection FastAPI Integration
BASH Installation
# Install FastRTC with voice activity detection
pip install "fastrtc[vad]"

# Additional dependencies for this example
pip install websockets numpy
PYTHON voice_assistant.py
import asyncio
import json
import base64
import numpy as np
import websockets
from fastapi import FastAPI
from fastrtc import ReplyOnPause, Stream

API_KEY = "YOUR_API_KEY"
NARRATOR_WS_URL = "wss://api.narrator.ai/api/v1/tts/stream"

async def synthesize_with_narrator(text: str, voice: str = "Chronicler"):
    """Stream TTS audio from Narrator API."""
    uri = f"{NARRATOR_WS_URL}?voice={voice}&api_key={API_KEY}"

    async with websockets.connect(uri) as ws:
        # Wait for ready signal
        ready = json.loads(await ws.recv())
        if ready.get("status") != "ready":
            return

        # Send text for synthesis
        await ws.send(text)

        # Receive and yield audio chunks
        while True:
            msg = json.loads(await ws.recv())

            if msg.get("type") == "audio":
                # Decode base64 audio to numpy array
                audio_bytes = base64.b64decode(msg["data"])
                audio_array = np.frombuffer(audio_bytes, dtype=np.float32)
                sample_rate = msg.get("sample_rate", 24000)
                yield (sample_rate, audio_array)

            elif msg.get("type") == "finalComplete":
                break

def voice_assistant(audio: tuple[int, np.ndarray]):
    """
    Handler triggered when user stops speaking.
    Receives audio, processes it, and yields TTS response.
    """
    sample_rate, audio_array = audio

    # Step 1: Transcribe user audio (use your preferred STT)
    # user_text = transcribe(audio_array, sample_rate)
    user_text = "Hello, tell me about the weather."  # Placeholder

    # Step 2: Generate response (use your preferred LLM)
    # response_text = generate_response(user_text)
    response_text = "The weather today is sunny with a high of 72 degrees."

    # Step 3: Stream TTS response from Narrator API
    async def stream_response():
        async for chunk in synthesize_with_narrator(response_text):
            yield chunk

    # Run async generator in sync context
    loop = asyncio.new_event_loop()
    gen = stream_response()
    try:
        while True:
            yield loop.run_until_complete(gen.__anext__())
    except StopAsyncIteration:
        pass
    finally:
        loop.close()

# Create the FastRTC stream
stream = Stream(
    handler=ReplyOnPause(voice_assistant),
    modality="audio",
    mode="send-receive",
)

# Mount on FastAPI
app = FastAPI()
stream.mount(app)

# Run with: uvicorn voice_assistant:app --host 0.0.0.0 --port 8000
# Test with: stream.ui.launch() for built-in UI

Key Concepts

ReplyOnPause

Triggers your handler when the user stops speaking. Handles voice activity detection automatically.

Stream

Core class that manages WebRTC connections and routes audio between client and your handler.

Audio Format

tuple[int, np.ndarray] - sample rate and audio samples as numpy array.

Mounting

Use .mount(app) for FastAPI or .ui.launch() for testing.

Voice Assistant Architecture

1
User Speaks

FastRTC captures audio via WebRTC

2
Pause Detected

ReplyOnPause triggers handler

3
Process + LLM

STT, generate response

4
Narrator TTS

Stream audio back to user

Tip: For production, consider using fastrtc[vad, stt] for built-in speech-to-text, or integrate with services like Groq Whisper or Deepgram for transcription.