Voice API
Text-to-speech and speech-to-text across OpenAI, Deepgram, Cartesia, ElevenLabs, AssemblyAI, and Spitch — one API, provider inferred from the model.
Turn text into speech and speech into text through a single billable, logged, PII-redacted gateway. Pick a model and the gateway routes to the right provider — OpenAI, Deepgram, Cartesia, ElevenLabs, AssemblyAI, or Spitch — with BYOK, spend caps, and cost tracking applied the same way as the rest of the platform.
Endpoints
The provider is inferred from model — you never pass a provider name. Both endpoints default to OpenAI for backward compatibility.
Text-to-Speech
const { audio } = await cencori.voice.speak({
input: 'Hello, welcome to Cencori.',
model: 'aura-asteria-en', // Deepgram — provider inferred
});
// audio is an ArrayBuffer
await fs.writeFile('hello.mp3', Buffer.from(audio));Parameters
TTS models and voices
The authoritative, always-current list comes from GET /api/ai/audio/voices, which returns each model with its voices, default_voice, formats, and max_input_chars. Omit voice to get the default.
Response
POST /api/ai/audio/speech returns raw audio bytes, not JSON:
Content-Type:audio/mpeg(mp3),audio/opus,audio/aac,audio/flac,audio/wav, oraudio/pcmperresponse_formatX-Provider: which provider served the requestX-Request-Id: correlation idContent-Length: byte count (buffered responses)X-Stream: trueon streaming responses, which are chunked and have noContent-Length
Save the body directly to a file or play it — no decoding needed.
Streaming
Set "stream": true to receive the audio as a chunked Transfer-Encoding: chunked response instead of one buffered blob. The client can begin decoding/playing bytes as they arrive, and the server never holds the full file in memory.
curl -X POST https://cencori.com/api/ai/audio/speech \
-H "CENCORI_API_KEY: csk_..." \
-H "Content-Type: application/json" \
-d '{ "input": "A long reply…", "stream": true }' \
--output speech.mp3Notes:
- Providers still synthesize the full utterance before sending bytes (none emit partial audio today), so the win is network-level: playback starts while the file is still in transit, and no
Content-Lengthis sent. - Billing, logging, and validation are identical to the buffered path; the character count is known before the first byte.
- Errors after the first byte cannot change the HTTP status (headers are already sent) — validation errors all occur before streaming starts.
Speech-to-Text
const { text } = await cencori.voice.transcribe({
audio: fileBytes, // Blob, ArrayBuffer, or Uint8Array
model: 'nova-3', // Deepgram — provider inferred
});Parameters
Accepted audio containers
mp3, wav, webm, mp4, m4a, ogg, flac — including browser-recorded audio/webm;codecs=opus and audio/mp4 blobs. The gateway validates by MIME type first, then falls back to the filename extension, so keep a real extension (e.g. recording.webm) on the multipart file name. Files over 25MB are rejected with 400 bad_request before any provider call.
There is no explicit audio-duration cap in the gateway; the 25MB size cap and the 55s provider timeout govern.
STT models
Response formats
Every STT response includes X-Provider (and X-Request-Id).
Speaker diarization
Use diarize() (or diarize: true + response_format: 'verbose_json') with a diarization-capable model to get speaker-attributed segments:
const { segments } = await cencori.voice.diarize({
audio: meetingBytes,
model: 'assemblyai-universal',
});
for (const s of segments) {
console.log(`${s.speaker}: ${s.text}`);
}Subtitles (SRT / VTT)
Set response_format to srt or vtt to get ready-to-use subtitles with timestamps:
const { text: srt } = await cencori.voice.transcribe({
audio: videoAudio,
model: 'nova-3',
response_format: 'srt',
});African languages with Spitch
Spitch is the only provider here doing native Yoruba, Hausa, Igbo, and Amharic — both TTS and STT. Ideal for WhatsApp voice notes and West-African products:
// Speak Yoruba
const { audio } = await cencori.voice.speak({
input: 'Bawo ni, e ku aaro.',
model: 'spitch-tts',
voice: 'sade',
language: 'yo',
});
// Transcribe a Yoruba voice note
const { text } = await cencori.voice.transcribe({
audio: voiceNote,
model: 'spitch-stt',
language: 'yo',
});SDKs
The same surface exists in every SDK — TypeScript, Python, Go, PHP, and Rust.
# Python
audio = cencori.voice.speak("Hello from Cencori.", model="aura-asteria-en")
open("hello.mp3", "wb").write(audio)
result = cencori.voice.transcribe("hello.mp3", model="nova-3")
print(result["text"])// Go
out, _ := c.Voice.Speak(ctx, &cencori.SpeakParams{Input: "Hello.", Model: "aura-asteria-en"})
os.WriteFile("hello.mp3", out.Audio, 0644)
tr, _ := c.Voice.Transcribe(ctx, out.Audio, &cencori.TranscribeParams{Model: "nova-3"})
fmt.Println(tr.Text)Drop-in React components
import { VoiceRecorder, SpeakButton } from 'cencori/react';
// Record from the mic and get the transcript
<VoiceRecorder model="nova-3" apiKey={key} onTranscript={(text) => console.log(text)} />
// Play any text as speech
<SpeakButton text="Hello from Cencori." model="aura-asteria-en" apiKey={key} />HTTP API
Speech (TTS)
curl -X POST https://cencori.com/api/ai/audio/speech \
-H "CENCORI_API_KEY: csk_..." \
-H "Content-Type: application/json" \
-d '{
"input": "Hello world",
"model": "aura-asteria-en"
}' \
--output speech.mp3The response carries an X-Provider header telling you which provider served the request.
Transcription (STT)
curl -X POST https://cencori.com/api/ai/audio/transcriptions \
-H "CENCORI_API_KEY: csk_..." \
-F file=@audio.mp3 \
-F model=nova-3 \
-F diarize=true \
-F response_format=verbose_jsonVoice discovery
# All TTS models with voices, defaults, and limits
curl https://cencori.com/api/ai/audio/voices
# Same registry plus formats / supports_streaming
curl https://cencori.com/api/ai/audio/speech
# STT models, accepted containers, response formats, max file size
curl https://cencori.com/api/ai/audio/transcriptionsThese GET endpoints are unauthenticated and safe to call at startup.
Errors
Errors use the shared flat envelope — see the Error Reference. For these endpoints the relevant cases are:
400 bad_request— unsupported model/voice/format, temperature/speed out of range, missing file, file > 25MB, input > 4096 chars400 provider_not_configured— no managed or BYOK key for the provider400 provider_error/502— upstream provider failed403 credit_balance_exhausted— credit-gated org out of credits (free tier is not credit-gated)429 rate_limit_exceeded— 60 requests/min per project (see Rate Limiting)
Pricing & billing
TTS bills per 1,000 characters; STT bills per minute of audio (derived from the provider's reported duration — a failed request without a duration is never billed). Every request is logged to ai_requests with the provider, model, provider cost, and your Cencori charge (provider cost + markup) — visible in the dashboard alongside chat and vision usage. BYOK keys are used when configured; otherwise the platform key is used. Streaming responses bill the same as buffered ones (characters are known before the first byte).

