Developers

VoiceoverWise REST API

Generate voiceovers from your own app or pipeline, with your pronunciation dictionary and smart reading applied. Available on every account.

Authentication

Create an API key in Dashboard → Settings → API key. Keys start with vw_live_ and are shown once. Send the key as a bearer token and JSON bodies with Content-Type: application/json:

curl https://voiceoverwise.com/api/v1/voices \
  -H "Authorization: Bearer vw_live_your_key"

Rate limits & errors

Up to 60 requests per minute per key. Errors return a JSON body {"error": "message"} with a standard HTTP status: 400 invalid input, 401 invalid key, 402 not enough credits, 404 not found, 409 not ready yet, 410 audio expired (files are kept 30 days), 429 rate limited, 503 generation temporarily unavailable.

Voices

GET/api/v1/voices

Lists voices (id, name, tone, gender, best for) and delivery styles (id, name).

{ "voices": [ { "id": "nova", "name": "Nova", "tone": "Bright and friendly", … } ],
  "styles": [ { "id": "natural", "name": "Natural narrator" }, …, { "id": "custom", "name": "Custom direction" } ] }

Speech

POST/api/v1/speech

Creates a voiceover. Credits equal to the script's character count are reserved immediately and returned if generation fails. Returns 202; generation usually takes a few seconds per minute of audio.

{
  "text": "Plans start at $29. [pause] Call (816) 555-0123.",   // required, up to 20,000 chars
  "voice": "nova",          // see /voices
  "style": "natural",       // or "custom" with "direction": "calm, slow, like a museum guide"
  "pace": "normal",         // slow | normal | fast
  "format": "mp3",          // mp3 | wav
  "smart": true,            // smart reading on/off
  "title": "Pricing intro"  // optional, used for file names
}
GET/api/v1/speech/{id}

Status and details. When status is done, audioUrl and subtitlesUrl are set (they need the same bearer token).

{ "speech": { "id": "clp_…", "status": "done", "chars": 49, "credits": 49,
  "durationMs": 4210, "format": "mp3", "audioUrl": "https://voiceoverwise.com/api/v1/speech/clp_…/audio",
  "subtitlesUrl": "https://voiceoverwise.com/api/v1/speech/clp_…/subtitles.srt", "expiresAt": "…" } }
GET/api/v1/speech/{id}/audio

The audio file (audio/mpeg or audio/wav). Supports range requests.

GET/api/v1/speech/{id}/subtitles.srt

The SRT subtitle file.

GET/api/v1/speech?limit=30&offset=0

Your most recent voiceovers.

Batches

POST/api/v1/batches

Up to 200 scripts with shared settings, each up to 3,000 characters. Pass rows (array of {"title","text"}), csv (with "title" and "text" columns) or lines (one script per line), plus the same voice settings as /speech.

{ "name": "Product audio", "voice": "coral", "rows": [ { "title": "earbuds", "text": "…" } ] }
GET/api/v1/batches/{id}

Progress and every clip in the batch, plus zipUrl once files are available.

GET/api/v1/batches/{id}/download.zip

All finished audio files and their subtitles in one ZIP.

Quickstart

const KEY = process.env.VOICEOVERWISE_API_KEY;
const api = (path, opts = {}) => fetch('https://voiceoverwise.com/api/v1' + path, {
  ...opts, headers: { Authorization: 'Bearer ' + KEY, 'Content-Type': 'application/json' }
});

const { speech } = await (await api('/speech', {
  method: 'POST',
  body: JSON.stringify({ text: 'Welcome to our store!', voice: 'coral', style: 'warm' })
})).json();

let s = speech;
while (s.status === 'queued' || s.status === 'processing') {
  await new Promise(r => setTimeout(r, 2000));
  s = (await (await api('/speech/' + s.id)).json()).speech;
}
if (s.status === 'done') {
  const audio = Buffer.from(await (await api('/speech/' + s.id + '/audio')).arrayBuffer());
  require('fs').writeFileSync('welcome.mp3', audio);
}

Credits

GET/api/v1/credits

Current balance, split into free and purchased credits, with expiry dates.

{ "balance": 284500, "free": 10000, "purchased": 274500, "lots": [ … ] }

Use of the API is subject to the Terms of Service and the Acceptable Use Policy. See pricing for credit packs.