Uncensored Text API for TTS Pipelineshttps://api.ttsapihub.com/v1

TTS API Key in Python: Setup, Streaming, JSON Mode

This is a Python walkthrough for the API behind this site: store the key, create a client, stream replies, force JSON output and handle rate limits. One caveat first: it generates text, not audio.

Updated

Glowing key above a terminal with streams of data, illustrating an API key used from Python

Key points

  • The endpoint is OpenAI-compatible, so the openai Python package works with base_url set to https://api.ttsapihub.com/v1.
  • Keep the key in an environment variable, never in source code.
  • Streaming, JSON mode and tool calling are supported; audio output is not.
  • Handle HTTP 429 with a short wait, and read X-Balance-USD to track spending.

Check what the key opens

People who search for a tts api key python example usually want to turn text into spoken audio. The key from this service does not do that. It authenticates requests to a text LLM: POST /v1/chat/completions and GET /v1/models, using the model uncensored. There is no speech synthesis, no audio upload and no embeddings.

If that fits your project, for example because you generate scripts, dialogue or narration that you pass to a separate speech tool, the rest of this page gives you working Python. If you need audio output from the API itself, this is the wrong service, and it is better to know that now. The docs list the exact feature set.

A practical way to use text generation in an audio project is to treat it as the writing stage. The model drafts narration, rewrites a line to fit a time limit, or turns notes into dialogue with several speakers. Your Python code then hands the final text to whichever speech tool you use. Keeping the stages separate also means you can swap either one without touching the other, and you only pay this API for the words it writes.

To follow along you need Python 3.9 or newer and a key. Sign up on the API key page and claim the one-click $0.50 trial credit; it lasts 7 days and needs no card.

Store the key and install the package

Never write the key into a file you commit. Put it in an environment variable and read it at runtime.

# base URL used below: https://api.ttsapihub.com/v1 , model: uncensored
pip install openai
export API_KEY="paste-your-key-here"

On Windows PowerShell the equivalent is $env:API_KEY = "paste-your-key-here". For a project, a .env file loaded with a package such as python-dotenv is a common pattern, as long as .env is in your .gitignore.

If a key leaks into a repository, treat it as burned: remove it from the history if you can, and request a new one. Remember that keys spend prepaid credit, so a leaked key is a financial leak too.

Create the client and make a first call

The official package accepts a base_url. Everything else is the usual OpenAI call shape.

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.ttsapihub.com/v1",
    api_key=os.environ["API_KEY"],
)

resp = client.chat.completions.create(
    model="uncensored",
    messages=[
        {"role": "system", "content": "You write short narration scripts."},
        {"role": "user", "content": "Draft a 40-word intro for a podcast about night trains."},
    ],
    temperature=0.8,
    max_tokens=200,
)

print(resp.choices[0].message.content)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)

The usage object shows how many tokens the call used, so you can compute cost: input tokens times $0.15 per million, plus output tokens times $0.50 per million. If the call above used 60 input tokens and 80 output tokens, the cost is $0.000009 plus $0.00004, which is $0.000049. Failed or refused requests are not charged.

max_tokens is optional. Without it, the completion can use whatever remains of the 100,000-token window, so set it when you want a hard cap on length and cost.

Stream the reply

Streaming returns tokens as they are generated using server-sent events. Set stream=True and iterate.

import os
from openai import OpenAI

client = OpenAI(base_url="https://api.ttsapihub.com/v1", api_key=os.environ["API_KEY"])

stream = client.chat.completions.create(
    model="uncensored",
    messages=[{"role": "user", "content": "Describe a rainy station platform in four sentences."}],
    stream=True,
)

parts = []
for chunk in stream:
    if not chunk.choices:
        continue
    delta = chunk.choices[0].delta.content or ""
    parts.append(delta)
    print(delta, end="", flush=True)

text = "".join(parts)

The guard for an empty choices list is there because the final event of a stream can carry usage data and no text. Skipping it avoids an index error. One operational detail: a streamed request holds its concurrency slot until the stream ends. If you start many streams in threads and never finish reading them, you will hit the concurrency limit even though your request rate is low.

Ask for JSON and call tools

JSON mode

Set response_format to a JSON object to make the reply parseable. Also tell the model in the prompt that you want JSON; the setting constrains the format, and the prompt tells it what shape.

import json, os
from openai import OpenAI

client = OpenAI(base_url="https://api.ttsapihub.com/v1", api_key=os.environ["API_KEY"])

resp = client.chat.completions.create(
    model="uncensored",
    response_format={"type": "json_object"},
    messages=[
        {"role": "system", "content": "Reply only with JSON: {\"title\": str, \"beats\": [str]}"},
        {"role": "user", "content": "Outline a three-beat radio drama about a lost key."},
    ],
)

data = json.loads(resp.choices[0].message.content)
print(data["title"], len(data["beats"]))

Wrap json.loads in a try block anyway. A reply that hits max_tokens can be cut off in the middle of an object.

Tool calling

Pass tools and optionally tool_choice. When the model asks for a tool, run it yourself and send the result back as a message with role: "tool".

import json, os
from openai import OpenAI

client = OpenAI(base_url="https://api.ttsapihub.com/v1", api_key=os.environ["API_KEY"])

tools = [{
    "type": "function",
    "function": {
        "name": "word_count",
        "description": "Count the words in a text",
        "parameters": {
            "type": "object",
            "properties": {"text": {"type": "string"}},
            "required": ["text"],
        },
    },
}]

messages = [{"role": "user", "content": "How many words are in: the quick brown fox?"}]
first = client.chat.completions.create(model="uncensored", messages=messages, tools=tools)
msg = first.choices[0].message

if msg.tool_calls:
    call = msg.tool_calls[0]
    args = json.loads(call.function.arguments)
    result = len(args["text"].split())
    messages.append(msg)
    messages.append({"role": "tool", "tool_call_id": call.id, "content": str(result)})
    final = client.chat.completions.create(model="uncensored", messages=messages, tools=tools)
    print(final.choices[0].message.content)

Keep a conversation and watch the token budget

The API is stateless. If you want a chat that remembers what was said, you keep the message list in your program and send all of it with every call. A small class does the job, and it is also the right place to cap the history so cost does not creep up.

import os
from openai import OpenAI

client = OpenAI(base_url="https://api.ttsapihub.com/v1", api_key=os.environ["API_KEY"])

history = [{"role": "system", "content": "You are a dialogue writer. Keep replies under 120 words."}]

def say(user_text, keep_last=12):
    history.append({"role": "user", "content": user_text})
    # keep the system message plus the most recent turns
    trimmed = [history[0]] + history[1:][-keep_last:]
    resp = client.chat.completions.create(model="uncensored", messages=trimmed, max_tokens=250)
    reply = resp.choices[0].message.content
    history.append({"role": "assistant", "content": reply})
    return reply, resp.usage.total_tokens

print(say("Open a scene in a ticket office at closing time.")[0])

Trimming matters for money more than for the window. The model accepts up to 100,000 tokens in total, but if every turn resends 30,000 tokens of history, each call costs $0.0045 in input alone (30,000 times $0.15 per million). Fifty such turns add up to $0.225 before a single output token is counted. Keeping the last dozen turns, or summarizing older ones into a short note, usually keeps quality and cuts the bill sharply.

Concurrency is the other thing to design for. If your program serves several users at once, a semaphore sized to your key's limit (8 for a full key, 2 for a trial key) keeps you from tripping the concurrency error. Calls above the limit wait in your own queue instead of failing, which is friendlier than retrying on a 429.

Testing is worth a word too. Write one small script that calls the API with a fixed seed, a low temperature and a short max_tokens, and run it whenever you change prompts or settings. A seed and low temperature make the output more repeatable, which lets you compare two prompt versions without noise. Keep these checks tiny; a test that costs a fraction of a cent can run on every commit without anyone noticing the bill.

Finally, log the request ID from each response. When a call behaves strangely, that ID is the quickest way to point support at the exact request.

Handle limits, errors and balance

A full key allows 8 concurrent requests and 300 requests per minute. The trial key allows 2 and 60. Exceeding the first returns HTTP 429 with error code concurrency, and exceeding the second returns 429 with rate_limited. Temporary overload also returns 429 with a Retry-After: 5 header.

The OpenAI package retries some failures on its own, and you can raise that with max_retries. For a batch job, add your own cap on parallel work too, so you stay under the concurrency limit instead of relying on retries.

import os
from openai import OpenAI, RateLimitError

client = OpenAI(base_url="https://api.ttsapihub.com/v1", api_key=os.environ["API_KEY"], max_retries=3)

raw = client.chat.completions.with_raw_response.create(
    model="uncensored",
    messages=[{"role": "user", "content": "One sentence about trains."}],
    max_tokens=60,
)
print("balance:", raw.headers.get("X-Balance-USD"))
print("request id:", raw.headers.get("X-Request-Id"))
print(raw.parse().choices[0].message.content)

The response headers X-Request-Id and X-Balance-USD are useful in logs. Quote the request ID when you contact support. Costs are listed on the pricing page: $0.15 per 1M input tokens and $0.50 per 1M output tokens, with top-ups from $20 to $500 and bonuses of 5%, 10% and 15% at $50, $100 and $200. Card (Visa, Mastercard, Apple Pay, Google Pay) and crypto (USDT on TRC20, USDC on Base) are accepted. Prompts are not used for training. The model is intended for lawful adult use, and sexual content involving minors is never allowed.

Questions and answers

Does this API key work for text to speech in Python?

No. The key authenticates a text LLM API with no audio output. Use it to generate text, then pass that text to a speech tool.

Which Python package should I use?

The official openai package. Set base_url to https://api.ttsapihub.com/v1 and model to uncensored.

How do I keep the key out of my code?

Read it from an environment variable such as API_KEY, and keep any .env file out of version control.

How can I see how much a call cost?

Read resp.usage for token counts and the X-Balance-USD response header for the remaining balance.

What happens when I hit the rate limit?

The API returns HTTP 429 with the code rate_limited or concurrency. Wait a few seconds and retry; the failed request is not charged.

Your key is one form away

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

Get API key