Get API key

Uncensored OpenAI-compatible API for developers.

AI Voice Script Generator API: Build One With an LLM

An ai voice script generator api takes a brief and returns text written to be read aloud. With an OpenAI-compatible endpoint you can build one in under fifty lines, and the audio step stays with whatever speech tool you already use.

Updated

Script cards moving along a conveyor toward a studio microphone, picturing a script generator feeding voice tools

The generator in five steps

Before any code, fix the shape of the job. A voice script generator is a loop around one chat call, and each step has a plain purpose:

  1. Collect a brief: topic, voices, tone, length, forbidden words.
  2. Send the brief and a fixed system prompt to the chat endpoint.
  3. Receive the script, ideally as structured turns.
  4. Clean it: strip markup, expand numbers, split into chunks your speech tool accepts.
  5. Hand the chunks to the TTS tool, which this API does not replace.

That last point deserves a plain sentence. The service behind this site is text only. There is no audio output, no voice cloning and no speech endpoint. It is an OpenAI-compatible host for one open-weight model that is tuned to answer without content refusals for lawful adult use, which makes it a fit for the writing stage of fiction, roleplay and companion-style audio where a filtered model keeps interrupting the script.

Set up the client

Use the standard OpenAI Python package and change two values. The base URL is https://api.vibevoice.top/v1 and the model ID is uncensored. Your key goes in as a bearer token, which the SDK does for you.

from openai import OpenAI

client = OpenAI(
    base_url="https://api.vibevoice.top/v1",
    api_key="YOUR_KEY",
    max_retries=4,
)

def write_script(brief, system, **opts):
    resp = client.chat.completions.create(
        model="uncensored",
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": brief},
        ],
        **opts,
    )
    return resp.choices[0].message.content, resp.usage

The SDK retries on 429 by default and honours the Retry-After header, so raising max_retries is usually the only rate-limit code you write. If you have no key yet, the key page explains sign-up and the one-click trial. The docs list every supported parameter.

A system prompt that writes for the ear

The system prompt does most of the work. Keep it fixed across a series so episodes sound consistent, and move everything that changes (topic, guest, length) into the user message. Here is a prompt for a two-voice show:

SYSTEM = (
    "You write spoken dialogue for two voices.\n"
    "Voice A: warm host, asks short questions.\n"
    "Voice B: dry expert, answers in two or three sentences.\n"
    "Rules:\n"
    "- Output lines in the form 'A: text' or 'B: text', one per line.\n"
    "- Plain spoken English. No markdown, no stage directions, no emoji.\n"
    "- Write numbers and dates as words.\n"
    "- Never address the reader as an AI. Never add disclaimers.\n"
    "- Stay inside the brief. If the brief is vague, make a choice and keep going."
)

text, usage = write_script(
    "Topic: how night-shift bakers plan their week. About 14 lines.",
    SYSTEM,
    temperature=0.8,
)
print(text)

Two details matter here. First, the "stay inside the brief" rule gives the model permission to decide rather than ask a clarifying question, and a question in the middle of a batch run is wasted money. Second, the format rule means a one-line regular expression can split the output into speaker and text. If the model ever drifts, drop lines that do not match the pattern instead of passing them to the speech tool.

Settings worth touching

Temperature, top_p, stop, seed and the frequency and presence penalties are passed through to the model. You do not need all of them, and the table lists what each is for in a script generator.

SettingTryWhy
temperature0.7 to 0.9Varied phrasing for dialogue. Use 0.3 to 0.5 for a steady narrator.
presence_penalty0.2 to 0.6Pushes the script toward new topics instead of circling one.
frequency_penalty0.2 to 0.5Cuts repeated phrases, such as every line opening with the same word.
seedany integerMakes drafts easier to compare while you tune a prompt. Do not rely on it as a guarantee.
stopa marker stringEnds the output at a delimiter, useful when you ask for a script plus notes.
max_tokensoptionalLeave it out unless you want a hard cap; the completion can use the rest of the window.

Change one setting at a time and compare outputs for the same brief. When the script feels stiff, a higher temperature is rarely the fix; a sharper character line in the system prompt usually is.

Long scripts: one scene at a time

The context window is 100,000 tokens, so a short audiobook chapter fits in a single request. For anything longer, or whenever you want to review as you go, write scene by scene and carry a rolling summary forward. The summary keeps each call small, which keeps the input bill small too, since every request is billed for all the prompt tokens it sends.

scenes = [
    "Scene 1: the crew finds the lighthouse lamp still warm.",
    "Scene 2: an argument about who touched the logbook.",
    "Scene 3: a storm cuts the radio.",
]

summary = "Nothing has happened yet."
episode = []

for beat in scenes:
    prompt = "Story so far: %s\n\nWrite the next scene. %s\nAbout 120 words." % (summary, beat)
    text, usage = write_script(prompt, SYSTEM, temperature=0.8)
    episode.append(text)

    s_text, _ = write_script(
        "Summarise in three sentences, keeping names and open questions:\n" + "\n".join(episode),
        "You are a careful story editor.",
        temperature=0.2,
    )
    summary = s_text

print("\n\n".join(episode))

Scenes depend on each other through the summary, so they run in order. Parallelism belongs one level up: produce several episodes at the same time. A full key allows 8 concurrent requests, so a thread pool of 6 or 8 workers, one episode per worker, keeps you under the cap. A trial key allows only 2 at once.

Check the script before it reaches the voice stage

A speech tool will read whatever you give it, so put a cheap gate between the two stages. The line-format rule from the system prompt makes this easy. Keep lines that match, count the words, and reject drafts that are far off the target length instead of voicing them.

import re

LINE = re.compile(r"^([AB]): (.+)$")

def validate(text, min_words=80, max_words=400):
    lines = [l for l in text.splitlines() if l.strip()]
    good = [l for l in lines if LINE.match(l)]
    words = sum(len(LINE.match(l).group(2).split()) for l in good)
    if len(good) < len(lines) * 0.9:
        return False, "too many lines in the wrong format"
    if not (min_words <= words <= max_words):
        return False, "length %d words is outside the target" % words
    return True, ""

When validation fails, retry the same brief once with a slightly different seed rather than editing the output by hand. Since failed or refused requests are not charged, a retry loop with a ceiling of three attempts is a sensible default. Log the rejected drafts for a week. They show you which rule in the system prompt is weakest, and fixing the prompt is cheaper than fixing drafts forever.

Two further checks are worth adding. Scan for characters your speech tool mispronounces, such as slashes, ampersands and long strings of capitals. And scan for the words you banned in the brief; a model follows a "never say" list well but not perfectly, and a regex catches the misses for free.

Streaming a draft to a preview

If an editor or a producer reads the script as it appears, stream it. The API returns server-sent events when you set stream: true, and usage arrives at the end. Remember that a streamed request occupies its concurrency slot until the stream closes, so a forgotten, half-read stream counts against your 8.

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.vibevoice.top/v1",
  apiKey: process.env.API_KEY,
});

const stream = await client.chat.completions.create({
  model: "uncensored",
  stream: true,
  stream_options: { include_usage: true },
  messages: [
    { role: "system", content: "You write short spoken ad copy. Plain text only." },
    { role: "user", content: "Thirty seconds for a late-night radio show about weather." },
  ],
});

for await (const chunk of stream) {
  const piece = chunk.choices[0]?.delta?.content;
  if (piece) process.stdout.write(piece);
}

What a batch costs

The rates are $0.15 per 1M input tokens and $0.50 per 1M output tokens, billed from prepaid credit with no subscription. Work out a real batch. A ten-scene episode where each scene call sends about 1,500 input tokens (system prompt, summary, beat) and returns about 700 output tokens:

  • Input: 10 x 1,500 = 15,000 tokens x $0.15 / 1,000,000 = $0.00225
  • Output: 10 x 700 = 7,000 tokens x $0.50 / 1,000,000 = $0.0035
  • One episode: $0.00575
  • Fifty episodes: $0.2875, about 29 cents

The summary calls add a little on top, so budget a third more and you will be close. The point is that text is the cheap stage of a voice pipeline. Details on top-ups ($20 to $500, with bonuses from $50) are on the pricing page, and failed or refused requests are not charged.

Two cost levers are worth knowing. Output tokens are priced at $0.50 per 1M against $0.15 per 1M for input, so asking for "about 120 words" per scene matters more than trimming the system prompt. And a rolling summary of three sentences costs far less than resending every earlier scene. If a series needs a longer memory, keep a short fact sheet (names, places, running jokes) and append to it, so each call stays small while continuity survives across episodes.

Questions and answers

Does the API produce audio files?

No. It returns text only. Pass the generated script to the text-to-speech tool of your choice.

How long can one script be?

The context window is 100,000 tokens, prompt plus completion, and max_tokens is optional. For long projects, write scene by scene with a rolling summary.

Can I get the same script twice?

The seed parameter is passed through and helps make drafts comparable, but treat it as a convenience, not a guarantee of identical output.

What happens if I send too many requests?

You get HTTP 429 with the code rate_limited or concurrency. A full key allows 8 concurrent requests and 300 per minute, so wait the Retry-After time and try again.

Your key is one form away

Create an account, copy the key, change the base URL. That is the whole setup.

Get API key