Text to Speech
← Back to dashboard

API documentation

Generate speech programmatically using an API key tied to your account. API requests use the exact same credits and the exact same admin-configured access window as the dashboard.

On this page

Authentication & API keys Access-time restrictions Endpoints Example requests Error responses Rate limits & other restrictions

Authentication & API keys

Every request to the API must include your API key in the X-API-Key header. This is separate from the session token your browser uses when you're logged into the dashboard.

Generating a key

  1. Log into the dashboard and open the API tab in the right-hand rail.
  2. Click Generate API key. The full key is shown exactly once — copy it somewhere safe immediately.
  3. Use that key as the value of the X-API-Key header on every request.
One active key per account. You can only have a single active API key at a time. If you already have one, generating a new key will fail — you must delete the existing key first (from the API tab, or via DELETE /api/keys) before creating a replacement. This is enforced by the backend, so it can't be worked around by calling the endpoint directly.

Revoking a key

Delete your key at any time from the dashboard's API tab, or by calling DELETE /api/keys with your normal dashboard session. Once deleted, that key immediately stops working for all API requests — you can then generate exactly one new key.

Access-time restrictions

Your account has an admin-configured allowed usage window, defined in Indian Standard Time (IST, UTC+5:30). This window applies identically to the dashboard and to the API — your API key inherits its permissions and time restrictions directly from your account, so there's no separate or conflicting restriction to configure. Requests made outside your allowed window are rejected by the backend, not just blocked in the UI.

If you're unsure of your current window, check the credits/status line on your dashboard, or ask an administrator.

Endpoints

Base URL: the same origin your dashboard is served from, e.g. http://localhost:4000.

MethodPathDescription
POST/api/v1/generateSubmit text for speech generation. Returns immediately with a request ID; generation happens asynchronously.
GET/api/v1/status/:requestIdCheck the status of a generation request.
GET/api/v1/audio/:requestIdDownload the generated audio once status is generated. This is also the moment credits are charged.

POST /api/v1/generate

Headers:

HeaderValue
X-API-KeyYour API key (required)
Content-Typeapplication/json

Body parameters:

FieldTypeRequiredDescription
textstringYesText to convert to speech. Up to 5,000 characters.
voiceIdstringYesThe voice to use.
clientRequestIdstringNoOptional idempotency key. Resubmitting the same value returns the existing request instead of creating a duplicate/double-charging.

Example requests

bash
curl -X POST http://localhost:4000/api/v1/generate \
  -H "X-API-Key: sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Hello from the API!",
    "voiceId": "your-voice-id"
  }'
bash — check status
curl http://localhost:4000/api/v1/status/REQUEST_ID \
  -H "X-API-Key: sk_live_your_key_here"
bash — download audio
curl http://localhost:4000/api/v1/audio/REQUEST_ID \
  -H "X-API-Key: sk_live_your_key_here" \
  -o output.mp3
python
import time
import requests

API_BASE = "http://localhost:4000"
API_KEY = "sk_live_your_key_here"
headers = {"X-API-Key": API_KEY}

# 1. Submit the generation request
resp = requests.post(
    f"{API_BASE}/api/v1/generate",
    headers=headers,
    json={"text": "Hello from the API!", "voiceId": "your-voice-id"},
)
data = resp.json()
if not data.get("success"):
    raise RuntimeError(data.get("error"))

request_id = data["request"]["requestId"]

# 2. Poll until generation finishes
while True:
    status_resp = requests.get(f"{API_BASE}/api/v1/status/{request_id}", headers=headers)
    status_data = status_resp.json()
    job_status = status_data["request"]["jobStatus"]
    if job_status == "generated":
        break
    if job_status == "failed":
        raise RuntimeError(status_data["request"].get("errorMessage"))
    time.sleep(1)

# 3. Download the audio (this is also when credits are charged)
audio_resp = requests.get(f"{API_BASE}/api/v1/audio/{request_id}", headers=headers)
with open("output.mp3", "wb") as f:
    f.write(audio_resp.content)
javascript
const API_BASE = "http://localhost:4000";
const API_KEY = "sk_live_your_key_here";
const headers = { "X-API-Key": API_KEY };

async function generateSpeech(text, voiceId) {
  // 1. Submit the generation request
  const genRes = await fetch(`${API_BASE}/api/v1/generate`, {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({ text, voiceId }),
  });
  const genData = await genRes.json();
  if (!genData.success) throw new Error(genData.error);

  const requestId = genData.request.requestId;

  // 2. Poll until generation finishes
  let jobStatus = genData.request.jobStatus;
  while (jobStatus !== "generated") {
    await new Promise((r) => setTimeout(r, 1000));
    const statusRes = await fetch(`${API_BASE}/api/v1/status/${requestId}`, { headers });
    const statusData = await statusRes.json();
    jobStatus = statusData.request.jobStatus;
    if (jobStatus === "failed") throw new Error(statusData.request.errorMessage);
  }

  // 3. Download the audio (this is also when credits are charged)
  const audioRes = await fetch(`${API_BASE}/api/v1/audio/${requestId}`, { headers });
  return Buffer.from(await audioRes.arrayBuffer());
}

generateSpeech("Hello from the API!", "your-voice-id")
  .then((audioBuffer) => require("fs").writeFileSync("output.mp3", audioBuffer))
  .catch(console.error);

Example successful response — POST /api/v1/generate

json
{
  "success": true,
  "request": {
    "requestId": "b3e1c2a0-...",
    "userId": "6f2a...",
    "voiceId": "your-voice-id",
    "characterCount": 20,
    "jobStatus": "queued",
    "audioUrl": null,
    "createdAt": "2026-09-08T09:15:00.000Z"
  },
  "backendStatus": {
    "isRefreshing": false,
    "secondsRemaining": 0,
    "canTakeJob": true,
    "workerOnline": true,
    "workersAvailable": 1,
    "totalRemaining": 10000,
    "message": null
  }
}

Error responses

All API errors use the same envelope: { "success": false, "error": "..." }, with additional fields where noted below.

Authentication errors

json — missing key (401)
{
  "success": false,
  "error": "Missing API key. Provide it via the X-API-Key header."
}

Invalid API key

json — invalid key (401)
{
  "success": false,
  "error": "Invalid API key."
}

Time restriction errors

Returned with HTTP status 403 when a request arrives outside your account's allowed IST access window:

json — outside access window (403)
{
  "success": false,
  "error": "API access is not authorized at this time.",
  "allowed_time": "9:00 AM to 6:00 PM IST"
}

Other errors

StatusMeaning
400Invalid request body (missing/too-long text, invalid voice ID, etc).
402Insufficient credits for the requested text length.
403Account disabled, or outside the allowed access window.
404Request ID not found, doesn't belong to your account, or audio isn't ready yet.
429Rate limit exceeded — see below.

Rate limits & other restrictions