DEV Community

VoiceDeveloper
VoiceDeveloper

Posted on

Build a Text-to-Speech App with ElevenLabs and Python

Why a Python TTS App?

If you’ve ever wanted to turn a paragraph of text into a natural‑sounding voice clip, you’re not alone. Text‑to‑speech (TTS) has moved far beyond the robotic “read‑aloud” voices of the early 2000s. Today, you can generate expressive, human‑like speech in a matter of seconds, and you can even clone a specific voice with a few minutes of audio.

In this post we’ll walk through a complete, production‑ready Python app that talks back to you using ElevenLabs – a service that’s quickly become the go‑to for high‑quality voice AI. By the end you’ll have:

  • A small Flask server that accepts raw text and returns an MP3 file
  • A reusable helper that talks to the ElevenLabs API (both via requests and curl)
  • A quick demo of voice cloning using a custom audio sample

Let’s dive in.

1. Get an ElevenLabs API key

First things first: you need an API key from ElevenLabs. Sign up at the affiliate link below – it’s free to start and gives you a generous quota for testing.

👉 Grab your key here

Once you have the key, store it safely. For local development a .env file works fine:

# .env
ELEVENLABS_API_KEY=your_secret_key_here
Enter fullscreen mode Exit fullscreen mode

We’ll load this with python-dotenv later.

2. Set up the Python environment

Create a fresh virtual environment and install the required packages:

python -m venv venv
source venv/bin/activate   # on Windows: venv\Scripts\activate
pip install flask requests python-dotenv
Enter fullscreen mode Exit fullscreen mode
  • flask – tiny web framework for our API
  • requests – simple HTTP client
  • python-dotenv – loads the .env file into os.environ

3. The core helper: talking to ElevenLabs

ElevenLabs exposes a straightforward REST endpoint. Below is a minimal wrapper that sends text and receives an MP3 stream.

# tts.py
import os
import requests
from typing import Optional

ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY")
BASE_URL = "https://api.elevenlabs.io/v1"

def synthesize(
    text: str,
    voice_id: str = "EXAVITQu4vr4xnSDxMaL",  # default “Rachel” voice
    model_id: str = "eleven_multilingual_v2",
    stability: float = 0.75,
    similarity_boost: float = 0.85,
) -> bytes:
    """
    Call ElevenLabs TTS endpoint and return raw MP3 bytes.
    """
    url = f"{BASE_URL}/text-to-speech/{voice_id}"
    headers = {
        "xi-api-key": ELEVENLABS_API_KEY,
        "Content-Type": "application/json",
    }
    payload = {
        "text": text,
        "model_id": model_id,
        "voice_settings": {
            "stability": stability,
            "similarity_boost": similarity_boost,
        },
    }

    response = requests.post(url, json=payload, headers=headers, timeout=30)
    response.raise_for_status()
    return response.content
Enter fullscreen mode Exit fullscreen mode

A quick curl equivalent (useful for debugging) looks like this:

curl -X POST "https://api.elevenlabs.io/v1/text-to-speech/EXAVITQu4vr4xnSDxMaL" \
  -H "xi-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "text": "Hello, world! This is ElevenLabs speaking.",
        "model_id": "eleven_multilingual_v2",
        "voice_settings": {
          "stability": 0.75,
          "similarity_boost": 0.85
        }
      }' --output hello.mp3
Enter fullscreen mode Exit fullscreen mode

Replace YOUR_API_KEY with the key you got from the link above.

4. Build a tiny Flask wrapper

Now let’s expose the TTS function as a web endpoint. This makes it easy to integrate with front‑ends, mobile apps, or even Discord bots.

# app.py
import os
from flask import Flask, request, send_file, abort
from io import BytesIO
from dotenv import load_dotenv
from tts import synthesize

load_dotenv()  # pulls ELEVENLABS_API_KEY into the environment

app = Flask(__name__)

@app.route("/speak", methods=["POST"])
def speak():
    data = request.get_json()
    if not data or "text" not in data:
        abort(400, description="JSON payload must contain a 'text' field")

    text = data["text"]
    voice_id = data.get("voice_id", "EXAVITQu4vr4xnSDxMaL")

    try:
        audio_bytes = synthesize(text, voice_id=voice_id)
    except Exception as e:
        abort(500, description=str(e))

    # Return as an in‑memory MP3 file
    return send_file(
        BytesIO(audio_bytes),
        mimetype="audio/mpeg",
        as_attachment=False,
        download_name="speech.mp3",
    )

if __name__ == "__main__":
    port = int(os.getenv("PORT", 5000))
    app.run(host="0.0.0.0", port=port, debug=True)
Enter fullscreen mode Exit fullscreen mode

Run it:

python app.py
Enter fullscreen mode Exit fullscreen mode

Then test with curl:

curl -X POST http://localhost:5000/speak \
  -H "Content-Type: application/json" \
  -d '{"text":"ElevenLabs makes voice AI feel magical!"}' \
  --output result.mp3
Enter fullscreen mode Exit fullscreen mode

Open result.mp3 – you should hear a clear, natural voice speaking your sentence.

5. Voice cloning in a few lines

ElevenLabs also lets you clone a custom voice using a short audio sample (as little as 30 seconds). The workflow is:

  1. Upload a WAV/MP3 file to the /voices/add endpoint.
  2. Retrieve the new voice_id.
  3. Use that voice_id in the synthesize call.

Here’s a helper that does the upload:

def add_voice(name: str, audio_path: str) -> str:
    """
    Upload a voice sample and return the new voice_id.
    """
    url = f"{BASE_URL}/voices/add"
    headers = {"xi-api-key": ELEVENLABS_API_KEY}
    files = {
        "files": open(audio_path, "rb"),
        "voice_name": (None, name),
    }

    response = requests.post(url, headers=headers, files=files)
    response.raise_for_status()
    voice_id = response.json()["voice_id"]
    return voice_id
Enter fullscreen mode Exit fullscreen mode

Example usage:

my_voice_id = add_voice("MyDemoVoice", "samples/my_demo.wav")
audio = synthesize("Hello, this is my own voice!", voice_id=my_voice_id)
with open("my_voice.mp3", "wb") as f:
    f.write(audio)
Enter fullscreen mode Exit fullscreen mode

Now you have a personalized voice that sounds like you (or anyone you have permission to clone). This is perfect for building audiobooks, interactive assistants, or even custom IVR systems.

6. Putting it together: a simple front‑end

If you want a quick UI to test the service, add an HTML page that posts to /speak and plays the result:

<!-- static/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>ElevenLabs TTS Demo</title>
</head>
<body>
  <h1>Text‑to‑Speech Playground</h1>
  <textarea id="txt" rows="4" cols="50">Enter text here...</textarea><br>
  <button onclick="speak()">Speak</button>
  <audio id="player" controls></audio>

  <script>
    async function speak() {
      const text = document.getElementById('txt').value;
      const resp = await fetch('/speak', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({text})
      });
      const blob = await resp.blob();
      const url = URL.createObjectURL(blob);
      document.getElementById('player').src = url;
    }
  </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Serve the static folder from Flask:

app = Flask(__name__, static_folder="static")
# existing routes stay the same
Enter fullscreen mode Exit fullscreen mode

Now you have a full‑stack demo that you can run locally, deploy to Render, Fly.io, or any container platform, and share with teammates.

7. Tips for Production

  • Rate limiting – ElevenLabs enforces per‑key limits. Cache generated audio when possible.
  • Error handling – The API may return 429 (Too Many Requests) or 422 (Invalid voice). Gracefully surface those messages to the client.
  • Security – Never expose your API key to the browser. All calls to ElevenLabs must happen server‑side.
  • Audio storage – For longer‑term use, consider uploading MP3s to S3 or another CDN and serving the URL instead of streaming from memory.

8. Next steps

  • Multi‑language support – ElevenLabs models support dozens of languages; just pass the desired text.
  • Dynamic voice switching – Pull a list of available voices (/voices) and let users pick.
  • Streaming – For real‑time chatbots, stream chunks of audio as they become available (requires WebSockets or Server‑Sent Events).

Ready to give it a spin?

All the heavy lifting of realistic speech synthesis and voice cloning is handled by ElevenLabs, letting you focus on the product logic. Grab your free API key, run the code above, and start building the next generation of voice‑first experiences.

👉 Try ElevenLabs now: https://try.elevenlabs.io/kr07zfuqn1bp

Happy coding!

Top comments (0)