Voices
Speech in consented voices from open models served on the Tenzro Network: one line at a time, streamed, or whole speech datasets in batches, and private custom voices trained on your own consented recordings. Every clip is marked as synthetic.
| Base URL | https://api.tenzro.xyz/voices |
| OpenAI-compatible | POST https://api.tenzro.xyz/v1/audio/speech |
| Auth | A Platform API key with the Voices scopes it needs: voices:speak, voices:batch, voices:train, voices:admin |
| Billing | Credits per 1,000 characters (batch at a lower rate, custom and cloned voices at a premium), per GPU-hour of training; plan allowances first. |
| Console | platform.tenzro.xyz/voices |
GET /voices/status says so and the API answers 503 coming_online; batches and training queue and start by themselves once it is.Models
| Model | Licence | Languages | Cloning | Fine-tuning |
|---|---|---|---|---|
qwen3-tts-1.7b | Apache-2.0 | en, de, fr, es, it, pt, ru, zh, ja, ko | yes | yes |
Only weights with a commercial licence are offered. GET /voices/models lists them with what is serving now and the prices.
Voices and consent
- Stock voices were recorded by speakers who agreed to cloning and commercial synthesis through Fieldwork.
- Cloned voices are yours. The speaker reads a consent statement ending in four random words (
POST /voices/consent-challenges), then records the reference; both go toPOST /voices/voicesas a form (name,challenge_id,consent,reference). The code words must be heard and both recordings must be the same speaker before the voice can speak. Or name a Fieldwork participant who opted into cloning:{ name, consent_ref, reference_key }. Voices of public figures are refused, an agent key cannot create a clone, and a withdrawn consent switches the voice off. - Custom voices are models you trained (below), private to your organisation unless you share them.
GET /voices/voices lists the voices your key may use; DELETE /voices/voices/{id} deletes one of yours with its recordings.
Speech
POST /voices/speech with voice, text (up to 5,000 characters) and optionally model, language, speed (0.5 to 2), seed and format (wav at 24 kHz, or wav16 at 16 kHz). The audio streams back as it is rendered, a sentence at a time; stream: false returns the finished file with its signed provenance manifest inside. Headers name the clip: x-tenzro-output-id, x-tenzro-watermark, x-tenzro-seed.
curl https://api.tenzro.xyz/voices/speech \
-H "Authorization: Bearer $TENZRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"voice": "vc_…", "text": "Hey there, this is Voices.", "language": "en"}' \
--output speech.wavconst res = await fetch("https://api.tenzro.xyz/voices/speech", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TENZRO_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ voice: "vc_…", text: "Hey there.", format: "wav16", stream: false }),
});
const clip = new Uint8Array(await res.arrayBuffer());
console.log(res.headers.get("x-tenzro-output-id"), res.headers.get("x-tenzro-watermark"));OpenAI-compatible
OpenAI clients work with the Platform's base URL: /v1/audio/speech takes OpenAI's body, with a Voices voice id and model, and answers wav or pcm.
from openai import OpenAI
client = OpenAI(base_url="https://api.tenzro.xyz/v1", api_key=os.environ["TENZRO_API_KEY"])
with client.audio.speech.with_streaming_response.create(
model="qwen3-tts-1.7b",
voice="vc_…", # a Voices voice id
input="Hey there, this is Voices.",
response_format="wav", # or "pcm": 24 kHz 16-bit mono
) as r:
r.stream_to_file("speech.wav")Batches into Datasets
A batch renders every phrase (or every line of lines, a file of lines) in every voice, speed and augmentation: rooms (small, medium, large), background noise at a signal-to-noise ratio, and gain. It runs unattended, survives restarts (each clip is its own part and resumes after the last finished one), can be cancelled and resumed, and delivers into a new Dataset (kind audio, source voices) with provenance on every item: {model, model_licence, voice, voice_source, voice_consent, settings, synthetic: true}. Estimate first; credits are drawn per delivered clip.
curl https://api.tenzro.xyz/voices/batches/estimate -H "Authorization: Bearer $TENZRO_API_KEY" -H "Content-Type: application/json" -d @batch.json
curl https://api.tenzro.xyz/voices/batches -H "Authorization: Bearer $TENZRO_API_KEY" -H "Content-Type: application/json" -d @batch.json
# batch.json
{
"name": "Wake word, accented English",
"phrases": ["Hey Sidekick", "Take a photo", "Live translate"],
"voices": ["vc_…", "vc_…", "vc_…"],
"speeds": [0.9, 1, 1.15],
"augmentations": [{}, {"room": "medium"}, {"noise": {"kind": "pink", "snr_db": 20}}],
"language": "en",
"format": "wav16",
"webhook_url": "https://example.com/hooks/voices"
}| Endpoint | |
|---|---|
POST /voices/batches/estimate | Clips, characters, audio and credits, before anything runs |
POST /voices/batches | Start; 201 with the batch |
GET /voices/batches | Your batches, newest first |
GET /voices/batches/{id} | Progress, credits, dataset |
GET /voices/batches/{id}/parts | Each clip and its state (status, after, limit) |
POST /voices/batches/{id}/cancel | Stop; finished clips stay |
POST /voices/batches/{id}/resume | Queue the unfinished clips again |
Webhooks
With webhook_url (public HTTPS), the Platform POSTs the result when a batch or a training job ends. The tenzro-signature header is a detached ES256 JWS over the exact body, signed with the Voices provenance key.
{
"type": "voices.batch.completed",
"batch": { "id": "vb_…", "status": "succeeded", "clips": 81, "done": 81, "failed": 0, "dataset_id": "ds_…", "credits_used": 1.2 },
"created_at": "2026-10-12T09:00:00.000Z"
}Custom voices
Collect recordings with Fieldwork, keep them in a Dataset, and fine-tune a base model on it into a private voice or language. Only items whose speaker agreed to training and to a synthetic voice are used; rejected and withdrawn items never are, and consent is checked again when training starts. A custom voice is one speaker: name the participant in speaker when the dataset has several.
curl https://api.tenzro.xyz/voices/custom/estimate -H "Authorization: Bearer $TENZRO_API_KEY" -H "Content-Type: application/json" \
-d '{"name": "Support voice", "dataset_id": "ds_…", "dataset_version": 2, "base_model": "qwen3-tts-1.7b", "language": "en"}'| Endpoint | |
|---|---|
POST /voices/custom/estimate | Items used and left out (with reasons), audio, credits |
POST /voices/custom | Train: name, dataset_id, dataset_version, base_model, language, speaker, settings {epochs, learning_rate, batch_size}, webhook_url |
GET /voices/custom | Your custom voices and those shared with you |
GET /voices/custom/{id} | State, training progress, credits, the voice id once ready |
POST /voices/custom/{id}/cancel · /resume | Stop training, or train again |
POST /voices/custom/{id}/share | { add: [org ids], remove: [org ids] } (voices:admin) |
POST /voices/custom/{id}/export | A download link for the weights, where the base licence allows (voices:admin) |
DELETE /voices/custom/{id} | Delete the model and its voice |
Marked as synthetic, and detection
Every clip carries three independent marks, as the EU AI Act asks of synthetic audio:
- a signed provenance manifest in the C2PA assertion model (created by a trained algorithm; model, licence, voice, consent, settings), bound to the audio by its SHA-256, inside the WAV file and at
GET /voices/outputs/{id}; - an imperceptible audio watermark embedded by the network, as soon as the network serves it (until then
x-tenzro-watermark: unavailable); - the generation log: every clip's hash and fingerprint.
POST /voices/detect checks any audio against all three, free and without a key, for regulators, media, researchers and anyone else. Each check is reported on its own: manifest valid, invalid or absent; watermark detected, not detected or unavailable; log match.
curl https://api.tenzro.xyz/voices/detect --data-binary @clip.wav -H "Content-Type: audio/wav"MCP
Agents use Voices through the MCP server, under their owner's passkey delegation and the key's scopes: list_voice_models, list_voices, speak, detect_voice, start_voice_batch, get_voice_batch, list_voice_batches, cancel_voice_batch, list_custom_voices, quote_custom_voice, start_custom_voice_training, get_custom_voice_training and the rest. Creating a cloned voice is not a tool: the speaker has to consent themselves.
Prices
| Use | Unit | Credits |
|---|---|---|
| Speech | 1,000 characters | 15 |
| Speech, custom or cloned voice | 1,000 characters | 24 |
| Batch speech | 1,000 characters | 9 |
| Batch speech, custom or cloned voice | 1,000 characters | 15 |
| Custom voice training | GPU-hour | 3,000 |
| Custom voice kept ready | custom voice per month | 1,000 |
| Watermark and provenance check | check | 0 |
Credits are Tenzro Labs credits (1 credit = 0.001 TNZO), not a currency. Plan allowances and the trial apply first; see pricing.
Errors
| Code | Meaning |
|---|---|
401 missing_api_key / invalid_api_key | No key, or a revoked one |
403 key_lacks_scope | Add the Voices scope to the key on the API keys page |
402 insufficient_credit | The balance does not cover the request or the estimate |
404 voice_not_found / model_not_found | Not a voice or model you may use |
409 voice_not_active / voice_consent_withdrawn | The voice is pending, rejected or switched off |
503 coming_online | This part is not served by the network yet |
503 provenance_unavailable | Audio could not be signed, so none was delivered |