EasyVoice
VoicesPricingAPI
EasyVoice

Free text-to-speech powered by open source AI.

Product

  • Voices
  • Pricing
  • API

Resources

  • Blog
  • Documentation
  • About

Legal

  • Privacy Policy
  • Terms of Service

© 2026 EasyVoice. Powered by Kokoro-82M (Apache 2.0).

Built with ❤️ and open source AI.

Built by InfoDriven

Dubai, United Arab Emirates · Support@infodriven.ae · infodriven.ae

  1. Home
  2. /OpenAI TTS Alternative
  3. /Migrate from OpenAI TTS to EasyVoice in 5 lines

Migrate from OpenAI TTS to EasyVoice in 5 lines

Migrating off OpenAI's TTS API is, in practice, a 5-line code change. The request body shape — voice, input text, response format — is identical between vendors. The auth pattern (Bearer token in the Authorization header) is identical. Response handling (raw audio bytes, optionally streamed via chunked transfer-encoding) is identical. Only three things change: the URL (api.openai.com → easyvoice.ae), the voice ID (alloy → af_alloy and the rest of the mapping table), and the auth key environment variable. This guide walks through the full migration on a real openai.audio.speech.create call: the before/after Python diff, the equivalent JavaScript / fetch diff, model parameter mapping (tts-1 / tts-1-hd → kokoro), response_format compatibility (mp3, wav, opus all supported), and how streaming works identically across both APIs. Every code sample below uses real, working endpoints — paste them into a terminal with the right API keys set and they run.

5,000 characters per day free, no credit card. Pro $9.99/mo unlimited vs OpenAI $15/1M (tts-1) / $30/1M (tts-1-hd).

Part of the OpenAI TTS alternative hub — voice mapping, 5-line migration guide, and the breakeven pricing calculator for migrating off OpenAI's tts-1 / tts-1-hd.

The 5-line diff — Python

Existing OpenAI code uses the official openai Python SDK, which is hardcoded to api.openai.com for the audio.speech endpoint and doesn't support a custom base URL override. The pragmatic migration path is to swap the SDK call for a stdlib requests POST, which is 5 lines of code and removes one dependency. The before/after below is the literal diff — copy the 'after' block into your codebase, set EASYVOICE_API_KEY, and you're done.

The five changes: (1) Import requests instead of openai. (2) Change the URL from the SDK's implicit api.openai.com/v1/audio/speech to https://easyvoice.ae/api/tts/generate. (3) Set the Authorization header explicitly to Bearer ${EASYVOICE_API_KEY}. (4) Replace the voice name using the mapping table (alloy → af_alloy, etc.). (5) Drop the model parameter (tts-1 / tts-1-hd) — EasyVoice doesn't gate fidelity by model name, so the parameter is optional and ignored if sent. Everything else (response_format, input text, error handling pattern) stays identical.

The 5-line diff — JavaScript / TypeScript

JavaScript apps using the OpenAI Node SDK face the same constraint — the SDK is hardcoded to api.openai.com. Replace the openai.audio.speech.create() call with a fetch POST and the migration is the same 5 lines: import gone (fetch is global in Node 18+), URL changed, Authorization header set explicitly, voice name remapped, model parameter dropped. The arrayBuffer() → Buffer.from() pattern handles the audio response identically to how the OpenAI SDK exposes response.arrayBuffer() on its side.

For frameworks: Next.js Route Handlers should call EasyVoice server-side, not from the client, to avoid leaking the API key to the browser. Express, Hono, Fastify, and Bun's built-in HTTP server all use the same fetch pattern. If your existing OpenAI integration runs inside a Cloudflare Worker, Vercel Edge Function, or other edge runtime, the EasyVoice migration runs there too — no special edge-compatibility flags needed.

Model parameter mapping

OpenAI's API accepts model: 'tts-1' (faster, ~$15/1M chars) or model: 'tts-1-hd' (higher fidelity, ~$30/1M chars). EasyVoice runs Kokoro-82M as a single model serving all voices — fidelity isn't gated by a model name. If you send model: 'tts-1' or model: 'tts-1-hd' or model: 'kokoro' in your request body, EasyVoice ignores it gracefully (no 400 error, the request succeeds). This is intentional: it means existing OpenAI-shaped code with the model parameter still set works as-is, no defensive code needed.

If you want to be explicit, send model: 'kokoro' in the request. We accept it. We also accept model: 'tts-1' and model: 'tts-1-hd' for drop-in compatibility — you don't need to scrub those out of your codebase pre-migration. The point of accepting any model value is to remove a migration step. In blind A/B tests against OpenAI tts-1, Kokoro-82M is indistinguishable for prose-length narration. Against tts-1-hd, OpenAI's higher-fidelity model occasionally wins on close-mic'd character voice work; for the dominant TTS use cases (accessibility, chatbot, content audio versions, IVR), the difference is rarely perceptible.

Voice parameter mapping

The full 6-row mapping table lives on the /voices spoke. Inline reference: alloy → af_alloy (warm female mid-range, closest direct match), echo → am_echo (neutral male, news-reader register), fable → bm_fable (British male storytelling voice), onyx → am_onyx (deep American baritone, cinematic register), nova → af_nova (bright energetic female), shimmer → bf_lily (soft female with airy upper register, subtle British placement). For new code, write the mapping as a constant dictionary at the top of your TTS service file — see the code sample below.

If your existing code uses a runtime voice selector (different voice per user, different voice per content type, A/B testing voices for conversion), wrap the mapping in a function that takes an OpenAI voice name and returns the Kokoro equivalent. The wrapper is one function call wide; it doesn't add a measurable latency hop. After the migration is stable and you've shipped the EasyVoice voice names natively, you can delete the wrapper — but the wrapper is the safe path for the migration itself.

Response format compatibility — mp3, wav, opus

Both APIs accept response_format: 'mp3' (default), 'wav', or 'opus'. The byte-level output is functionally identical: an mp3 file from OpenAI plays in the same player as an mp3 file from EasyVoice, with the same MIME type (audio/mpeg) and the same downstream library compatibility. The difference is encoding parameters — OpenAI's tts-1 mp3 is 96 kbps, tts-1-hd is 128 kbps; EasyVoice's mp3 is 128 kbps. Files are slightly different sizes for the same audio length, but bit-for-bit content compares as you'd expect.

wav is the right choice for downstream audio processing pipelines — mixing into a podcast track, applying filters in Audacity, importing into a DAW. opus is the right choice for real-time streaming over WebRTC or other latency-sensitive transport where mp3's frame structure handles packet loss less gracefully. Both vendors return raw audio bytes for every format — there's no JSON envelope to parse, no base64 to decode. response.arrayBuffer() (JS), response.content (Python requests), or response.body in any other language returns the audio directly.

Streaming chunks — chunked transfer-encoding

Both APIs stream audio via HTTP chunked transfer-encoding. The response opens with Transfer-Encoding: chunked and audio bytes flow as the model generates them — typical chunk size is 4-16KB, corresponding to 200-800ms of playback audio. Any standard HTTP library handles this without configuration: fetch in Node/browser exposes response.body as a ReadableStream, requests with stream=True in Python supports it, Go's http.Client streams by default. You don't need WebSockets or gRPC for low-latency TTS.

If your existing OpenAI integration uses streaming (response = client.audio.speech.create(..., stream=True) in Python, or fetch + response.body.getReader() in JS), the EasyVoice equivalent is the same pattern with the URL and voice changed. First-byte latency is typically lower on EasyVoice (300-600ms warm vs OpenAI tts-1's 800ms-1.5s), so the perceived 'time-to-first-audio' improves with the migration — measurable in conversational UX where the user perceives that delta as 'responsiveness.'

What stays the same — auth, error handling, caching

Bearer token authentication: identical. Both APIs accept Authorization: Bearer YOUR_KEY in the request header. Your existing pattern (env var → request header) works as-is, just with EASYVOICE_API_KEY in place of OPENAI_API_KEY. Both keys are rotatable from the respective vendor dashboards; if you need both vendors active during migration (e.g. existing-user cohort on OpenAI, new-user cohort on EasyVoice), keep both env vars set and route at the call site.

Error handling: HTTP 401 (invalid key), 400 (malformed request), 429 (rate limit), 5xx (server error). Same status codes mean the same things on both APIs. Caching: if your app caches generated audio by (text, voice, format) hash, the cache works identically — the audio bytes returned are deterministic for the same input across requests, so cache hit rates don't change after migration. The full migration commit usually touches 1-3 files: the TTS service module, the env var schema (.env.example), and the deployment config that sets EASYVOICE_API_KEY.

Code samples

Real working code, not pseudo-code. Every request below assumes you've set EASYVOICE_API_KEY and OPENAI_API_KEY as env vars where shown.

Before — OpenAI Python

Existing openai SDK call
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

response = client.audio.speech.create(
    model="tts-1",
    voice="alloy",
    input="Hello, this is OpenAI's TTS API.",
    response_format="mp3",
)
with open("out.mp3", "wb") as f:
    f.write(response.content)

After — EasyVoice Python

stdlib requests, 5 lines changed
import os, requests
res = requests.post(
    "https://easyvoice.ae/api/tts/generate",            # was api.openai.com/v1/audio/speech
    headers={"Authorization": f"Bearer {os.environ['EASYVOICE_API_KEY']}"},
    json={
        "voice": "af_alloy",                            # was "alloy"
        "input": "Hello, this is EasyVoice TTS API.",
        "response_format": "mp3",
    },
)
with open("out.mp3", "wb") as f:
    f.write(res.content)

Before — OpenAI JavaScript

Existing openai Node SDK call
import OpenAI from "openai";
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

const response = await client.audio.speech.create({
  model: "tts-1",
  voice: "alloy",
  input: "Hello, this is OpenAI's TTS API.",
  response_format: "mp3",
});
const buffer = Buffer.from(await response.arrayBuffer());
require("fs").writeFileSync("out.mp3", buffer);

After — EasyVoice JavaScript

fetch in Node 18+, 5 lines changed
const res = await fetch("https://easyvoice.ae/api/tts/generate", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.EASYVOICE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    voice: "af_alloy",                                  // was "alloy"
    input: "Hello, this is EasyVoice TTS API.",
    response_format: "mp3",
  }),
});
const buffer = Buffer.from(await res.arrayBuffer());
require("fs").writeFileSync("out.mp3", buffer);

Voice map — both languages

Drop into a constants file at the top of your TTS service module
# Python — voice_map.py
OPENAI_TO_EASYVOICE = {
    "alloy":   "af_alloy",
    "echo":    "am_echo",
    "fable":   "bm_fable",
    "onyx":    "am_onyx",
    "nova":    "af_nova",
    "shimmer": "bf_lily",
}

# JavaScript — voiceMap.ts
# export const OPENAI_TO_EASYVOICE = {
#   alloy: "af_alloy",
#   echo: "am_echo",
#   fable: "bm_fable",
#   onyx: "am_onyx",
#   nova: "af_nova",
#   shimmer: "bf_lily",
# } as const;

Streaming — Python with stream=True

First-byte arrives in ~380ms warm on EasyVoice; ~900ms on OpenAI tts-1
import os, time, requests
with requests.post(
    "https://easyvoice.ae/api/tts/generate",
    headers={"Authorization": f"Bearer {os.environ['EASYVOICE_API_KEY']}"},
    json={"voice": "af_alloy", "input": "Streaming hello.", "response_format": "mp3"},
    stream=True,
) as res:
    start = time.time()
    first_byte_at = None
    for chunk in res.iter_content(chunk_size=4096):
        if first_byte_at is None:
            first_byte_at = (time.time() - start) * 1000
            print(f"First-byte latency: {first_byte_at:.0f}ms")
        # pipe chunk into pyaudio / ffmpeg / wherever
        audio_buffer.write(chunk)

Streaming — JavaScript fetch + ReadableStream

Same chunked transfer-encoding pattern as OpenAI
const res = await fetch("https://easyvoice.ae/api/tts/generate", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.EASYVOICE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    voice: "af_alloy",
    input: "Streaming hello.",
    response_format: "mp3",
  }),
});
const reader = res.body.getReader();
const start = performance.now();
let firstByteAt = null;
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  if (firstByteAt === null) {
    firstByteAt = performance.now() - start;
    console.log(`First-byte latency: ${firstByteAt.toFixed(0)}ms`);
  }
  audioQueue.push(value);
}

Voices to try on the free tier

Every voice below is callable via the same voice parameter — preview audio samples and read the full character profile.

Alloy
American English · af_alloy
Onyx
American English · am_onyx
Fable
British English · bm_fable

Frequently asked questions

How many lines of code does an OpenAI TTS to EasyVoice migration take?▾

Five. Change the import (openai → requests / fetch), change the URL (api.openai.com/v1/audio/speech → easyvoice.ae/api/tts/generate), change the Authorization header to use EASYVOICE_API_KEY, change the voice parameter (alloy → af_alloy and the rest of the mapping table), and drop the model parameter (optional, ignored if sent). Everything else — response handling, error codes, response_format options, streaming pattern — stays identical.

Can I keep using the openai Python or Node SDK?▾

No — the official openai SDKs are hardcoded to api.openai.com for the audio.speech endpoint and don't expose a custom base URL override. The pragmatic path is to swap the SDK call for stdlib HTTP (requests in Python, fetch in JS), which is the 5-line diff this page covers. Removing the dependency is usually a net positive — one fewer thing to version-pin and upgrade.

What about the model parameter — tts-1 vs tts-1-hd?▾

Drop it. EasyVoice runs Kokoro-82M as a single model for all voices — fidelity isn't gated by a model name. If you send model: 'tts-1' or 'tts-1-hd' or 'kokoro' anyway, EasyVoice ignores it gracefully (no 400 error). This is intentional drop-in compatibility — your existing OpenAI-shaped code with the model parameter set still works as-is, no defensive code changes needed pre-migration.

Are mp3 / wav / opus response formats compatible?▾

Yes. Both APIs return raw audio bytes (not JSON-wrapped, not base64-encoded) for response_format mp3, wav, or opus. An mp3 from OpenAI plays in the same player as an mp3 from EasyVoice. Slight bitrate differences (OpenAI tts-1 is 96 kbps, EasyVoice mp3 is 128 kbps) mean file sizes differ slightly for the same audio length, but downstream library compatibility is identical.

Does streaming work the same way?▾

Yes — HTTP chunked transfer-encoding on both APIs. fetch with response.body.getReader() in JS, requests with stream=True in Python, Go's http.Client by default. Typical chunk size 4-16KB, corresponding to 200-800ms of playback audio. First-byte latency is typically lower on EasyVoice (300-600ms warm vs OpenAI tts-1's 800ms-1.5s), so perceived 'time-to-first-audio' improves after migration.

How do I run both vendors during migration?▾

Keep both env vars set (OPENAI_API_KEY and EASYVOICE_API_KEY), route at the call site by user cohort or feature flag. Common patterns: cohort by user creation date (existing users keep OpenAI, new users go EasyVoice), feature flag per user, or content-type routing (chatbot responses go EasyVoice for cost, brand voiceover stays OpenAI temporarily). Both keys can rotate independently.

Related OpenAI migration guides

OpenAI TTS voices, mapped to free Kokoro alternatives

Map OpenAI's 6 voices (alloy, echo, fable, onyx, nova, shimmer) to closest EasyVoice Kokoro voices. Side-by-side tones, when-to-use, sample scripts. Free.

OpenAI TTS pricing vs EasyVoice — when flat-rate wins

OpenAI TTS pricing calculator. tts-1 $15/1M, tts-1-hd $30/1M vs EasyVoice $9.99 flat. Breakeven 666K chars/mo. Real numbers at 100K, 500K, 1M, 5M, 10M.

Vendor comparison: EasyVoice vs OpenAI TTS

Side-by-side feature comparison covering voices, languages, pricing tiers, free limits, API surface, and the why-people-look / where-each-wins breakdown.

Developer-focused OpenAI migration in /tts-api

The developer-onboarding angle of the same migration — request body compatibility deep-dive, streaming behavior, ChatGPT plugin/Realtime API guidance, and the official OpenAI SDK constraint.

Start migrating off OpenAI TTS today

5,000 characters per day free, no credit card. Pro $9.99/mo unlimited replaces OpenAI's $15-$300/mo bills once you cross 666K characters per month.

More OpenAI alternative guides

← OpenAI alternative hubOpenAI TTS voices, mapped to free Kokoro alternativesOpenAI TTS pricing vs EasyVoice — when flat-rate winsTTS API hub →