API Quick Start

Create a key, pick a voice and generate your first audio.

Create an API key

Sign in, open API Keys, click "New key" and copy the key that starts with sk-. It is shown only once.

Save it as an environment variable - the examples below use it:

export VAL_API_KEY="sk-your-api-key"

Check your account

curl https://voiceailabs.com/api/v1/account \
  -H "Authorization: Bearer $VAL_API_KEY"

credits.balance is your available credits, and limits.max_text_length is the maximum characters per synthesis.

Pick a voice

Use a public voice from the Voice Square (it doesn't use a voice slot) or one of your cloned voices:

# Public voices
curl "https://voiceailabs.com/api/v1/voices/public?limit=5" \
  -H "Authorization: Bearer $VAL_API_KEY"

# Your voices
curl "https://voiceailabs.com/api/v1/voices" \
  -H "Authorization: Bearer $VAL_API_KEY"

Note a voice's id - you'll pass it as voice_id.

Create a task

curl https://voiceailabs.com/api/v1/tts \
  -H "Authorization: Bearer $VAL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1024-intro" \
  -d '{
    "voice_id": "VOICE_ID",
    "text": "Welcome to Voice AI Labs.",
    "language": "en"
  }'

You get 202 and a task with status: "pending". The Idempotency-Key makes network retries safe - no duplicate tasks or charges. See Errors & Limits.

Get the audio

Poll every few seconds until status is completed or failed:

curl https://voiceailabs.com/api/v1/syntheses/SYNTHESIS_ID \
  -H "Authorization: Bearer $VAL_API_KEY"

Download the MP3 from audio_url. In production, use a webhook instead of polling.

Complete example

const BASE_URL = "https://voiceailabs.com/api/v1";
const headers = { Authorization: `Bearer ${process.env.VAL_API_KEY}` };

async function call(path, init = {}) {
  const response = await fetch(`${BASE_URL}${path}`, {
    ...init,
    headers: { ...headers, ...init.headers },
  });
  const body = await response.json();
  if (body.code !== 0) {
    const error = new Error(body.message);
    Object.assign(error, body.data); // error_code, retryable, request_id
    throw error;
  }
  return body.data;
}

async function textToSpeech(voiceId, text) {
  let task = await call("/tts", {
    method: "POST",
    headers: { "Content-Type": "application/json", "Idempotency-Key": crypto.randomUUID() },
    body: JSON.stringify({ voice_id: voiceId, text }),
  });

  while (task.status === "pending" || task.status === "processing") {
    await new Promise((resolve) => setTimeout(resolve, 3000));
    task = await call(`/syntheses/${task.id}`);
  }
  if (task.status === "failed") {
    throw new Error(`Synthesis failed, refund: ${task.refund?.status}`);
  }
  return task.audio_url;
}

textToSpeech("VOICE_ID", "Hello from Voice AI Labs!").then(console.log);
import os
import time
import uuid

import requests

BASE_URL = "https://voiceailabs.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['VAL_API_KEY']}"}


def call(method, path, **kwargs):
    headers = {**HEADERS, **kwargs.pop("headers", {})}
    body = requests.request(method, f"{BASE_URL}{path}", headers=headers, timeout=60, **kwargs).json()
    if body["code"] != 0:
        raise RuntimeError(f"{body['data']['error_code']}: {body['message']} ({body['data']['request_id']})")
    return body["data"]


def text_to_speech(voice_id, text):
    task = call(
        "POST",
        "/tts",
        json={"voice_id": voice_id, "text": text},
        headers={"Idempotency-Key": str(uuid.uuid4())},
    )
    while task["status"] in ("pending", "processing"):
        time.sleep(3)
        task = call("GET", f"/syntheses/{task['id']}")
    if task["status"] == "failed":
        raise RuntimeError(f"Synthesis failed, refund: {task['refund']}")
    return task["audio_url"]


print(text_to_speech("VOICE_ID", "Hello from Voice AI Labs!"))

Next steps