TTS API Demo: Live Request and Response Examples
This demo shows real requests you can run in a minute and the response format that comes back. The API is a text model, so the output is script text, JSON and tool calls that a separate speech engine can then read aloud.
Updated

Before you run anything
A fair warning for anyone expecting to press play and hear a voice: this tts api demo produces no sound. The service returns text from one model, uncensored, over POST /v1/chat/completions. There is a second endpoint, GET /v1/models, for listing what is available. Nothing returns audio, images or embeddings.
Why a demo then? Because many speech projects need exactly this part: a script writer, a text cleaner, a splitter that decides who says what. The examples below show those jobs, and the output format is what your speech code will parse.
You need a key. Sign up on the API key page with Google or with email and password, then claim the $0.50 trial credit with one click. It is valid for 7 days and needs no card. Export the key so the commands below can read it:
export XTTS_API_KEY="paste-your-key-here"
curl -s https://api.xtts.cc/v1/models -H "Authorization: Bearer $XTTS_API_KEY" | grep uncensored
If that call returns a JSON list that includes uncensored, the key works and you can continue.
Demo 1: a plain request
The smallest useful call asks for a short spoken-style paragraph:
curl -s https://api.xtts.cc/v1/chat/completions \
-H "Authorization: Bearer $XTTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "uncensored",
"messages": [
{"role": "system", "content": "You write short lines for a voice actor. No stage directions."},
{"role": "user", "content": "Welcome listeners to a show about lighthouses. Two sentences."}
],
"temperature": 0.7,
"max_tokens": 120
}'
The reply looks like the sample below. The text inside content will differ on every run, so treat it as an illustration of the structure and not as recorded output.
{
"id": "chatcmpl-example",
"object": "chat.completion",
"model": "uncensored",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Welcome to the show. Tonight we climb the stairs of the world's loneliest lighthouses."
},
"finish_reason": "stop"
}
],
"usage": {"prompt_tokens": 38, "completion_tokens": 21, "total_tokens": 59}
}
Your speech code reads choices[0].message.content. The finish_reason tells you why generation ended: stop for a natural end, length if max_tokens cut it short. If you leave max_tokens out, the completion may use whatever remains of the 100,000-token window, which covers prompt and completion together.
What the headers tell you
Add -i to the curl command and you will see X-Request-Id, X-Balance-USD, X-RateLimit-Limit-Requests and X-RateLimit-Limit-Concurrency. The balance header is useful in a demo because you can watch it drop by a tiny amount after each call. For this request the usage above works out to 38 x $0.15 / 1,000,000 plus 21 x $0.50 / 1,000,000, which is $0.0000057 plus $0.0000105, about $0.0000162.
Demo 2: streaming the script
For audio you rarely want to wait for the full text. Add "stream": true and the API sends server-sent events, one small piece of text at a time. The Node example below prints text as it arrives, which is where you would feed a sentence queue.
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.xtts.cc/v1",
apiKey: process.env.XTTS_API_KEY,
});
const stream = await client.chat.completions.create({
model: "uncensored",
stream: true,
messages: [
{ role: "user", content: "Read a four-line weather report for a coastal town." },
],
});
for await (const chunk of stream) {
const piece = chunk.choices[0]?.delta?.content ?? "";
process.stdout.write(piece);
}
process.stdout.write("\n");
Run it as an ES module (save as demo.mjs and run node demo.mjs after npm install openai). On the wire each event is a line beginning with data: followed by JSON, and the stream closes with a final marker. Token usage is reported at the end of the stream. One point matters for planning: a streamed request keeps its concurrency slot until the stream ends, so a slow consumer can block your own next request.
Demo 3: tool calling as a speech router
Tool calling lets the model hand back a structured instruction instead of prose. In a voice project that is a clean way to choose a delivery style per line. You define a function called say, and the model fills in its arguments. Your code then decides what to do with them. The model does not run anything itself.
import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.xtts.cc/v1", api_key=os.environ["XTTS_API_KEY"])
tools = [{
"type": "function",
"function": {
"name": "say",
"description": "Queue one line for the voice engine.",
"parameters": {
"type": "object",
"properties": {
"text": {"type": "string"},
"style": {"type": "string", "enum": ["calm", "urgent", "whisper"]},
},
"required": ["text", "style"],
},
},
}]
messages = [
{"role": "system", "content": "Always answer by calling say, once per line."},
{"role": "user", "content": "The alarm sounds. The guard tells everyone to leave."},
]
r = client.chat.completions.create(
model="uncensored", messages=messages, tools=tools, tool_choice="auto"
)
msg = r.choices[0].message
for call in msg.tool_calls or []:
args = json.loads(call.function.arguments)
print(args["style"], "->", args["text"])
Arguments arrive as a JSON string, so parse them and validate before use. Models can produce a style outside your enum or omit a field, and an unchecked value should never reach your engine. If you want the conversation to continue, append the assistant message and a reply with role: "tool" and the matching tool_call_id, then call the API again.
When streaming, tool calls arrive in pieces. The function name comes first and the argument string builds up across chunks, so collect fragments by index and parse once the stream finishes.
Demo 4: JSON mode for a cast list
If you prefer to skip tool definitions, JSON mode gives valid JSON in the message content. It is a good fit for turning a rough scene into lines per speaker.
curl -s https://api.xtts.cc/v1/chat/completions \
-H "Authorization: Bearer $XTTS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "uncensored",
"response_format": {"type": "json_object"},
"temperature": 0.2,
"messages": [
{"role": "system", "content": "Return JSON: {\"lines\": [{\"speaker\": string, \"text\": string}]}"},
{"role": "user", "content": "Ana asks Ben to hold the ladder. Ben agrees reluctantly."}
]
}'
JSON mode guarantees that the output parses, not that it matches your shape. Describe the shape in the prompt, as above, and check keys in code. Low temperature keeps the structure steady.
Demo 5: a long document in one request
The context window is 100,000 tokens, shared by prompt and completion. That is enough to send a long transcript or a whole short story and ask for a version written for the ear. The request body can be up to 8 MB, so size is rarely the limit; the token count is.
Here is the cost of a heavy call. Say you send 80,000 tokens and get 2,000 back. Input: 80,000 x $0.15 / 1,000,000 = $0.012. Output: 2,000 x $0.50 / 1,000,000 = $0.001. The total is $0.013, so the $0.50 trial would cover about 38 calls like it. Long input is cheap compared with long output, which is why summaries and rewrites of big texts are affordable while generating a whole novel in one go is not.
A practical tip for long inputs: put your instructions first and repeat the key rule at the end. Models tend to follow the most recent instruction best, and a one-line reminder after 80,000 tokens of source text costs almost nothing.
Reading errors in the demo
Break something on purpose once so you recognise it later. Send the request with a wrong key and you get an authentication error instead of a completion. Send ten requests at the same instant from a trial key and some come back as HTTP 429.
The 429 has three causes, and the error code tells them apart:
rate_limitedmeans too many requests in one minute: 60 on a trial key, 300 on a full key.concurrencymeans too many requests in flight at once: 2 on a trial key, 8 on a full key.- A 429 with a
Retry-After: 5header means the service was briefly overloaded. Wait five seconds and send it again.
Failed or refused requests are not charged, so errors cost nothing. To see the limits your key has, read X-RateLimit-Limit-Requests and X-RateLimit-Limit-Concurrency on any normal response. A tiny retry loop that sleeps for the Retry-After value, or five seconds when it is missing, is all a demo script needs.
What to try next
Once these four calls work, you have the pieces of a script pipeline. Wrap the plain request in a function, run the streaming loop into a queue, and connect the queue to the speech engine of your choice. Engines differ in price, license and quality, so read their current terms. This service does not provide one and does not state anything about them.
Costs stay small while you experiment. At $0.15 per million input tokens and $0.50 per million output tokens, a typical demo call costs a fraction of a cent, and the $0.50 trial covers hundreds of them. See the pricing page for top-ups from $20 to $500 with bonuses from 5% to 15%, payable by crypto. The docs cover the full parameter list, rate limits (8 concurrent and 300 per minute on a full key) and error codes. Prompts are not used for training.
A reasonable order for a first afternoon: run the plain request until the tone is right, switch to streaming and print sentences as they complete, then add either tool calls or JSON mode, whichever matches the way your speech code wants its input. Keep every prompt in a file under version control. When a line sounds wrong in the final audio, you can then trace it back to the exact instruction that produced it and change one thing at a time.
More guides
Questions and answers
Can I hear a sample voice in this demo?
No. The API returns text, JSON and tool calls only. You supply a separate speech engine to turn the text into audio.
Which model ID do I send?
Send uncensored as the model value, with the base URL https://api.xtts.cc/v1 and your key in the Authorization: Bearer header.
Do the demos work on a trial key?
Yes. A trial key allows 2 concurrent requests and 60 per minute, which is plenty for these examples. Run them one at a time.
Why do I get a 429 response?
You exceeded requests per minute (rate_limited), had too many requests open at once (concurrency), or the service was briefly overloaded. Wait for Retry-After if given, then retry.
Your key is one form away
Create an account, copy the key, change the base URL. That is the whole setup.