Tutorial 7 June 2026

How to Convert Text to Speech with a REST API

NarrateAI 3 min read

Building an app that reads text aloud? Whether you're narrating articles, generating podcast scripts, or making your product more accessible, a TTS API is the simplest path. This guide walks through the NarrateAI API from first request to downloaded audio file.

Prerequisites

You'll need:

That's it. No SDKs to install, no client libraries. It's plain HTTP.

Step 1: Submit a narration job

NarrateAI processes text asynchronously. You submit a job and get back a job ID immediately — the audio is ready moments later depending on length.

curl -X POST https://api.narrateai.mossforge.dev/v1/tts/async \
  -H "Authorization: Bearer nai_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "The quick brown fox jumped over the lazy dog.",
    "lang": "en",
    "voice": "michael"
  }'

The response comes back immediately:

{
  "job_id": "888d45c5-1234-5678-abcd-ef0123456789",
  "status": "processing",
  "total_chunks": 1,
  "total_chars": 45,
  "estimated_seconds": 3,
  "poll_url": "/v1/tts/async/888d45c5-..."
}

Save the job_id — you'll need it to fetch the result.

Step 2: Poll for completion

Short texts finish in a few seconds. Poll the job endpoint until status is complete:

curl https://api.narrateai.mossforge.dev/v1/tts/async/888d45c5-1234-5678-abcd-ef0123456789 \
  -H "Authorization: Bearer nai_your_key_here"

When it's ready:

{
  "job_id": "888d45c5-...",
  "status": "complete",
  "progress_percent": 100.0,
  "url": "https://s3.amazonaws.com/...",
  "duration_seconds": 3.2
}

The url is a presigned S3 link valid for 1 hour. Download it before it expires — the file itself is stored for 7 days.

Step 3: Download the audio

curl -o narration.wav "https://s3.amazonaws.com/..."

You now have a WAV file. Done.

Python example

Here's a complete script that submits a job and waits for it:

import time
import httpx

API_KEY  = "nai_your_key_here"
BASE_URL = "https://api.narrateai.mossforge.dev"
HEADERS  = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}

def narrate(text: str, lang: str = "en", voice: str = "michael") -> str:
    # Submit job
    resp = httpx.post(f"{BASE_URL}/v1/tts/async", headers=HEADERS, json={
        "text": text, "lang": lang, "voice": voice,
    })
    resp.raise_for_status()
    job_id = resp.json()["job_id"]
    print(f"Job submitted: {job_id}")

    # Poll until complete
    while True:
        status = httpx.get(f"{BASE_URL}/v1/tts/async/{job_id}", headers=HEADERS).json()
        print(f"  {status['progress_percent']:.0f}% complete")
        if status["status"] == "complete":
            return status["url"]
        if status["status"] == "failed":
            raise RuntimeError(f"Job failed: {status.get('error')}")
        time.sleep(2)

audio_url = narrate("Hello from NarrateAI. This is a test narration.")
print(f"Audio ready: {audio_url}")

Choosing a voice

NarrateAI ships with 10 voice styles — 5 male, 5 female. Pass the name or the ID:

Name Character
michael Male, natural (default)
emily Female, natural
richard Male, British accent
cassandra Female, British accent
alex Male, expressive

Working with long documents

The 100,000 character limit per job covers roughly a 75-page document or a full hour of audio. For a 10,000-word article, the API handles chunking and stitching internally — you still get back a single WAV file.

curl -X POST https://api.narrateai.mossforge.dev/v1/tts/async \
  -H "Authorization: Bearer nai_your_key_here" \
  -H "Content-Type: application/json" \
  -d "{\"text\": \"$(cat my-article.txt)\", \"lang\": \"en\", \"voice\": \"emily\"}"

Expression tags

Want the voice to sound more human? Inline expression tags adjust delivery:

She paused. <breath> "I wasn't expecting that," she said with a <laugh> laugh.

Supported tags: <laugh>, <sigh>, <breath>, <scream>, <surprise>, <yawn>, <cough>, <throatclear>, <angry>, <sad>

What's next

Get started

Try NarrateAI free

125,000 characters on signup. No credit card required.

Get your API key →