Ready to use the API?
Free account — no credit card required. Get an API key in 30 seconds.
Using an AI agent (Claude, ChatGPT, Cursor)? Connect over MCP instead → easyvoice.ae/mcp
OpenAI-compatible Text-to-Speech API. Drop-in replacement — change your base URL and API key. Free keys work today: 5,000 characters/day, 12 voices, no card. Pro ($9.99/mo) unlocks all 66 voices, cloning, and unlimited characters.
curl -X POST https://easyvoice.ae/api/v1/audio/speech \
-H "Authorization: Bearer ev_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"model": "kokoro-82m",
"input": "Hello, this is EasyVoice!",
"voice": "af_aoede"
}' \
--output speech.mp3This runs as-is with a free key — af_aoede is one of the 12 free voices. Create a free account, generate a key on the API keys page, paste it in. No card.
Prefer tools over raw HTTP? EasyVoice is also a hosted MCP server — speech, long-form narration, podcast episodes, and voice cloning from any MCP-capable agent, authenticated with these same ev_ keys. Connection instructions for Claude, ChatGPT, and Cursor live at /mcp; the TTS MCP server explainer covers what agents can do with it.
Same engine and voices as /api/v1/audio/speech — try it in the browser before you create a key. 2,000 free characters a day, no signup.
Generate speech from text. Returns audio file directly.
| Header | Value |
|---|---|
| Authorization | Bearer ev_your_api_key |
| Content-Type | application/json |
| Parameter | Type | Required | Description |
|---|---|---|---|
| model | string | No | Always "kokoro-82m" |
| input | string | Yes | Text to convert. Free: up to 5,000 chars (your daily pool). Pro: up to 50,000 per request. |
| voice | string | No | Voice ID (default: af_aoede). Free keys: the 12 free voices. Pro: all 66 + cloned voices. |
| response_format | string | No | "mp3" or "wav" (default: mp3) |
| speed | number | No | 0.5 to 2.0 (default: 1.0) |
EasyVoice adds optional audio controls on top of the OpenAI-compatible request. On the public POST /v1/audio/speech endpoint these use an ev_ prefix. All are optional and default to a no-op, so standard OpenAI clients that omit them receive identical output to before. These are deterministic audio/voicing controls (pitch shift, output gain, and EQ tone presets) — not generative effects.
| Parameter | Type | Required | Description |
|---|---|---|---|
| ev_pitch | number | No | Pitch shift in semitones. Range -4 to +4. Default: 0 (no shift). |
| ev_volume_db | number | No | Output gain in dB. Range -6 to +6. Default: 0 (no change). |
| ev_tone | string | No | EQ tone preset: "neutral" (default), "warm", "bright", or "bass". |
| <break> in input | markup | No | Insert silence: embed <break time="500ms"/> or <break time="0.5s"/> in the input text. Max 3000ms per break. |
On the web app (POST /api/tts/generate) the same controls are sent without the prefix: pitch, volume_db, and tone (same ranges and defaults). Out-of-range values are rejected with 400.
curl -X POST https://easyvoice.ae/api/v1/audio/speech \
-H "Authorization: Bearer ev_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"model": "kokoro-82m",
"input": "Take a breath. <break time=\"500ms\"/> Now continue.",
"voice": "af_aoede",
"ev_pitch": 2,
"ev_volume_db": -1.5,
"ev_tone": "warm"
}' \
--output speech.mp3from openai import OpenAI
client = OpenAI(
api_key="ev_your_api_key",
base_url="https://easyvoice.ae/api/v1"
)
response = client.audio.speech.create(
model="kokoro-82m",
voice="af_aoede",
input="Hello from EasyVoice!"
)
response.stream_to_file("output.mp3")import OpenAI from "openai";
const client = new OpenAI({
apiKey: "ev_your_api_key",
baseURL: "https://easyvoice.ae/api/v1"
});
const mp3 = await client.audio.speech.create({
model: "kokoro-82m",
voice: "af_aoede",
input: "Hello from EasyVoice!"
});
const buffer = Buffer.from(await mp3.arrayBuffer());
await fs.promises.writeFile("output.mp3", buffer);| Limit | Free | Pro ($9.99/mo) |
|---|---|---|
| Characters | 5,000/day, shared with the web app. Resets midnight UTC. | Unlimited — fair use 10M API chars/mo ≈ 180+ hours of audio |
| Requests per minute | 20 | 60 |
| Concurrent requests | 2 | No per-key cap |
| Max input per request | 5,000 characters | 50,000 (Kokoro) · 12,000 (Arabic) · 2,000 (cloned) — longer via the Jobs API |
| Voices | 12 free voices | All 66 + your cloned voices |
Fair use, in plain words: Pro is unlimited for real products and real audiences — narrate your videos, your app, your audiobooks without counting characters. 10M API characters a month is roughly 180+ hours of finished audio; past that we may throttle to protect other users before we ever bill you. No overage charges, ever. Reselling raw API capacity is the one thing fair use excludes.
When you hit a limit the API returns a structured 429 with a Retry-After header: rate_limit_exceeded (per-minute window), concurrency_limit_exceeded (too many in flight), or daily_quota_exceeded (free daily pool spent — reset_at in the body tells you when it refills). Requesting a Pro voice with a free key returns 403 plan_required naming the voice.
66 voices across 9 languages. Use the voice ID in your API requests.
Free API keys include 12 voices: af_aoede, af_heart, af_bella, af_nova, af_sky, am_adam, am_eric, am_michael, bf_emma, bm_daniel, plus Arabic ar_m1 and ar_f1. The other 54 return 403 plan_required on a free key.
See the voice browser for the full list with audio previews.
Arabic voices: ar_m1–ar_m5 and ar_f1–ar_f5 work directly in POST /v1/audio/speech. Arabic numerals, dates, and AED amounts are normalized to spoken form automatically. See Arabic text to speech.
Pro subscribers can also use cloned voice IDs (e.g. voice_abc123) returned by the GET /v1/voices endpoint.
Enroll, list, and delete custom voice clones. Cloned voice IDs can be used directly in POST /v1/audio/speech requests. Requires a Pro subscription and explicit consent for each enrolled voice.
Multipart form upload. Returns 202 with {voice_id, status:"enrolling"}. Enrollment typically completes in 1–2 minutes; poll GET /v1/voices for status.
curl -X POST https://easyvoice.ae/api/v1/voices \ -H "Authorization: Bearer ev_your_api_key" \ -F "name=My Voice" \ -F "consent=true" \ -F "audio=@sample.wav"
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Display name for the cloned voice |
| consent | string | Yes | Must be "true" — attests you own/have consent to clone this voice |
| audio | file | Yes | WAV, MP3, MP4, or M4A. 15–60 seconds of clear speech recommended. |
Returns all cloned voices for the authenticated user. Use status === "ready" voices in speech requests.
curl https://easyvoice.ae/api/v1/voices \
-H "Authorization: Bearer ev_your_api_key"
# Response:
# {
# "voices": [
# { "id": "voice_abc123", "name": "My Voice", "status": "ready", "createdAt": 1234567890 }
# ]
# }For long inputs, submit an asynchronous job instead of waiting on the synchronous endpoint. Jobs are queued, processed, and the result is fetched by polling. Works with any voice ID, including Arabic and cloned voices. API-key access to the Jobs API requires Pro — free keys use the synchronous /v1/audio/speech endpoint above.
curl -X POST https://easyvoice.ae/api/v1/jobs \
-H "Authorization: Bearer ev_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"input": "A long article text...",
"voice": "af_aoede",
"format": "mp3"
}'
# 202 Accepted
# { "id": "1f6f7c2e-...", "status": "queued" }curl https://easyvoice.ae/api/v1/jobs/1f6f7c2e-... \
-H "Authorization: Bearer ev_your_api_key"
# { "id": "1f6f7c2e-...", "status": "completed",
# "audio_url": "/audio/....mp3", "created_at": ..., "completed_at": ... }Statuses: queued → active → completed / failed. Jobs are visible only to the account that created them.
Generate two-host dialogue podcasts from a list of script segments. Submit a job via POST /api/v1/jobs with type:"podcast", then poll for the stitched result. Per-segment audio URLs are returned alongside the final episode URL.
curl -X POST https://easyvoice.ae/api/v1/jobs \
-H "Authorization: Bearer ev_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"type": "podcast",
"voices": { "A": "af_aoede", "B": "am_echo" },
"format": "mp3",
"segments": [
{ "speaker": "A", "text": "Welcome to the show. Today we explore the future of AI." },
{ "speaker": "B", "text": "Thanks for having me. AI is evolving at a remarkable pace." },
{ "speaker": "A", "text": "Absolutely. What do you think the next five years will look like?" },
{ "speaker": "B", "text": "I expect multimodal models to become the default in most apps." }
]
}'
# 202 Accepted
# { "id": "3a9f1b7c-...", "status": "queued" }curl https://easyvoice.ae/api/v1/jobs/3a9f1b7c-... \
-H "Authorization: Bearer ev_your_api_key"
# {
# "id": "3a9f1b7c-...",
# "status": "completed",
# "audio_url": "/audio/episode-3a9f1b7c.mp3",
# "segments": [
# { "speaker": "A", "audioUrl": "/audio/seg-0.mp3" },
# { "speaker": "B", "audioUrl": "/audio/seg-1.mp3" },
# { "speaker": "A", "audioUrl": "/audio/seg-2.mp3" },
# { "speaker": "B", "audioUrl": "/audio/seg-3.mp3" }
# ],
# "error": null,
# "created_at": 1749500000000,
# "completed_at": 1749500045000
# }| Plan | Max chars / episode | Cloned hosts |
|---|---|---|
| Free | 2,000 | No |
| Pro | 30,000 | Yes — use IDs |
ev_timestampsPassing ev_timestamps: true changes the response from raw audio bytes to a JSON envelope: { audio: <base64>, format, words: [{word, start, end}], timestamps_available }. Available for Kokoro voices only.
// Request
{
"model": "kokoro-82m",
"input": "Hello world.",
"voice": "af_aoede",
"ev_timestamps": true
}
// Response (JSON envelope — not raw audio bytes)
{
"audio": "<base64-encoded mp3>",
"format": "mp3",
"words": [
{ "word": "Hello", "start": 0.042, "end": 0.312 },
{ "word": "world", "start": 0.312, "end": 0.630 }
],
"timestamps_available": true
}Spoken-form policy: captions show the spoken form — numbers and abbreviations appear as read aloud, matching the audio. For example, "$9.99" becomes "nine dollars and ninety-nine cents" in the caption cues.
Scope: not available for Arabic (ar_*) or cloned (voice_*) voices — those requests return 400 timestamps_not_supported. When omitted or false, the endpoint returns raw audio bytes unchanged (fully backward-compatible).
Embed <break time="500ms"/> (or <break time="0.5s"/>) anywhere in the input text to insert a silent pause. Accepts milliseconds (ms) or seconds (s), capped at 3000ms per break.
Embed [word](/phonemes/) inline in your input text to control pronunciation using misaki IPA notation. Example: The [Louisville](/lˈuːɪvɪl/) skyline.
Permanently deletes the voice clone and its embedding. Returns 200 on success.
curl -X DELETE https://easyvoice.ae/api/v1/voices/voice_abc123 \ -H "Authorization: Bearer ev_your_api_key"
Pass the voice_* ID as the voice field in any speech request. Pro required.
curl -X POST https://easyvoice.ae/api/v1/audio/speech \
-H "Authorization: Bearer ev_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"model": "kokoro-82m",
"input": "Hello, this is my cloned voice!",
"voice": "voice_abc123"
}' \
--output speech.mp3voice_*Use standard voice IDs (e.g. af_aoede, am_echo) for hosts A and B. Pro subscribers can assign cloned voice_* IDs returned by GET /v1/voices.